Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • subugoe/emo/tido
1 result
Select Git revision
Show changes
Commits on Source (2)
Showing
with 937 additions and 1130 deletions
......@@ -30,6 +30,12 @@
</template>
<script>
export default {
name: 'TIDO',
}
</script>
<script setup>
import { setCssVar } from 'quasar';
import { biBook } from '@quasar/extras/bootstrap-icons';
import GlobalHeader from '@/components/header/GlobalHeader.vue';
......@@ -38,146 +44,124 @@ import PanelsWrapper from '@/components/panels/PanelsWrapper.vue';
import Notification from '@/components/Notification.vue';
import Loading from '@/components/Loading.vue';
export default {
name: 'TIDO',
components: {
Loading,
PanelsWrapper,
GlobalHeader,
Notification,
},
data() {
return {
errorTitle: '',
errorMessage: '',
isLoading: true,
};
},
computed: {
ready() {
const { collection: collectionUrl, manifest: manifestUrl } = this.config;
if (!this.item) {
return false;
}
if (this.item.annotationCollection && this.annotations === null) {
return false;
}
if (collectionUrl) {
return this.manifests !== null && !!(this.collection) && !!(this.manifest);
}
if (manifestUrl) {
return !!(this.manifest);
}
return true;
},
annotations() {
return this.$store.getters['annotations/annotations'];
},
config() {
return this.$store.getters['config/config'];
},
collection() {
return this.$store.getters['contents/collection'];
},
item() {
return this.$store.getters['contents/item'];
},
manifest() {
return this.$store.getters['contents/manifest'];
},
manifests() {
return this.$store.getters['contents/manifests'];
},
},
created() {
this.emptyIcon = biBook;
},
async mounted() {
this.isLoading = true;
this.$q.dark.set('auto');
await this.loadConfig();
this.$i18n.locale = this.config.lang;
const colorsForceMode = this.config.colors.forceMode;
if (colorsForceMode && colorsForceMode !== 'none') {
this.$q.dark.set(colorsForceMode === 'dark');
}
import { computed, inject, onMounted, ref } from 'vue';
import { useStore } from 'vuex';
import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
if (this.config?.colors?.primary) {
setCssVar('primary', this.config.colors.primary);
}
const store = useStore();
const $q = useQuasar();
const { t, locale: i18nLocale } = useI18n();
if (this.config?.colors?.secondary) {
setCssVar('secondary', this.config.colors.secondary);
}
const errorTitle = ref('');
const errorMessage = ref('');
const isLoading = ref(true);
const emptyIcon = biBook;
const ready = computed(() => {
const { collection: collectionUrl, manifest: manifestUrl } = config.value;
if (!item.value) {
return false;
}
if (item.value.annotationCollection && annotations.value === null) {
return false;
}
if (collectionUrl) {
return manifests.value !== null && !!(collection.value) && !!(manifest.value);
}
if (manifestUrl) {
return !!(manifest.value);
}
return true;
});
const annotations = computed(() => store.getters['annotations/annotations']);
const config = computed(() => store.getters['config/config']);
const collection = computed(() => store.getters['contents/collection']);
const item = computed(() => store.getters['contents/item']);
const manifest = computed(() => store.getters['contents/manifest']);
const manifests = computed(() => store.getters['contents/manifests']);
if (this.config?.colors?.accent) {
setCssVar('accent', this.config.colors.accent);
onMounted(async () => {
isLoading.value = true;
$q.dark.set('auto');
await loadConfig();
i18nLocale.value = config.value.lang;
const colorsForceMode = config.value.colors.forceMode;
if (colorsForceMode && colorsForceMode !== 'none') {
$q.dark.set(colorsForceMode === 'dark');
}
if (config.value?.colors?.primary) {
setCssVar('primary', config.value.colors.primary);
}
if (config.value?.colors?.secondary) {
setCssVar('secondary', config.value.colors.secondary);
}
if (config.value?.colors?.accent) {
setCssVar('accent', config.value.colors.accent);
}
await init();
})
async function getCollection(url) {
await store.dispatch('contents/initCollection', url);
}
async function loadConfig() {
try {
const config = inject('config');
await store.dispatch('config/load', config);
} catch ({ title, message }) {
errorTitle.value = t('config_error');
errorMessage.value = message;
}
}
async function getManifest(url) {
await store.dispatch('contents/initManifest', url);
}
async function getItem(url) {
await store.dispatch('contents/initItem', url);
}
async function init() {
const { collection, manifest, item } = config.value;
try {
// We want to preload all required data that the components need.
// Initialize priority:
// We always load the item first as here is the main data that we want to display.
if (item) {
await getItem(item);
}
await this.init();
},
methods: {
async getCollection(url) {
await this.$store.dispatch('contents/initCollection', url);
},
async loadConfig() {
try {
await this.$store.dispatch('config/load', this.$root.config);
} catch ({ title, message }) {
this.errorTitle = this.$t('config_error');
this.errorMessage = message;
}
},
async getManifest(url) {
await this.$store.dispatch('contents/initManifest', url);
},
async getItem(url) {
await this.$store.dispatch('contents/initItem', url);
},
async init() {
const { collection, manifest, item } = this.config;
try {
// We want to preload all required data that the components need.
// Initialize priority:
// We always load the item first as here is the main data that we want to display.
if (item) {
await this.getItem(item);
}
// After that we load additionally the parent objects.
// If a collection is given we ignore the manifest setting
// and try to figure out the correct manifest by searching for the above item.
// Otherwise, no collection is given but a single manifest instead, so we load that manifest.
if (collection) {
await this.getCollection(collection);
} else if (manifest) {
await this.getManifest(manifest);
}
} catch (e) {
await delay(1000);
this.errorTitle = e.title || 'unknown_error';
this.errorMessage = e.message || 'please_try_again_later';
} finally {
this.isLoading = false;
}
},
async onItemUrlChange(val) {
if (val) {
this.isLoading = false;
}
},
},
};
// After that we load additionally the parent objects.
// If a collection is given we ignore the manifest setting
// and try to figure out the correct manifest by searching for the above item.
// Otherwise, no collection is given but a single manifest instead, so we load that manifest.
if (collection) {
await getCollection(collection);
} else if (manifest) {
await getManifest(manifest);
}
} catch (e) {
await delay(1000);
errorTitle.value = e.title || 'unknown_error';
errorMessage.value = e.message || 'please_try_again_later';
} finally {
isLoading.value = false;
}
}
</script>
<style scoped lang="scss">
......
......@@ -16,93 +16,80 @@
</div>
</template>
<script>
import DomMixin from '@/mixins/dom';
<script setup>
import Notification from '@/components/Notification.vue';
import { computed, readonly, ref, watch } from 'vue';
import { useStore } from 'vuex';
import { request } from '@/utils/http';
import { domParser, delay } from '@/utils';
export default {
name: 'ContentView',
components: {
Notification,
},
mixins: [DomMixin],
props: {
url: String,
type: String,
fontSize: Number,
},
data: () => ({
content: '',
errorTextMessage: null,
}),
computed: {
manifest() {
return this.$store.getters['contents/manifest'];
},
config() {
return this.$store.getters['config/config'];
},
contentStyle() {
return {
fontSize: `${this.fontSize}px`,
};
},
notificationMessage() {
return this.errorTextMessage;
},
},
watch: {
url: {
handler: 'loadContent',
immediate: true,
},
},
methods: {
async loadContent(url) {
this.content = '';
try {
if (!url) {
return;
}
this.errorTextMessage = '';
this.$emit('loading', true);
await delay(300);
const data = await request(url);
this.isValidTextContent(data);
const dom = domParser(data);
this.content = dom.documentElement.innerHTML;
setTimeout(async () => {
this.$emit('loading', false);
const root = document.getElementById('text-content');
this.$store.dispatch('annotations/addHighlightAttributesToText', root);
await this.$store.dispatch('annotations/addHighlightClickListeners');
// TODO: Enable Hover + Tooltip feature when reqs are clarified
// await this.$store.dispatch('annotations/addHighlightHoverListeners');
this.$store.commit('contents/setActiveContentUrl', this.url);
}, 100);
} catch (err) {
this.errorTextMessage = err.message;
}
},
isValidTextContent(text) {
let parsed;
try {
parsed = JSON.parse(text);
} catch (err) {
// TODO : Handle json parsing more gracefully
}
if (parsed && parsed.status === 500) {
throw new Error('no_text_in_view');
}
},
},
};
const props = defineProps({
url: String,
type: String,
fontSize: Number,
});
const emit = defineEmits(['loading']);
const store = useStore();
const content = ref('');
const errorTextMessage = ref(null);
const notificationMessage = readonly(errorTextMessage);
const manifest = computed(() => store.getters['contents/manifest']);
const config = computed(() => store.getters['config/config']);
const contentStyle = computed(() => ({
fontSize: `${props.fontSize}px`,
}));
watch(
() => props.url,
loadContent,
{ immediate: true },
);
async function loadContent(url) {
content.value = '';
try {
if (!url) {
return;
}
errorTextMessage.value = '';
emit('loading', true);
await delay(300);
const data = await request(url);
isValidTextContent(data);
const dom = domParser(data);
content.value = dom.documentElement.innerHTML;
setTimeout(async () => {
emit('loading', false);
const root = document.getElementById('text-content');
store.dispatch('annotations/addHighlightAttributesToText', root);
await store.dispatch('annotations/addHighlightClickListeners');
// TODO: Enable Hover + Tooltip feature when reqs are clarified
// await store.dispatch('annotations/addHighlightHoverListeners');
store.commit('contents/setActiveContentUrl', props.url);
}, 100);
} catch (err) {
errorTextMessage.value = err.message;
}
}
function isValidTextContent(text) {
let parsed;
try {
parsed = JSON.parse(text);
} catch (err) {
// TODO : Handle json parsing more gracefully
}
if (parsed && parsed.status === 500) {
throw new Error('no_text_in_view');
}
}
</script>
<style lang="scss" scoped>
......
......@@ -7,91 +7,80 @@
</div>
</template>
<script>
<script setup>
import OpenSeadragon from 'openseadragon';
import { delay } from '@/utils';
import Notification from '@/components/Notification.vue';
export default {
name: 'ImageView',
components: {
Notification,
},
data() {
return {
viewer: null,
error: null,
};
},
computed: {
item() {
return this.$store.getters['contents/item'];
},
imageUrl() {
return this.item.image?.id;
},
options() {
return {
id: 'openseadragon',
tileSources: {
type: 'image',
url: this.imageUrl,
},
maxZoomLevel: 10,
zoomInButton: 'zoom-in',
zoomOutButton: 'zoom-out',
homeButton: 'default',
fullPageButton: 'fullscreen',
};
},
},
watch: {
item: {
async handler() {
if (!this.item.image) {
this.error = { message: 'no_image_available' };
return;
}
this.$emit('loading', true);
try {
const response = await fetch(this.item.image.id);
if (response.status === 500) throw { message: 'error_image_not_exists' };
if (response.status !== 200 && response.status !== 201) throw { message: 'error_vpn' };
this.error = null;
await delay(1000);
this.initOpenSeagragon();
} catch (error) {
this.error = error;
this.$emit('loading', false);
}
},
immediate: true,
},
import { computed, ref, watch } from 'vue';
import { useStore } from 'vuex';
import { delay } from '@/utils';
const emit = defineEmits('loading');
const store = useStore();
const viewer = ref(null);
const error = ref(null);
const item = computed(() => store.getters['contents/item']);
const imageUrl = computed(() => item.value?.image?.id);
const options = computed(() => ({
id: 'openseadragon',
tileSources: {
type: 'image',
url: imageUrl.value,
},
methods: {
initOpenSeagragon() {
if (this.viewer) {
this.viewer.destroy();
this.viewer = null;
}
this.viewer = new OpenSeadragon.Viewer(this.options);
this.viewer.controlsFadeDelay = 1000;
this.viewer.addHandler('tile-loaded', () => {
this.$emit('loading', false);
});
this.viewer.addHandler('open-failed', () => {
this.error = { message: 'error_open_image' };
this.$emit('loading', false);
});
},
maxZoomLevel: 10,
zoomInButton: 'zoom-in',
zoomOutButton: 'zoom-out',
homeButton: 'default',
fullPageButton: 'fullscreen',
}));
watch(
item,
async () => {
if (!item.value?.image) {
error.value = { message: 'no_image_available' };
return;
}
emit('loading', true);
try {
const response = await fetch(item.value.image.id);
if (response.status === 500) throw { message: 'error_image_not_exists' };
if (response.status !== 200 && response.status !== 201) throw { message: 'error_vpn' };
error.value = null;
await delay(1000);
initOpenSeagragon();
} catch (error) {
error.value = error;
emit('loading', false);
}
},
};
{ immediate: true },
)
function initOpenSeagragon() {
if (viewer.value) {
viewer.value.destroy();
viewer.value = null;
}
viewer.value = new OpenSeadragon.Viewer(options.value);
viewer.value.controlsFadeDelay = 1000;
viewer.value.addHandler('tile-loaded', () => {
emit('loading', false);
});
viewer.value.addHandler('open-failed', () => {
error.value = { message: 'error_open_image' };
emit('loading', false);
});
}
</script>
<style lang="scss" scoped>
......
......@@ -12,22 +12,18 @@
</q-inner-loading>
</template>
<script>
<script setup>
import { useQuasar } from 'quasar';
export default {
name: 'Loading',
props: {
background: String,
},
data: () => ({
loading: true,
}),
methods: {
getBackground() {
if (!this.background) return this.$q.dark.isActive ? 'bg-grey-9' : 'bg-white';
if (this.background === 'none') return 'bg-transparent';
return this.background;
},
},
};
const props = defineProps({
background: String,
});
const $q = useQuasar();
function getBackground() {
if (!props.background) return $q.dark.isActive ? 'bg-grey-9' : 'bg-white';
if (props.background === 'none') return 'bg-transparent';
return props.background;
}
</script>
......@@ -16,54 +16,45 @@
</div>
</template>
<script>
<script setup>
import { biInfoCircleFill, biExclamationTriangleFill } from '@quasar/extras/bootstrap-icons';
import { computed } from 'vue';
import { useStore } from 'vuex';
export default {
name: 'Notification',
props: {
message: {
type: String,
default: () => '',
},
title: String,
type: {
type: String,
default: () => '',
},
const props = defineProps({
message: {
type: String,
default: () => '',
},
data() {
return {
};
},
computed: {
config() {
return this.$store.getters['config/config'];
},
notificationColors() {
return this.config.notificationColors;
},
color() {
switch (this.type) {
case 'info':
return this.notificationColors?.info ? this.notificationColors.info : '';
case 'warning':
return this.notificationColors?.warning ? this.notificationColors.warning : '';
default:
return '';
}
},
icon() {
switch (this.type) {
case 'info':
return biInfoCircleFill;
case 'warning':
return biExclamationTriangleFill;
default:
return '';
}
},
title: String,
type: {
type: String,
default: () => '',
},
};
});
const store = useStore();
const config = computed(() => store.getters['config/config']);
const notificationColors = computed(() => config.value.notificationColors);
const color = computed(() => {
switch (props.type) {
case 'info':
return notificationColors.value?.info ? notificationColors.value.info : '';
case 'warning':
return notificationColors.value?.warning ? notificationColors.value.warning : '';
default:
return '';
}
});
const icon = computed(() => {
switch (props.type) {
case 'info':
return biInfoCircleFill;
case 'warning':
return biExclamationTriangleFill;
default:
return '';
}
});
</script>
......@@ -19,211 +19,202 @@
</div>
</template>
<script>
<script setup>
import { biChevronRight } from '@quasar/extras/bootstrap-icons';
import { delay, isElementVisible } from '@/utils';
import { request } from '@/utils/http';
async function getManifest(url) {
return request(url);
import { computed, nextTick, ref, watch } from 'vue';
import { useStore } from 'vuex';
import { useI18n } from 'vue-i18n';
const emit = defineEmits(['loading'])
const expandIcon = biChevronRight;
const store = useStore();
const { t } = useI18n();
const isLoading = ref(false);
const expanded = ref([]);
const selected = ref(null);
const tree = ref([]);
const treeRef = ref(null);
const containerRef = ref(null);
const config = computed(() => store.getters['config/config']);
const collectionTitle = computed(() => store.getters['contents/collectionTitle']);
const collection = computed(() => store.getters['contents/collection']);
const labels = computed(() => (config.value && config.value.labels) || {});
const item = computed(() => store.getters['contents/item']);
const itemUrl = computed(() => store.getters['contents/itemUrl']);
const manifest = computed(() => store.getters['contents/manifest']);
const manifests = computed(() => store.getters['contents/manifests']);
watch(
itemUrl,
onItemUrlChange,
)
async function onItemUrlChange() {
selected.value = itemUrl.value;
}
export default {
name: 'TreeView',
data() {
return {
isLoading: false,
expanded: [],
selected: null,
tree: [],
treeRef: null,
};
},
computed: {
config() {
return this.$store.getters['config/config'];
},
collectionTitle() {
return this.$store.getters['contents/collectionTitle'];
},
collection() {
return this.$store.getters['contents/collection'];
},
labels() {
return this.config.labels || {};
},
item() {
return this.$store.getters['contents/item'];
},
itemUrl() {
return this.$store.getters['contents/itemUrl'];
},
manifest() {
return this.$store.getters['contents/manifest'];
},
manifests() {
return this.$store.getters['contents/manifests'];
},
},
watch: {
itemUrl: {
handler: 'onItemUrlChange',
},
collection: {
handler: 'onCollectionChange',
immediate: true,
},
manifest: {
handler: 'onManifestChange',
immediate: true,
},
selected: {
handler: 'onSelectedChange',
immediate: true,
},
},
created() {
this.expandIcon = biChevronRight;
},
methods: {
async onCollectionChange() {
if (this.collection) {
this.$emit('loading', true);
this.tree = [{
label: this.collectionTitle,
selectable: false,
url: this.collectionTitle,
children: this.collection.sequence.map(({ label: manifestLabel, id: manifestId }, i) => ({
label: manifestLabel ?? this.getDefaultManifestLabel(i),
lazy: this.manifest.id !== manifestId,
url: manifestId,
selectable: false,
// Prerender item tree elements for the manifest that should be open at initial load
// and don't render children on every other manifest. They will be lazy loaded on expand.
...((this.manifest.id === manifestId)
? {
children: this.manifest.sequence.map(({ id, label: itemLabel }, j) => ({
label: itemLabel ?? this.getDefaultItemLabel(j),
url: id,
parent: manifestId,
})),
}
: {}),
watch(
collection,
onCollectionChange,
{ immediate: true },
)
async function onCollectionChange() {
if (collection.value) {
emit('loading', true);
tree.value = [{
label: collectionTitle.value,
selectable: false,
url: collectionTitle.value,
children: collection.value.sequence.map(({ label: manifestLabel, id: manifestId }, i) => ({
label: manifestLabel ?? getDefaultManifestLabel(i),
lazy: manifest.value.id !== manifestId,
url: manifestId,
selectable: false,
// Prerender item tree elements for the manifest that should be open at initial load
// and don't render children on every other manifest. They will be lazy loaded on expand.
...((manifest.value.id === manifestId)
? {
children: manifest.value.sequence.map(({ id, label: itemLabel }, j) => ({
label: itemLabel ?? getDefaultItemLabel(j),
url: id,
parent: manifestId,
})),
}
)),
}];
this.$nextTick(() => {
this.expanded = [this.collectionTitle, this.manifest.id];
this.selected = this.itemUrl !== '' ? this.itemUrl : this.manifest.sequence[0]?.id;
});
}
},
async onManifestChange() {
const { label, sequence, id: manifestId } = this.manifest;
if (!this.collection) {
this.$emit('loading', true);
await delay(300);
this.tree = [{
label: label ?? this.getDefaultManifestLabel(),
sequence,
url: manifestId,
selectable: false,
children: (Array.isArray(sequence) ? sequence : [sequence]).map(({ id: itemId, label: itemLabel }, j) => ({
label: itemLabel ?? this.getDefaultItemLabel(j),
url: itemId,
parent: manifestId,
})),
}];
: {}),
}
)),
}];
this.$nextTick(() => {
const node = this.$refs.treeRef.getNodeByKey(manifestId);
// We need to remove the lazy loading off of this manifest node and insert children manually,
// because Quasar does not trigger lazy loading on this.expanded change.
node.lazy = false;
node.children = this.manifest.sequence.map(({ id, label: itemLabel }, j) => ({
label: itemLabel ?? this.getDefaultItemLabel(j),
url: id,
parent: manifestId,
}));
this.expanded.push(manifestId);
this.selected = this.itemUrl !== '' ? this.itemUrl : sequence[0]?.id;
});
},
async onItemUrlChange() {
this.selected = this.itemUrl;
},
async onLazyLoad({ node, fail, done }) {
const { url, children } = node;
if (!url) {
done(children);
return;
}
nextTick(() => {
expanded.value = [collectionTitle.value, manifest.value.id];
selected.value = itemUrl.value !== '' ? itemUrl.value : manifest.value.sequence[0]?.id;
});
}
}
const manifest = await getManifest(url);
watch(
manifest,
onManifestChange,
{ immediate: true },
)
async function onManifestChange() {
const { label, sequence, id: manifestId } = manifest.value;
if (!collection.value) {
emit('loading', true);
await delay(300);
tree.value = [{
label: label ?? getDefaultManifestLabel(),
sequence,
url: manifestId,
selectable: false,
children: (Array.isArray(sequence) ? sequence : [sequence]).map(({ id: itemId, label: itemLabel }, j) => ({
label: itemLabel ?? getDefaultItemLabel(j),
url: itemId,
parent: manifestId,
})),
}];
}
if (!manifest) {
fail();
return;
}
nextTick(() => {
const node = treeRef.value.getNodeByKey(manifestId);
// We need to remove the lazy loading off of this manifest node and insert children manually,
// because Quasar does not trigger lazy loading on this.expanded change.
node.lazy = false;
node.children = manifest.value.sequence.map(({ id, label: itemLabel }, j) => ({
label: itemLabel ?? getDefaultItemLabel(j),
url: id,
parent: manifestId,
}));
expanded.value.push(manifestId);
selected.value = itemUrl.value !== '' ? itemUrl.value : sequence[0]?.id;
});
}
const itemNodes = manifest.sequence.map(({ id, label: itemLabel }, j) => ({
label: itemLabel ?? this.getDefaultItemLabel(j),
url: id,
parent: manifest.id,
}));
if (itemNodes) done(itemNodes);
else fail();
},
onSelectedChange(value) {
const { treeRef } = this.$refs;
if (!treeRef) return;
const node = treeRef.getNodeByKey(value);
if (!node) return;
const { url: itemUrl, parent: manifestUrl } = node;
this.$nextTick(async () => {
await delay(600);
const el = document.getElementById(this.itemUrl);
if (el && !isElementVisible(el, this.$refs.containerRef)) {
this.scrollIntoView(el);
}
await delay(100);
this.$emit('loading', false);
});
watch(
selected,
onSelectedChange,
{ immediate: true },
)
function onSelectedChange(value) {
if (!treeRef.value) return;
if (itemUrl === this.itemUrl) return;
const node = treeRef.value.getNodeByKey(value);
if (!node) return;
if (manifestUrl !== this.manifest.id) {
this.$store.dispatch('contents/initManifest', manifestUrl);
this.$store.dispatch('config/setDefaultActiveViews');
this.expanded.push(manifestUrl);
}
const { url: _itemUrl, parent: _manifestUrl } = node;
nextTick(async () => {
await delay(600);
const el = document.getElementById(itemUrl.value);
if (el && !isElementVisible(el, containerRef.value)) {
scrollIntoView(el);
}
await delay(100);
emit('loading', false);
});
if (_itemUrl === itemUrl.value) return;
if (!this.expanded.includes(manifestUrl)) this.expanded.push(manifestUrl);
this.$store.dispatch('contents/initItem', itemUrl);
},
getDefaultManifestLabel(index) {
const prefix = this.labels.manifest ?? this.$t('manifest');
return `${prefix} ${index !== undefined ? index + 1 : ''}`;
},
getDefaultItemLabel(index) {
const prefix = this.labels.item ?? this.$t('page');
return `${prefix} ${index + 1}`;
},
scrollIntoView(el) {
el.scrollIntoView({ block: 'center', behavior: 'smooth' });
},
},
};
if (_manifestUrl !== manifest.value.id) {
store.dispatch('contents/initManifest', _manifestUrl);
store.dispatch('config/setDefaultActiveViews');
expanded.value.push(_manifestUrl);
}
if (!expanded.value.includes(_manifestUrl)) expanded.value.push(_manifestUrl);
store.dispatch('contents/initItem', _itemUrl);
}
async function getManifest(url) {
return request(url);
}
async function onLazyLoad({ node, fail, done }) {
const { url, children } = node;
if (!url) {
done(children);
return;
}
const manifest = await getManifest(url);
if (!manifest) {
fail();
return;
}
const itemNodes = manifest.sequence.map(({ id, label: itemLabel }, j) => ({
label: itemLabel ?? getDefaultItemLabel(j),
url: id,
parent: manifest.id,
}));
if (itemNodes) done(itemNodes);
else fail();
}
function getDefaultManifestLabel(index) {
const prefix = labels.value.manifest ?? t('manifest');
return `${prefix} ${index !== undefined ? index + 1 : ''}`;
}
function getDefaultItemLabel(index) {
const prefix = labels.value.item ?? t('page');
return `${prefix} ${index + 1}`;
}
function scrollIntoView(el) {
el.scrollIntoView({ block: 'center', behavior: 'smooth' });
}
</script>
<style scoped lang="scss">
......
......@@ -6,33 +6,25 @@
size="16px"
/>
</template>
<script>
<script setup>
import { computed } from 'vue';
import { Dark } from 'quasar';
import { isUrl } from '@/utils';
import { icon } from '@/utils/icon';
export default {
name: 'AnnotationIcon',
props: {
name: String,
},
computed: {
show() {
return !!(this.types.find((type) => type.name === this.contentType)?.icon);
},
isDarkMode() {
return Dark.isActive;
},
iconName() {
return isUrl(this.name) ? `img:${this.name}` : this.getIcon(this.name);
},
},
methods: {
getIcon(name) {
return icon(name);
},
},
};
const props = defineProps({
name: String,
});
const isDarkMode = computed(() => Dark.isActive);
const iconName = computed(() => (
isUrl(props.name) ? `img:${props.name}` : getIcon(props.name)
));
function getIcon() {
return icon(props.name);
}
</script>
<style scoped>
......
......@@ -22,51 +22,43 @@
</q-list>
</template>
<script>
<script setup>
import AnnotationIcon from '@/components/annotations/AnnotationIcon.vue';
export default {
name: 'AnnotationsList',
components: { AnnotationIcon },
props: {
activeAnnotation: {
type: Object,
default: () => {},
},
configuredAnnotations: {
type: Array,
default: () => [],
},
toggle: {
type: Function,
default: () => null,
},
types: Array,
import { computed } from 'vue';
const props = defineProps({
activeAnnotation: {
type: Object,
default: () => {},
},
computed: {
config() {
return this.$store.getters['config/config'];
},
annotationTypesMapping() {
return this.types.reduce((prev, curr) => {
prev[curr.name] = curr.annotationType || 'annotation';
return prev;
}, {});
},
configuredAnnotations: {
type: Array,
default: () => [],
},
methods: {
isActive(annotation) {
return !!this.activeAnnotation[annotation.id];
},
isText(annotation) {
return this.annotationTypesMapping[annotation.body['x-content-type']] === 'text';
},
getIconName(typeName) {
return this.types.find(({ name }) => name === typeName)?.icon || 'biPencilSquare';
},
toggle: {
type: Function,
default: () => null,
},
};
types: Array,
});
const annotationTypesMapping = computed(() => (
props.types.reduce((prev, curr) => {
prev[curr.name] = curr.annotationType || 'annotation';
return prev;
}, {})
));
function isActive(annotation) {
return !!props.activeAnnotation[annotation.id];
}
function isText(annotation) {
return annotationTypesMapping.value[annotation.body['x-content-type']] === 'text';
}
function getIconName(typeName) {
return props.types.find(({ name }) => name === typeName)?.icon || 'biPencilSquare';
}
</script>
<style lang="scss" scoped>
......
......@@ -20,98 +20,79 @@
</div>
</template>
<script>
<script setup>
import AnnotationsList from '@/components/annotations/AnnotationsList.vue';
import Notification from '@/components/Notification.vue';
import * as AnnotationUtils from '@/utils/annotations';
export default {
name: 'AnnotationsView',
components: {
AnnotationsList,
Notification,
},
data: () => ({
message: 'no_annotations_in_view',
}),
props: {
url: String,
types: Array,
},
computed: {
config() {
return this.$store.getters['config/config'];
},
annotations() {
return this.$store.getters['annotations/annotations'];
},
item() {
return this.$store.getters['contents/item'];
},
activeAnnotations() {
return this.$store.getters['annotations/activeAnnotations'];
},
filteredAnnotations() {
return this.$store.getters['annotations/filteredAnnotations'];
},
activeContentUrl() {
return this.$store.getters['contents/activeContentUrl'];
},
updateTextHighlighting() {
// We need to make sure that annotations are loaded (this.annotations),
// the text HTML is present in DOM (this.activeContentUrl is set after DOM update)
// and the annotation are filtered by type (this.filteredAnnotations).
return `${this.annotations !== null}|${this.activeContentUrl}`;
},
},
watch: {
updateTextHighlighting: {
handler(contentData) {
if (contentData) {
const [hasAnnotations, activeContentUrl] = contentData.split('|');
if (hasAnnotations !== 'true' && activeContentUrl === 'null') return;
this.$store.dispatch('annotations/setFilteredAnnotations', this.types);
this.highlightTargetsLevel0();
}
},
immediate: true,
},
},
beforeUnmount() {
return this.$store.dispatch('annotations/resetAnnotations');
import { computed, onBeforeUnmount, ref, watch } from 'vue';
import { useStore } from 'vuex';
const props = defineProps({
url: String,
types: Array,
});
const store = useStore();
const message = ref('no_annotations_in_view');
const config = computed(() => store.getters['config/config']);
const annotations = computed(() => store.getters['annotations/annotations']);
const activeAnnotations = computed(() => store.getters['annotations/activeAnnotations']);
const filteredAnnotations = computed(() => store.getters['annotations/filteredAnnotations']);
const activeContentUrl = computed(() => store.getters['contents/activeContentUrl']);
const updateTextHighlighting = computed(() => {
// We need to make sure that annotations are loaded (this.annotations),
// the text HTML is present in DOM (this.activeContentUrl is set after DOM update)
// and the annotation are filtered by type (this.filteredAnnotations).
return `${annotations.value !== null}|${activeContentUrl.value}`;
});
watch(
updateTextHighlighting,
(contentData) => {
const [hasAnnotations, activeContentUrl] = contentData.split('|');
if (hasAnnotations !== 'true' && activeContentUrl === 'null') return;
store.dispatch('annotations/setFilteredAnnotations', props.types);
highlightTargetsLevel0();
},
methods: {
addAnnotation(id) {
this.$store.dispatch('annotations/addActiveAnnotation', id);
},
removeAnnotation(id) {
this.$store.dispatch('annotations/removeActiveAnnotation', id);
},
toggle({ id }) {
const exists = !!this.activeAnnotations[id];
if (exists) {
this.removeAnnotation(id);
} else {
this.addAnnotation(id);
}
},
highlightTargetsLevel0() {
const mergedSelector = this.filteredAnnotations
.reduce((acc, cur) => {
const selector = AnnotationUtils.generateTargetSelector(cur);
if (acc !== '') {
acc += ',';
}
acc += selector;
return acc;
}, '');
{ immediate: true },
);
onBeforeUnmount(() => store.dispatch('annotations/resetAnnotations'));
function addAnnotation(id) {
store.dispatch('annotations/addActiveAnnotation', id);
}
function removeAnnotation(id) {
store.dispatch('annotations/removeActiveAnnotation', id);
}
if (mergedSelector) {
AnnotationUtils.highlightTargets(mergedSelector, { level: 0 });
function toggle({ id }) {
const exists = !!activeAnnotations.value[id];
if (exists) {
removeAnnotation(id);
} else {
addAnnotation(id);
}
}
function highlightTargetsLevel0() {
const mergedSelector = filteredAnnotations.value
.reduce((acc, cur) => {
const selector = AnnotationUtils.generateTargetSelector(cur);
if (acc !== '') {
acc += ',';
}
},
},
};
acc += selector;
return acc;
}, '');
if (mergedSelector) {
AnnotationUtils.highlightTargets(mergedSelector, { level: 0 });
}
}
</script>
<style scoped>
......
......@@ -17,47 +17,30 @@
</div>
</template>
<script>
<script setup>
import Navbar from '@/components/header/Navbar.vue';
import TitleBar from '@/components/header/TitleBar.vue';
import PanelsToggle from '@/components/header/PanelsToggle.vue';
import Tools from '@/components/header/Tools.vue';
export default {
name: 'GlobalHeader',
components: {
Navbar,
TitleBar,
PanelsToggle,
Tools,
},
props: {
configErrorTitle: {
type: String,
default: () => '',
},
},
computed: {
show() {
return this.config.header?.show;
},
manifests() {
return this.$store.getters['contents/manifests'];
},
config() {
return this.$store.getters['config/config'];
},
item() {
return this.$store.getters['contents/item'];
},
showNavbar() {
return this.config.header?.navigation || true;
},
showPanelsToggle() {
return this.config.header?.panelsToggle !== undefined ? this.config.header?.panelsToggle : true;
},
import { computed } from 'vue';
import { useStore } from 'vuex';
const props = defineProps({
configErrorTitle: {
type: String,
default: () => '',
},
};
});
const store = useStore();
const show = computed(() => config.value?.header?.show);
const manifests = computed(() => store.getters['contents/manifests']);
const config = computed(() => store.getters['config/config']);
const item = computed(() => store.getters['contents/item']);
const showNavbar = computed(() => config.value?.header?.navigation || true);
const showPanelsToggle = computed(() => config.value?.header?.panelsToggle !== undefined ? config.value?.header?.panelsToggle : true);
</script>
<style lang="scss" scoped>
......
......@@ -19,42 +19,39 @@
</div>
</template>
<script>
<script setup>
import { computed, onMounted, ref, watch } from 'vue';
import { useStore } from 'vuex';
import { useI18n } from 'vue-i18n';
import { biTranslate } from '@quasar/extras/bootstrap-icons';
export default {
name: 'Language',
data() {
return {
langs: [
{ label: 'DE', value: 'de-de' },
{ label: 'EN', value: 'en-US' },
],
selectedLang: 'en-US',
};
},
watch: {
selectedLang(lang) {
this.$i18n.locale = lang;
},
},
created() {
this.icon = biTranslate;
},
mounted() {
this.selectedLang = this.config.lang || 'en-US';
},
computed: {
config() {
return this.$store.getters['config/config'];
},
},
methods: {
handleLanguageChange(lang) {
this.selectedLang = lang.value;
},
const store = useStore();
const { locale: i18nLocale } = useI18n();
const langs = ref([
{ label: 'DE', value: 'de-de' },
{ label: 'EN', value: 'en-US' },
]);
const selectedLang = ref('en-US');
const icon = biTranslate;
const config = computed(() => store.getters['config/config']);
watch(
selectedLang,
(lang) => {
i18nLocale.value = lang;
},
};
);
onMounted(() => {
selectedLang.value = config.value.lang || 'en-US';
});
function handleLanguageChange(lang) {
selectedLang.value = lang.value;
}
</script>
<style lang="scss">
......
......@@ -28,107 +28,96 @@
</div>
</template>
<script>
<script setup>
import { computed } from 'vue';
import { useStore } from 'vuex';
import { useI18n } from 'vue-i18n';
import { biArrowLeft, biArrowRight } from '@quasar/extras/bootstrap-icons';
export default {
name: 'Navbar',
computed: {
manifest() {
return this.$store.getters['contents/manifest'];
},
manifests() {
return this.$store.getters['contents/manifests'];
},
item() {
return this.$store.getters['contents/item'];
},
itemUrl() {
return this.$store.getters['contents/itemUrl'];
},
itemIndex() {
return this.manifest ? this.manifest.sequence.findIndex(({ id }) => id === this.itemUrl) : -1;
},
hasPrev() {
const prevIndex = this.itemIndex - 1;
if (prevIndex < 0) {
if (this.manifests === null) return false;
const prevManifestIndex = this.manifests.findIndex(({ id }) => id === this.manifest.id) - 1;
if (prevManifestIndex < 0) return false;
}
const store = useStore();
const { t } = useI18n();
return true;
},
hasNext() {
const nextIndex = this.itemIndex + 1;
if (nextIndex > this.manifest.sequence.length - 1) {
if (this.manifests === null) return false;
const nextManifestIndex = this.manifests.findIndex(({ id }) => id === this.manifest.id) + 1;
if (nextManifestIndex > this.manifests.length - 1) return false;
}
return true;
},
nextButtonLabel() {
return this.itemIndex === this.manifest.sequence.length - 1
? `${this.$t('next')} ${this.$t(this.labels.manifest)}`
: `${this.$t('next')} ${this.$t(this.labels.item)}`;
},
const prevIcon = biArrowLeft;
const nextIcon = biArrowRight;
prevButtonLabel() {
return this.itemIndex === 0
? `${this.$t('prev')} ${this.$t(this.labels.manifest)}`
: `${this.$t('prev')} ${this.$t(this.labels.item)}`;
},
labels() {
return this.$store.getters['config/config'].labels || {
manifest: 'manifest',
item: 'item',
};
},
},
created() {
this.prevIcon = biArrowLeft;
this.nextIcon = biArrowRight;
},
methods: {
prev() {
const prevIndex = this.itemIndex - 1;
let itemUrl = '';
const manifest = computed(() => store.getters['contents/manifest']);
const manifests = computed(() => store.getters['contents/manifests']);
const item = computed(() => store.getters['contents/item']);
const itemUrl = computed(() => store.getters['contents/itemUrl']);
const itemIndex = computed(() => manifest.value ? manifest.value.sequence.findIndex(({ id }) => id === itemUrl.value) : -1);
const hasPrev = computed(() => {
const prevIndex = itemIndex.value - 1;
if (prevIndex < 0) {
if (manifests.value === null) return false;
const prevManifestIndex = manifests.value.findIndex(({ id }) => id === manifest.value.id) - 1;
if (prevManifestIndex < 0) return false;
}
return true;
});
const hasNext = computed(() => {
const nextIndex = itemIndex.value + 1;
if (nextIndex > manifest.value.sequence.length - 1) {
if (manifests.value === null) return false;
const nextManifestIndex = manifests.value.findIndex(({ id }) => id === manifest.value.id) + 1;
if (nextManifestIndex > manifests.value.length - 1) return false;
}
return true;
});
const nextButtonLabel = computed(() => {
return itemIndex.value === manifest.value.sequence.length - 1
? `${t('next')} ${t(labels.value.manifest)}`
: `${t('next')} ${t(labels.value.item)}`;
});
const prevButtonLabel = computed(() => {
return itemIndex.value === 0
? `${t('prev')} ${t(labels.value.manifest)}`
: `${t('prev')} ${t(labels.value.item)}`;
});
const labels = computed(() => {
return store.getters['config/config'].labels || {
manifest: 'manifest',
item: 'item',
};
});
if (prevIndex < 0) {
// If the index is lower than 0, we will load the prev manifest's last item
const prevManifestIndex = this.manifests.findIndex(({ id }) => id === this.manifest.id) - 1;
if (prevManifestIndex < 0) return;
function prev() {
const prevIndex = itemIndex.value - 1;
let itemUrl = '';
const prevManifest = this.manifests[prevManifestIndex];
this.$store.commit('contents/setManifest', prevManifest);
this.$store.dispatch('config/setDefaultActiveViews');
itemUrl = prevManifest.sequence[prevManifest.sequence.length - 1].id;
} else {
// We load the previous item
itemUrl = this.manifest.sequence[prevIndex].id;
}
this.$store.dispatch('contents/initItem', itemUrl);
},
next() {
const nextIndex = this.itemIndex + 1;
let itemUrl = '';
if (prevIndex < 0) {
// If the index is lower than 0, we will load the prev manifest's last item
const prevManifestIndex = manifests.value.findIndex(({ id }) => id === manifest.value.id) - 1;
if (prevManifestIndex < 0) return;
if (nextIndex > this.manifest.sequence.length - 1) {
const nextManifestIndex = this.manifests.findIndex(({ id }) => id === this.manifest.id) + 1;
if (nextManifestIndex > this.manifests.length - 1) return;
const prevManifest = manifests.value[prevManifestIndex];
store.commit('contents/setManifest', prevManifest);
store.dispatch('config/setDefaultActiveViews');
itemUrl = prevManifest.sequence[prevManifest.sequence.length - 1].id;
} else {
// We load the previous item
itemUrl = manifest.value.sequence[prevIndex].id;
}
store.dispatch('contents/initItem', itemUrl);
}
const nextManifest = this.manifests[nextManifestIndex];
this.$store.commit('contents/setManifest', nextManifest);
this.$store.dispatch('config/setDefaultActiveViews');
itemUrl = nextManifest.sequence[0].id;
} else {
itemUrl = this.manifest.sequence[nextIndex].id;
}
this.$store.dispatch('contents/initItem', itemUrl);
},
},
};
function next() {
const nextIndex = itemIndex.value + 1;
let itemUrl = '';
if (nextIndex > manifest.value.sequence.length - 1) {
const nextManifestIndex = manifests.value.findIndex(({ id }) => id === manifest.value.id) + 1;
if (nextManifestIndex > manifests.value.length - 1) return;
const nextManifest = manifests.value[nextManifestIndex];
store.commit('contents/setManifest', nextManifest);
store.dispatch('config/setDefaultActiveViews');
itemUrl = nextManifest.sequence[0].id;
} else {
itemUrl = manifest.value.sequence[nextIndex].id;
}
store.dispatch('contents/initItem', itemUrl);
}
</script>
<style lang="scss" scoped>
......
......@@ -74,84 +74,84 @@
</div>
</template>
<script>
<script setup>
import { computed, ref, watch } from 'vue';
import { useStore } from 'vuex';
import { useI18n } from 'vue-i18n';
import {
biCheckCircleFill, biCircle, biArrowCounterclockwise, biChevronDown,
biCheckCircleFill,
biCircle,
biArrowCounterclockwise,
biChevronDown,
} from '@quasar/extras/bootstrap-icons';
export default {
name: 'PanelsToggle',
data: () => ({
toggles: [],
showDropdown: false,
}),
computed: {
panels() {
return this.$store.getters['config/config'].panels;
},
resetColor() {
return this.toggles.filter(({ show }) => !show).length > 0 ? 'primary' : 'grey-7';
},
},
watch: {
panels: {
handler(value) {
this.toggles = value
.filter(({ toggle }) => toggle === true)
.map(({ show, label }, index) => ({ index, show, label }));
},
immediate: true,
},
showDropdown: {
handler(value) {
const tido = document.getElementById('tido');
let backdrop = tido.querySelector('#tido-backdrop');
if (value) {
if (!backdrop) {
const el = document.createElement('div');
el.id = 'tido-backdrop';
tido.appendChild(el);
backdrop = tido.querySelector('#tido-backdrop');
backdrop.clickOutsideEvent = () => {
this.showDropdown = false;
};
backdrop.addEventListener('click', backdrop.clickOutsideEvent);
}
} else if (backdrop) backdrop.remove();
},
},
},
created() {
this.checkedIcon = biCheckCircleFill;
this.uncheckedIcon = biCircle;
this.resetIcon = biArrowCounterclockwise;
this.dropdownIcon = biChevronDown;
const store = useStore();
const { t } = useI18n();
const toggles = ref([]);
const showDropdown = ref(false);
const checkedIcon = biCheckCircleFill;
const uncheckedIcon = biCircle;
const resetIcon = biArrowCounterclockwise;
const dropdownIcon = biChevronDown;
const panels = computed(() => store.getters['config/config'].panels);
const resetColor = computed(() => toggles.value.filter(({ show }) => !show).length > 0 ? 'primary' : 'grey-7');
watch(
panels,
(value) => {
toggles.value = value
.filter(({ toggle }) => toggle === true)
.map(({ show, label }, index) => ({ index, show, label }));
},
methods: {
update(index, show) {
this.toggles[index].show = show;
this.$store.dispatch('config/setShowPanel', { index, show });
},
reset() {
this.toggles.forEach((toggle, index) => {
this.toggles[index].show = true;
this.$store.dispatch('config/setShowPanel', { index, show: true });
});
},
// display toggle title when hovering
handleToggleTitle(idx) {
const titleName = this.$t(this.toggles[idx].label);
const titleUpper = `${titleName[0].toUpperCase()}${titleName.slice(1)}`;
return this.toggles[idx].show
? `${this.$t('hide')} ${titleUpper} Panel`
: `${this.$t('show')} ${titleUpper} Panel`;
},
{ immediate: true },
)
watch(
showDropdown,
(value) => {
const tido = document.getElementById('tido');
let backdrop = tido.querySelector('#tido-backdrop');
if (value) {
if (!backdrop) {
const el = document.createElement('div');
el.id = 'tido-backdrop';
tido.appendChild(el);
backdrop = tido.querySelector('#tido-backdrop');
backdrop.clickOutsideEvent = () => {
showDropdown.value = false;
};
backdrop.addEventListener('click', backdrop.clickOutsideEvent);
}
} else if (backdrop) backdrop.remove();
},
};
)
function update(index, show) {
toggles.value[index].show = show;
store.dispatch('config/setShowPanel', { index, show });
}
function reset() {
toggles.value.forEach((toggle, index) => {
toggles.value[index].show = true;
store.dispatch('config/setShowPanel', { index, show: true });
});
}
// display toggle title when hovering
function handleToggleTitle(idx) {
const titleName = t(toggles.value[idx].label);
const titleUpper = `${titleName[0].toUpperCase()}${titleName.slice(1)}`;
return toggles.value[idx].show
? `${t('hide')} ${titleUpper} Panel`
: `${t('show')} ${titleUpper} Panel`;
}
</script>
<style lang="scss">
.dropdown-list {
position: absolute;
......
......@@ -66,35 +66,27 @@
</div>
</template>
<script>
<script setup>
import { computed, ref } from 'vue';
import {
biInfoLg, biBook, biCodeSlash, biBugFill,
} from '@quasar/extras/bootstrap-icons';
import packageInfo from '../../../package.json';
export default {
name: 'SoftwareInfo',
data() {
return {
infobox: false,
tidoVersion: '',
};
},
computed: {
actualYear() {
const d = new Date();
return d.getFullYear();
},
},
created() {
this.tidoVersion = packageInfo.version;
this.infoIcon = biInfoLg;
this.docsIcon = biBook;
this.codeIcon = biCodeSlash;
this.bugIcon = biBugFill;
},
};
const infobox = ref(false);
const tidoVersion = packageInfo.version;
const infoIcon = biInfoLg;
const docsIcon = biBook;
const codeIcon = biCodeSlash;
const bugIcon = biBugFill;
const actualYear = computed(() => {
const d = new Date();
return d.getFullYear();
})
</script>
<style lang="scss">
.q-dialog {
.tido {
......
......@@ -37,27 +37,21 @@
</div>
</template>
<script>
<script setup>
import { computed } from 'vue';
import { useStore } from 'vuex';
import { biChevronRight } from '@quasar/extras/bootstrap-icons';
export default {
name: 'Titlebar',
props: {
item: {
type: Object,
default: () => {},
},
const props = defineProps({
item: {
type: Object,
default: () => {},
},
computed: {
collectionTitle() {
return this.$store.getters['contents/collectionTitle'];
},
manifestTitle() {
return this.$store.getters['contents/manifest']?.label;
},
},
created() {
this.arrowIcon = biChevronRight;
},
};
});
const store = useStore();
const arrowIcon = biChevronRight;
const collectionTitle = computed(() => store.getters['contents/collectionTitle']);
const manifestTitle = computed(() => store.getters['contents/manifest']?.label);
</script>
......@@ -12,28 +12,22 @@
</div>
</template>
<script>
import { biMoonFill, biSunFill } from '@quasar/extras/bootstrap-icons';
<script setup>
import Language from '@/components/header/Language.vue';
import SoftwareInfo from '@/components/header/SoftwareInfo.vue';
export default {
name: 'Tools',
components: {
Language,
SoftwareInfo,
},
computed: {
config() {
return this.$store.getters['config/config'];
},
showLanguageSwitch() {
return this.config.header?.languageSwitch !== undefined ? this.config.header?.languageSwitch : true;
},
},
created() {
this.darkIcon = biMoonFill;
this.lightIcon = biSunFill;
},
};
import { computed } from 'vue';
import { useStore } from 'vuex';
import { biMoonFill, biSunFill } from '@quasar/extras/bootstrap-icons';
const store = useStore();
const darkIcon = biMoonFill;
const lightIcon = biSunFill;
const config = computed(() => store.getters['config/config']);
const showLanguageSwitch = computed(() => (
config.value?.header?.languageSwitch !== undefined
? config.value.header.languageSwitch
: true
));
</script>
......@@ -7,28 +7,23 @@
</div>
</template>
<script>
<script setup>
import MetadataValue from '@/components/metadata/MetadataValue.vue';
export default {
name: 'Actor',
components: { MetadataValue },
props: {
data: {
type: Array,
default: () => [],
},
const props = defineProps({
data: {
type: Array,
default: () => [],
},
methods: {
getRole(actorItem) {
const { role } = actorItem;
if (!role) return 'undefined_role';
if (!Array.isArray(role)) return role;
if (role.length > 0) return role[0];
return 'undefined_role';
},
},
};
});
function getRole(actorItem) {
const { role } = actorItem;
if (!role) return 'undefined_role';
if (!Array.isArray(role)) return role;
if (role.length > 0) return role[0];
return 'undefined_role';
}
</script>
<style scoped>
......
......@@ -14,43 +14,38 @@
</q-list>
</template>
<script>
<script setup>
import MetadataItem from '@/components/metadata/MetadataItem.vue';
export default {
name: 'CollectionMetadata',
components: {
MetadataItem,
},
computed: {
collection() {
return this.$store.getters['contents/collection'];
},
metadata() {
if (!this.collection) return [];
const mappings = {
main: 'title',
sub: 'subtitle',
};
const collectorName = this.collection?.collector?.name;
const description = this.collection?.description;
return [
...this.collection.title
.filter((collection) => collection)
.map((collectionTitle) => ({
key: mappings[collectionTitle.type] || 'title',
value: collectionTitle.title,
})),
...(collectorName ? [{ key: 'collector', value: collectorName }] : []),
...(description ? [{ key: 'description', value: description }] : []),
...(this.collection.metadata || []),
];
},
},
};
import { computed } from 'vue';
import { useStore } from 'vuex';
const store = useStore();
const collection = computed(() => store.getters['contents/collection']);
const metadata = computed(() => {
if (!collection.value) return [];
const mappings = {
main: 'title',
sub: 'subtitle',
};
const collectorName = collection.value.collector?.name;
const description = collection.value.description;
return [
...collection.value.title
.filter((collection) => collection)
.map((collectionTitle) => ({
key: mappings[collectionTitle.type] || 'title',
value: collectionTitle.title,
})),
...(collectorName ? [{ key: 'collector', value: collectorName }] : []),
...(description ? [{ key: 'description', value: description }] : []),
...(collection.value.metadata || []),
];
});
</script>
<style scoped>
......
......@@ -14,46 +14,29 @@
</q-list>
</template>
<script>
<script setup>
import MetadataItem from '@/components/metadata/MetadataItem.vue';
export default {
name: 'ItemMetadata',
components: {
MetadataItem,
},
computed: {
item() {
return this.$store.getters['contents/item'];
},
itemUrl() {
return this.$store.getters['contents/itemUrl'];
},
manifest() {
return this.$store.getters['contents/manifest'];
},
itemsCount() {
return this.manifest?.sequence.length;
},
labels() {
return this.$store.getters['config/config'].labels;
},
number() {
return this.manifest ? this.manifest.sequence.findIndex(({ id }) => id === this.itemUrl) + 1 : 1;
},
total() {
return this.itemsCount ?? 1;
},
metadata() {
return [
{ key: 'label', value: this.item.n },
{ key: 'language', value: this.item.lang?.join(',') },
{ key: 'image_license', value: this.item.image?.license?.id },
{ key: 'image_notes', value: this.item.image?.license?.notes },
].filter((item) => item.value);
},
},
};
import { computed } from 'vue';
import { useStore } from 'vuex';
const store = useStore();
const item = computed(() => store.getters['contents/item']);
const itemUrl = computed(() => store.getters['contents/itemUrl']);
const manifest = computed(() => store.getters['contents/manifest']);
const itemsCount = computed(() => manifest.value?.sequence.length);
const labels = computed(() => store.getters['config/config'].labels);
const number = computed(() => manifest.value ? manifest.value.sequence.findIndex(({ id }) => id === itemUrl.value) + 1 : 1);
const total = computed(() => itemsCount.value ?? 1);
const metadata = computed(() => (
[
{ key: 'label', value: item.value.n },
{ key: 'language', value: item.value?.lang?.join(',') },
{ key: 'image_license', value: item.value?.image?.license?.id },
{ key: 'image_notes', value: item.value?.image?.license?.notes },
].filter((item) => item.value)
));
</script>
<style scoped>
......
......@@ -18,51 +18,33 @@
</q-list>
</template>
<script>
<script setup>
import MetadataItem from '@/components/metadata/MetadataItem.vue';
import Actor from '@/components/metadata/Actor.vue';
export default {
name: 'ManifestMetadata',
components: {
MetadataItem,
Actor,
},
computed: {
manifest() {
return this.$store.getters['contents/manifest'];
},
manifests() {
return this.$store.getters['contents/manifests'];
},
manifestHasItems() {
return this.manifest?.sequence.length > 0;
},
number() {
return this.manifests !== null ? this.manifests.findIndex(({ id }) => id === this.manifest.id) + 1 : 1;
},
total() {
return this.manifests !== null ? this.manifests.length : 1;
},
labels() {
return this.$store.getters['config/config'].labels;
},
metadata() {
if (!this.manifest) return [];
return [
{ key: 'label', value: this.manifest.label },
...(this.manifest.license || []).map((manifest) => ({
key: 'License',
value: manifest.id,
})),
...(this.manifest.metadata || []),
];
},
actor() {
return this.manifest?.actor;
},
},
};
import { computed } from 'vue';
import { useStore } from 'vuex';
const store = useStore();
const manifest = computed(() => store.getters['contents/manifest'] );
const manifests = computed(() => store.getters['contents/manifests']);
const manifestHasItems = computed(() => manifest.value?.sequence.length > 0);
const number = computed(() => manifests.value !== null ? manifests.value.findIndex(({ id }) => id === manifest.value.id) + 1 : 1);
const total = computed(() => manifests.value !== null ? manifests.value.length : 1);
const labels = computed(() => store.getters['config/config'].labels);
const metadata = computed(() => {
if (!manifest.value) return [];
return [
{ key: 'label', value: manifest.value.label },
...(manifest.value.license || []).map((manifest) => ({
key: 'License',
value: manifest.id,
})),
...(manifest.value.metadata || []),
];
});
const actor = computed(() => manifest.value?.actor );
</script>
<style scoped>
......