Skip to content

Commit

Permalink
Refactor modules, fix multi-instance
Browse files Browse the repository at this point in the history
- Replace instance-dependent modules with globally available plugins to restore multi-instance functionality
- Move all functions from main to App for better testability
- Fix text flashing in fulltext panel
- Fix translations in help panel
- Improve coding style
- Improve unit tests
  • Loading branch information
t11r committed Jun 7, 2023
1 parent e74085d commit 168e183
Show file tree
Hide file tree
Showing 42 changed files with 1,262 additions and 1,433 deletions.
159 changes: 114 additions & 45 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
>
<!-- NOTE: Root element must be focusable for global keyboard events to work -->
<app-header
v-if="ready && (collection['@id'] || manifest['@id'])"
v-if="ready && ($store.collection || $store.manifest)"
:fulltext-enabled="hasOtherContent"
:toc-enabled="hasToc"
/>
Expand All @@ -14,107 +14,176 @@
v-if="ready"
class="tify-main"
>
<template v-if="manifest['@id']">
<template v-if="$store.manifest">
<!-- Scan must come first, other views in arbitrary order -->
<view-scan :id="getId('scan')" />
<view-scan :id="$store.getId('scan')" />

<view-fulltext
v-if="hasOtherContent"
v-show="options.view === 'fulltext'"
:id="getId('fulltext')"
v-show="$store.options.view === 'fulltext'"
:id="$store.getId('fulltext')"
/>
<view-thumbnails
v-show="options.view === 'thumbnails'"
:id="getId('thumbnails')"
v-show="$store.options.view === 'thumbnails'"
:id="$store.getId('thumbnails')"
/>
<view-toc
v-if="hasToc"
v-show="options.view === 'toc'"
:id="getId('toc')"
v-show="$store.options.view === 'toc'"
:id="$store.getId('toc')"
/>
<view-export
v-show="options.view === 'export'"
:id="getId('export')"
v-show="$store.options.view === 'export'"
:id="$store.getId('export')"
/>
</template>

<view-info
v-if="collection['@id'] || manifest['@id']"
v-show="options.view === 'info'"
:id="getId('info')"
v-if="$store.collection || $store.manifest"
v-show="$store.options.view === 'info'"
:id="$store.getId('info')"
/>
<view-collection
v-if="collection['@id']"
v-show="options.view === 'collection'"
:id="getId('collection')"
v-if="$store.collection"
v-show="$store.options.view === 'collection'"
:id="$store.getId('collection')"
/>
<view-help
v-show="options.view === 'help'"
:id="getId('help')"
v-show="$store.options.view === 'help'"
:id="$store.getId('help')"
/>
</div>

<div
v-if="loading"
v-if="$store.loading"
class="tify-loading"
:aria-label="translationLoaded ? translate('Loading') : 'Loading'"
:aria-label="$translate('Loading')"
/>

<section
v-if="errorHandler.messages.length"
v-if="$store.errors.length"
class="tify-error"
>
<button
type="button"
class="tify-error-close"
:aria-label="translate('Dismiss')"
@click="errorHandler.clear()"
:aria-label="$translate('Dismiss')"
@click="$store.clearErrors()"
>
<icon-close />
</button>
<!-- NOTE: Error messages can contain user-controlled content -->
<div class="tify-error-messages">
<p v-for="message in errorHandler.messages" :key="message">
{{ message }}
<!-- NOTE: Error messages can contain user-controlled content -->
<p v-for="error in $store.errors" :key="error">
{{ error }}
</p>
</div>
</section>
</article>
</template>

<script>
import { errorHandler } from './modules/errorHandler';
import { getId } from './modules/id';
import { loading } from './modules/http';
import { translate } from './modules/i18n';
import { canvases, collection, manifest, options, translation } from './modules/store';
export default {
props: {
ready: Boolean,
readyPromise: {
type: Object,
default: null,
},
},
data() {
return {
canvases,
collection,
manifest,
errorHandler,
loading,
options,
translationLoaded: !!translation,
ready: false,
};
},
computed: {
hasOtherContent() {
return canvases.value.some((canvas) => 'otherContent' in canvas);
return this.$store.canvases.some((canvas) => 'otherContent' in canvas);
},
hasToc() {
return manifest.structures && manifest.structures.length > 0;
return this.$store.manifest && this.$store.manifest.structures && this.$store.manifest.structures.length > 0;
},
},
created() {
this.$api.expose(this.setLanguage);
this.$api.expose(this.$store.setPage);
},
mounted() {
this.$store.rootElement = this.$el;
if (!this.$store.options.manifestUrl) {
this.$store.addError('Missing option "manifestUrl"');
return;
}
// Set current breakpoint as classes on container element for use in CSS
this.updateBreakpoint();
new ResizeObserver(this.updateBreakpoint).observe(this.$el);
Promise.all([
this.$store.loadManifest(this.$store.options.manifestUrl),
this.setLanguage(this.$store.options.language),
]).then(() => {
this.$nextTick(() => {
this.ready = true;
this.readyPromise.resolve();
});
}, (error) => {
this.readyPromise.reject(error);
});
},
beforeUnmount() {
clearTimeout(this.$store.urlUpdateTimeout);
window.removeEventListener('popstate', this.$store.initOptions);
},
methods: {
getId,
translate,
setLanguage(language) {
let resolveFunction;
let rejectFunction;
const promise = new Promise((resolve, reject) => {
resolveFunction = resolve;
rejectFunction = reject;
});
if (language === 'en') {
this.$store.options.language = 'en';
this.$translate.setTranslation(null);
resolveFunction(language);
return promise;
}
if (this.$store.options.translationsDirUrl === null) {
rejectFunction(new Error('Could not determine translationsDirUrl'));
return promise;
}
const translationUrl = `${this.$store.options.translationsDirUrl}/${language}.json`;
this.$store.fetchJson(translationUrl).then((loadedTranslation) => {
this.$store.options.language = language;
this.$translate.setTranslation(loadedTranslation);
resolveFunction(language);
}, (error) => {
const status = error.response ? error.response.statusText : error.message;
this.$store.addError(`Error loading translation for "${language}": ${status}`);
rejectFunction(new Error(error));
});
return promise;
},
updateBreakpoint() {
Object.keys(this.$store.options.breakpoints).forEach((breakpoint) => {
if (this.$el.clientWidth <= this.$store.options.breakpoints[breakpoint]) {
this.$el.classList.add(`-${breakpoint}`);
} else {
this.$el.classList.remove(`-${breakpoint}`);
}
});
if (this.$el.clientHeight < 520) {
this.$el.classList.add('-short');
} else {
this.$el.classList.remove('-short');
}
},
},
};
</script>
Expand Down
Loading

0 comments on commit 168e183

Please sign in to comment.