diff --git a/package-lock.json b/package-lock.json
index 175f9adb..b45cc0b8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,9 @@
"name": "@subugoe/tido",
"version": "3.3.0",
"license": "AGPL-3.0-or-later",
+ "dependencies": {
+ "pinia": "^2.1.7"
+ },
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.4",
"@vueuse/core": "^10.9.0",
@@ -25,7 +28,6 @@
"standard-version": "^9.5.0",
"start-server-and-test": "^2.0.3",
"tailwindcss": "^3.4.1",
- "typescript": "^5.4.5",
"vite": "^5.2.6",
"vue": "^3.4.20",
"vue-i18n": "^9.2.0-beta.35",
@@ -169,7 +171,6 @@
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz",
"integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==",
- "dev": true,
"bin": {
"parser": "bin/babel-parser.js"
},
@@ -891,8 +892,7 @@
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
- "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
- "dev": true
+ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25",
@@ -1224,7 +1224,6 @@
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.21.tgz",
"integrity": "sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==",
- "dev": true,
"dependencies": {
"@babel/parser": "^7.23.9",
"@vue/shared": "3.4.21",
@@ -1237,7 +1236,6 @@
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz",
"integrity": "sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==",
- "dev": true,
"dependencies": {
"@vue/compiler-core": "3.4.21",
"@vue/shared": "3.4.21"
@@ -1247,7 +1245,6 @@
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz",
"integrity": "sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==",
- "dev": true,
"dependencies": {
"@babel/parser": "^7.23.9",
"@vue/compiler-core": "3.4.21",
@@ -1264,7 +1261,6 @@
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.21.tgz",
"integrity": "sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==",
- "dev": true,
"dependencies": {
"@vue/compiler-dom": "3.4.21",
"@vue/shared": "3.4.21"
@@ -1273,14 +1269,12 @@
"node_modules/@vue/devtools-api": {
"version": "6.6.1",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.1.tgz",
- "integrity": "sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==",
- "dev": true
+ "integrity": "sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA=="
},
"node_modules/@vue/reactivity": {
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.21.tgz",
"integrity": "sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw==",
- "dev": true,
"dependencies": {
"@vue/shared": "3.4.21"
}
@@ -1289,7 +1283,6 @@
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.21.tgz",
"integrity": "sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA==",
- "dev": true,
"dependencies": {
"@vue/reactivity": "3.4.21",
"@vue/shared": "3.4.21"
@@ -1299,7 +1292,6 @@
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.21.tgz",
"integrity": "sha512-gvf+C9cFpevsQxbkRBS1NpU8CqxKw0ebqMvLwcGQrNpx6gqRDodqKqA+A2VZZpQ9RpK2f9yfg8VbW/EpdFUOJw==",
- "dev": true,
"dependencies": {
"@vue/runtime-core": "3.4.21",
"@vue/shared": "3.4.21",
@@ -1310,7 +1302,6 @@
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.21.tgz",
"integrity": "sha512-aV1gXyKSN6Rz+6kZ6kr5+Ll14YzmIbeuWe7ryJl5muJ4uwSwY/aStXTixx76TwkZFJLm1aAlA/HSWEJ4EyiMkg==",
- "dev": true,
"dependencies": {
"@vue/compiler-ssr": "3.4.21",
"@vue/shared": "3.4.21"
@@ -1322,8 +1313,7 @@
"node_modules/@vue/shared": {
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz",
- "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==",
- "dev": true
+ "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g=="
},
"node_modules/@vueuse/core": {
"version": "10.9.0",
@@ -2676,8 +2666,7 @@
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
- "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "dev": true
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
},
"node_modules/cypress": {
"version": "13.7.0",
@@ -3111,7 +3100,6 @@
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
- "dev": true,
"engines": {
"node": ">=0.12"
},
@@ -3612,8 +3600,7 @@
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
- "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
- "dev": true
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"node_modules/esutils": {
"version": "2.0.3",
@@ -5475,7 +5462,6 @@
"version": "0.30.8",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz",
"integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==",
- "dev": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
},
@@ -5850,7 +5836,6 @@
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -6312,8 +6297,7 @@
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
- "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
- "dev": true
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
},
"node_modules/picomatch": {
"version": "2.3.1",
@@ -6336,6 +6320,56 @@
"node": ">=0.10.0"
}
},
+ "node_modules/pinia": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.7.tgz",
+ "integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==",
+ "dependencies": {
+ "@vue/devtools-api": "^6.5.0",
+ "vue-demi": ">=0.14.5"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.4.0",
+ "typescript": ">=4.4.4",
+ "vue": "^2.6.14 || ^3.3.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/pinia/node_modules/vue-demi": {
+ "version": "0.14.7",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz",
+ "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==",
+ "hasInstallScript": true,
+ "bin": {
+ "vue-demi-fix": "bin/vue-demi-fix.js",
+ "vue-demi-switch": "bin/vue-demi-switch.js"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.0.0-rc.1",
+ "vue": "^3.0.0-0 || ^2.6.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+ },
"node_modules/pirates": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
@@ -6390,7 +6424,6 @@
"version": "8.4.38",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
- "dev": true,
"funding": [
{
"type": "opencollective",
@@ -7234,7 +7267,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -8055,7 +8087,8 @@
"version": "5.4.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
- "dev": true,
+ "optional": true,
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -8282,7 +8315,6 @@
"version": "3.4.21",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.21.tgz",
"integrity": "sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA==",
- "dev": true,
"dependencies": {
"@vue/compiler-dom": "3.4.21",
"@vue/compiler-sfc": "3.4.21",
diff --git a/package.json b/package.json
index 2a52e281..90622cfb 100644
--- a/package.json
+++ b/package.json
@@ -45,6 +45,7 @@
"http-server": "^14.1.1",
"ncp": "^2.0.0",
"openseadragon": "^3.1.0",
+ "pinia": "^2.1.7",
"primevue": "^3.49.1",
"sass": "^1.71.1",
"standard-version": "^9.5.0",
diff --git a/src/App.vue b/src/App.vue
index b88de9f0..f653905a 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -26,17 +26,19 @@
-
-
+
\ No newline at end of file
diff --git a/src/components/ContentView.vue b/src/components/ContentView.vue
index b3dde870..8beac3fb 100644
--- a/src/components/ContentView.vue
+++ b/src/components/ContentView.vue
@@ -22,6 +22,7 @@ import {
computed, readonly, ref, watch,
} from 'vue';
import { useStore } from 'vuex';
+import { useConfigStore } from '@/stores/config';
import Notification from '@/components/Notification.vue';
import { request } from '@/utils/http';
import { domParser, delay } from '@/utils';
@@ -34,12 +35,13 @@ const props = defineProps({
const emit = defineEmits(['loading']);
const store = useStore();
+const configStore = useConfigStore()
const content = ref('');
const errorTextMessage = ref(null);
const notificationMessage = readonly(errorTextMessage);
-const config = computed(() => store.getters['config/config']);
+const config = computed(() => configStore.config);
const contentStyle = computed(() => ({
fontSize: `${props.fontSize}px`,
}));
@@ -118,4 +120,4 @@ function isValidTextContent(text) {
.rtl {
direction: rtl;
}
-
+
\ No newline at end of file
diff --git a/src/components/Notification.vue b/src/components/Notification.vue
index 69cd8cd9..742cd92f 100644
--- a/src/components/Notification.vue
+++ b/src/components/Notification.vue
@@ -17,7 +17,7 @@
+
\ No newline at end of file
diff --git a/src/components/TreeView.vue b/src/components/TreeView.vue
index 88d99583..efe96551 100644
--- a/src/components/TreeView.vue
+++ b/src/components/TreeView.vue
@@ -48,6 +48,7 @@ import {
computed, nextTick, onMounted, ref, watch,
} from 'vue';
import { useStore } from 'vuex';
+import { useConfigStore } from '@/stores/config';
import { useI18n } from 'vue-i18n';
import { request } from '@/utils/http';
import { isElementVisible } from '@/utils';
@@ -55,6 +56,7 @@ import { isElementVisible } from '@/utils';
const emit = defineEmits(['loading']);
const store = useStore();
+const configStore = useConfigStore()
const { t } = useI18n();
const expanded = ref({});
@@ -62,7 +64,7 @@ const selected = ref(null);
const tree = ref([]);
const containerRef = ref(null);
-const config = computed(() => store.getters['config/config']);
+const config = computed(() => configStore.config);
const collectionTitle = computed(() => store.getters['contents/collectionTitle']);
const collection = computed(() => store.getters['contents/collection']);
const labels = computed(() => (config.value && config.value.labels) || {});
@@ -162,10 +164,11 @@ async function onNodeExpand(node) {
}
async function onNodeSelect(node) {
+ const configStore = useConfigStore()
if (currentManifest.value.id !== node.parent) {
// If we selected an item from a different manifest
await store.dispatch('contents/initManifest', node.parent);
- await store.dispatch('config/setDefaultActiveViews');
+ await configStore.setDefaultActiveViews()
}
await store.dispatch('contents/initItem', node.key);
@@ -204,4 +207,4 @@ function getNodeByKey(key, root) {
return root.children.find((child) => !!(getNodeByKey(key, child)));
}
-
+
\ No newline at end of file
diff --git a/src/components/annotations/AnnotationIcon.vue b/src/components/annotations/AnnotationIcon.vue
index 8680e892..8a7e3990 100644
--- a/src/components/annotations/AnnotationIcon.vue
+++ b/src/components/annotations/AnnotationIcon.vue
@@ -12,6 +12,7 @@ import BaseIcon from '@/components/base/BaseIcon.vue';
interface Props {
name: string
}
+
const props = defineProps()
diff --git a/src/components/annotations/AnnotationsView.vue b/src/components/annotations/AnnotationsView.vue
index 54c11513..6aae6de8 100644
--- a/src/components/annotations/AnnotationsView.vue
+++ b/src/components/annotations/AnnotationsView.vue
@@ -28,6 +28,10 @@ import AnnotationsList from '@/components/annotations/AnnotationsList.vue';
import Notification from '@/components/Notification.vue';
import * as AnnotationUtils from '@/utils/annotations';
+import { useConfigStore } from '@/stores/config';
+
+const configStore = useConfigStore()
+
const props = defineProps({
url: String,
types: Array,
@@ -36,7 +40,7 @@ const props = defineProps({
const store = useStore();
const message = ref('no_annotations_in_view');
-const config = computed(() => store.getters['config/config']);
+const config = computed(() => configStore.config);
const annotations = computed(() => store.getters['annotations/annotations']);
const activeAnnotations = computed(() => store.getters['annotations/activeAnnotations']);
const filteredAnnotations = computed(() => store.getters['annotations/filteredAnnotations']);
@@ -95,4 +99,4 @@ function highlightTargetsLevel0() {
+
\ No newline at end of file
diff --git a/src/components/base/BaseCheckbox.vue b/src/components/base/BaseCheckbox.vue
index 59116499..654dbff3 100644
--- a/src/components/base/BaseCheckbox.vue
+++ b/src/components/base/BaseCheckbox.vue
@@ -1,6 +1,5 @@
+
\ No newline at end of file
diff --git a/src/components/header/GlobalHeader.vue b/src/components/header/GlobalHeader.vue
index dcd98324..fa410852 100644
--- a/src/components/header/GlobalHeader.vue
+++ b/src/components/header/GlobalHeader.vue
@@ -19,6 +19,7 @@
+
\ No newline at end of file
diff --git a/src/components/header/Language.vue b/src/components/header/Language.vue
index 99a65423..57caa7fc 100644
--- a/src/components/header/Language.vue
+++ b/src/components/header/Language.vue
@@ -21,7 +21,7 @@
import {
computed, onMounted, ref, watch,
} from 'vue';
-import { useStore } from 'vuex';
+import { useConfigStore } from '@/stores/config';
import { useI18n } from 'vue-i18n';
import BaseDropdown from '@/components/base/BaseDropdown.vue';
@@ -30,7 +30,7 @@ interface Language {
value: string
}
-const store = useStore();
+const configStore = useConfigStore()
const { locale: i18nLocale } = useI18n();
const langs = ref([
@@ -39,7 +39,7 @@ const langs = ref([
]);
const selectedLang = ref(langs.value[0]);
const showDropdown = ref(false);
-const config = computed(() => store.getters['config/config']);
+const config = computed(() => configStore.config);
watch(
selectedLang,
diff --git a/src/components/header/Navbar.vue b/src/components/header/Navbar.vue
index 6846a2ca..c294288b 100644
--- a/src/components/header/Navbar.vue
+++ b/src/components/header/Navbar.vue
@@ -26,10 +26,12 @@
+
\ No newline at end of file
diff --git a/src/components/header/PanelsToggle.vue b/src/components/header/PanelsToggle.vue
index 425aefe1..5acf0041 100644
--- a/src/components/header/PanelsToggle.vue
+++ b/src/components/header/PanelsToggle.vue
@@ -57,20 +57,19 @@
+
\ No newline at end of file
diff --git a/src/components/header/TitleBar.vue b/src/components/header/TitleBar.vue
index 53204dfe..70e8d972 100644
--- a/src/components/header/TitleBar.vue
+++ b/src/components/header/TitleBar.vue
@@ -36,6 +36,7 @@
diff --git a/src/components/panels/Panel.vue b/src/components/panels/Panel.vue
index 104bffc0..bed3c1eb 100644
--- a/src/components/panels/Panel.vue
+++ b/src/components/panels/Panel.vue
@@ -95,6 +95,7 @@ import {
computed, nextTick, ref, watch,
} from 'vue';
import { useStore } from 'vuex';
+import { useConfigStore } from '@/stores/config';
import { useI18n } from 'vue-i18n';
import TabView from 'primevue/tabview';
import TabPanel from 'primevue/tabpanel';
@@ -136,6 +137,7 @@ export default {
},
setup(props, { emit }) {
const store = useStore();
+ const configStore = useConfigStore()
const { t } = useI18n();
const tabs = ref([]);
@@ -317,7 +319,7 @@ export default {
[contentItem] = item.value.content;
// TODO: this should be moved to loading time in order dynamically recognize all content types
// instead of only the first one
- store.dispatch('config/setContentType', contentItem.type.split('type=')[1]);
+ configStore.setContentType(contentItem.type.split('type=')[1]);
}
contentItem = item.value.content.find((c) => c.type.split('type=')[1] === type);
diff --git a/src/components/panels/PanelsWrapper.vue b/src/components/panels/PanelsWrapper.vue
index 3c164a4d..49645dd5 100644
--- a/src/components/panels/PanelsWrapper.vue
+++ b/src/components/panels/PanelsWrapper.vue
@@ -15,23 +15,25 @@
+
\ No newline at end of file
diff --git a/src/main.js b/src/main.js
index 1ae38cf1..0914c20d 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,5 +1,6 @@
import { createApp, h } from 'vue';
import PrimeVue from 'primevue/config';
+import { createPinia } from 'pinia'
import store from './store';
import { i18n } from './i18n';
import App from './App.vue';
@@ -8,6 +9,8 @@ import './css/style.css';
import './css/style.scss';
import { getRGBColor } from '@/utils/color';
+const pinia = createPinia()
+
function generateId() {
return Math.random().toString(36).slice(2, 16);
}
@@ -23,6 +26,7 @@ window.Tido = function Tido(config = {}) {
this.app.provide('config', this.config);
this.app.use(PrimeVue);
+ this.app.use(pinia);
this.app.use(store);
this.app.use(i18n);
@@ -63,4 +67,4 @@ window.Tido = function Tido(config = {}) {
this.mount(container);
};
-export default window.Tido;
+export default window.Tido;
\ No newline at end of file
diff --git a/src/store/annotations/actions.js b/src/store/annotations/actions.js
index c73cf3ea..66e4915a 100644
--- a/src/store/annotations/actions.js
+++ b/src/store/annotations/actions.js
@@ -4,7 +4,10 @@ import * as Utils from '@/utils';
import { scrollIntoViewIfNeeded } from '@/utils';
import { getAnnotationListElement } from '@/utils/annotations';
+import { useConfigStore } from '@/stores/config';
+
export const addActiveAnnotation = ({ getters, rootGetters, dispatch }, id) => {
+ const configStore = useConfigStore()
const { activeAnnotations, annotations } = getters;
const newActiveAnnotation = annotations.find((annotation) => annotation.id === id);
@@ -12,7 +15,7 @@ export const addActiveAnnotation = ({ getters, rootGetters, dispatch }, id) => {
return;
}
- const iconName = rootGetters['config/getIconByType'](newActiveAnnotation.body['x-content-type']);
+ const iconName = configStore.getIconByType(newActiveAnnotation.body['x-content-type']);
const activeAnnotationsList = { ...activeAnnotations };
@@ -37,7 +40,8 @@ export const setActiveAnnotations = ({ commit }, activeAnnotations) => {
export const setFilteredAnnotations = ({ commit, getters, rootGetters }, types) => {
const { annotations } = getters;
- const activeContentType = rootGetters['config/activeContentType'];
+ const configStore = useConfigStore()
+ const activeContentType = configStore.activeContentType
let filteredAnnotations = [];
if (annotations !== null) {
@@ -143,6 +147,7 @@ export const addHighlightHoverListeners = ({ getters, rootGetters }) => {
const annotationElements = Array.from(document.querySelectorAll('[data-annotation]'));
const tooltipEl = null;
+ const configStore = useConfigStore()
// Annotations can be nested, so we filter out all outer elements from this selection and
// iterate over the deepest elements
@@ -161,7 +166,7 @@ export const addHighlightHoverListeners = ({ getters, rootGetters }) => {
const { filteredAnnotations } = getters;
const annotationTooltipModels = filteredAnnotations.reduce((acc, curr) => {
const { id } = curr;
- const name = rootGetters['config/getIconByType'](curr.body['x-content-type']);
+ const name = configStore.getIconByType(curr.body['x-content-type'])
acc[id] = {
value: curr.body.value,
name,
@@ -283,4 +288,4 @@ function discoverChildAnnotationIds(el, annotationIds = {}) {
}
});
return annotationIds;
-}
+}
\ No newline at end of file
diff --git a/src/store/config/actions.js b/src/store/config/actions.js
deleted file mode 100644
index a5771986..00000000
--- a/src/store/config/actions.js
+++ /dev/null
@@ -1,376 +0,0 @@
-import messages from 'src/i18n';
-import { isUrl } from '@/utils';
-import BookmarkService from '@/services/bookmark';
-import { i18n } from '@/i18n';
-
-const defaultPanel = {
- label: 'Panel',
- show: true,
- toggle: true,
- views: [],
-};
-
-const defaultView = {
- id: 'view',
- name: 'View',
- default: false,
- connector: {
- id: 1,
- options: {},
- },
-};
-
-function validateCollection(value) {
- return !!(value);
-}
-
-function validateManifest(value) {
- return !!(value);
-}
-
-function validateItem(value) {
- return !!(value);
-}
-
-function validateTranslations(value) {
- return !!(value) && Object.keys(value).every((key) => key === 'en' || key === 'de');
-}
-
-function validatePanels(value) {
- return !!(value) && Array.isArray(value);
-}
-
-function validateLang(value) {
- return !!(value);
-}
-
-function validateColors(value) {
- return !!(value);
-}
-
-function validateContainer(value) {
- return !!(value);
-}
-
-function validateHeader(value, defaultValue) {
- if (!value) return false;
-
- const defaultKeys = Object.keys(defaultValue);
- const invalidKeys = Object.keys(value)
- .filter((key) => defaultKeys.findIndex((defaultKey) => defaultKey === key) === -1);
- return invalidKeys.length === 0;
-}
-
-function validateLabels(labels, validLabels) {
- // valid labels are the labels from the default config
- // we consider the custom labels, in the case when all the keys have a value, otherwise we would have the button with empty text i.e for the following scenario
- // when the item is ''
- if (!labels || !validLabels) return false;
-
- let isValid = true;
- Object.keys(labels).forEach((key) => {
- if (!(key in validLabels) || labels[key] === '') {
- isValid = false;
- }
- });
-
- return isValid;
-}
-
-function createDefaultActiveViews(panelsConfig) {
- return panelsConfig
- .filter((p) => p.views && p.views.length > 0)
- .map((panel) => {
- const defaultIndex = panel.views.findIndex((view) => view.default === true);
- return defaultIndex > -1 ? defaultIndex : 0;
- })
- .reduce((acc, cur, i) => {
- acc[i] = cur;
- return acc;
- }, {});
-}
-
-// URL Config
-function splitUrlParts(urlQuery, attributes) {
- if (urlQuery === '') {
- return [undefined, undefined, undefined, undefined];
- }
- const arrayAttributes = urlQuery.split('_');
- const manifestPart = arrayAttributes.find((element) => element[0].includes(attributes[0])); // index of manifest part in the splitted array: element[0] is 'm' the first letter of the part ?
- const itemPart = arrayAttributes.find((element) => element[0].includes(attributes[1]));
- const panelsPart = arrayAttributes.find((element) => element[0].includes(attributes[2]));
- const showPart = arrayAttributes.find((element) => element[0].includes(attributes[3]));
- return [manifestPart, itemPart, panelsPart, showPart];
-}
-
-function isManifestPartValid(manifestPart) {
- const regexManifest = /m\d+$/;
- return regexManifest.exec(manifestPart) !== null;
-}
-
-function isItemPartValid(itemPart) {
- const regexItem = /i\d+$/;
- return regexItem.exec(itemPart) !== null;
-}
-
-function isPanelsPartValid(panelsPart, panelsValue, numberPanels) {
- const numbersPartArray = panelsValue.split('-');
- const regexNumber = /^\d+$/;
- if (panelsPart[0] !== 'p' || numbersPartArray.length !== numberPanels) {
- return false;
- }
-
- for (let i = 0; i < numbersPartArray.length; i++) {
- const panelTabPair = numbersPartArray[i];
- if (panelTabPair.length !== 3
- || regexNumber.test(panelTabPair[0]) === false
- || regexNumber.test(panelTabPair[2]) === false
- || panelTabPair[1] !== '.') {
- return false;
- }
- }
- return true;
-}
-
-function isShowPartValid(showValue, numberPanels) {
- const showValueAsArray = showValue.split('-');
- const regexNumbersPart = /\d\-/;
- if (showValueAsArray.length > numberPanels) {
- return false;
- }
- for (let i = 0; i < showValueAsArray.length - 1; i++) {
- // if s0-2 is given and there are in total 4 panels, then it is still fine, since we can show less number of panels than the total one
- // match the couples of (d-) -> a digit followed by a "-" character. In total there are (s.length - 1) - so number of panels we want to open - 1
- const groupMatch = showValue.slice(i * 2, i * 2 + 2).match(regexNumbersPart);
- if (groupMatch === null) {
- return false;
- }
- }
- const lastNumberString = showValue.slice(-1)[0];
- const lastNumberInt = parseInt(lastNumberString, 10);
- // last character must have only digits and not be greater than number of max panels
- if (/^\d+$/.test(lastNumberString) === false || (lastNumberInt >= numberPanels || lastNumberInt < 0)) {
- return false;
- }
- return true;
-}
-
-function createDefaultPanelValue(numberPanels) {
- // get the number of panels and then create as many couples of (panel_index.0) until n_panels-1, the last couple need not have the '-' symbol
- let p = '';
- for (let j = 0; j < numberPanels; j++) {
- if (j !== numberPanels - 1) {
- p += `${j}.0-`;
- } else {
- p += `${j}.0`;
- }
- }
- return p;
-}
-
-function createActiveViewsFromPanelsArray(panelsArray) {
- // converts 'panelsArray' to an object with key, value: 'panel index: visible tab index'
- return panelsArray.reduce((acc, cur) => {
- // eslint-disable-next-line no-shadow
- const [panelIndex, viewIndex] = cur.split('.').map((i) => parseInt(i, 10));
- acc[panelIndex] = viewIndex;
- return acc;
- }, {});
-}
-
-function discoverCustomConfig(customConfig, defaultConfig) {
- const {
- container, translations, collection, manifest, item, panels, lang, colors, header, labels
- } = customConfig;
-
- return {
- ...(validateContainer(container) && { container }),
- ...(validateCollection(collection) && { collection }),
- ...(validateManifest(manifest) && { manifest }),
- ...(validateItem(item) && { item }),
- ...(validateTranslations(translations) && { translations }),
- ...(validatePanels(panels) && { panels }),
- ...(validateLang(lang) && { lang }),
- ...(validateColors(colors) && { colors }),
- ...(validateHeader(header, defaultConfig.header) && { header }),
- ...(validateLabels(labels, defaultConfig.labels) && { labels }),
- };
-}
-
-function discoverUrlConfig(config) {
- // split the url based on '_'
- // get the part of attribute: get the attribute name and the value based on the type of attribute
- // add each attribute to UrlConfig as key value
- let urlConfig = {};
- const urlQuery = BookmarkService.getQuery();
- const attributes = ['m', 'i', 'p', 's'];
- // values of manifest, item Indices ...
- let [m, i, p, s] = [undefined, undefined, undefined, undefined];
- const numberPanels = config.panels ? config.panels.length : 0;
- const [manifestPart, itemPart, panelsPart, showPart] = splitUrlParts(urlQuery, attributes);
- /*
- if (isUrl(item)) urlConfig.item = item;
- if (isUrl(manifest)) urlConfig.manifest = manifest;
- if (isUrl(collection)) urlConfig.collection = collection;
- */
- // here we will validate for the structure of each component:, not their value range
- if (manifestPart !== undefined) {
- if (!isManifestPartValid(manifestPart)) {
- throw new Error(i18n.global.t('error_manifestpart_tido_url'));
- } else {
- urlConfig.m = parseInt(manifestPart.slice(1), 10);
- }
- }
- if (itemPart !== undefined) {
- if (!isItemPartValid(itemPart)) {
- throw new Error(i18n.global.t('error_itempart_tido_url'));
- } else {
- urlConfig.i = parseInt(itemPart.slice(1), 10);
- }
- }
- if (panelsPart !== undefined) {
- const panelsValue = panelsPart.slice(1);
- if (!isPanelsPartValid(panelsPart, panelsValue, numberPanels)) {
- throw new Error(i18n.global.t('error_panelspart_tido_url'));
- } else {
- p = panelsValue;
- }
- } else {
- p = createDefaultPanelValue(numberPanels);
- }
- const panelsArray = p !== '' ? p.split('-') : [];
- urlConfig.activeViews = createActiveViewsFromPanelsArray(panelsArray);
-
- if (showPart !== undefined) {
- const showValue = showPart.slice(1);
- if (!isShowPartValid(showValue, numberPanels)) {
- throw new Error(i18n.global.t('error_showpart_tido_url'));
- } else {
- // showValue needs to be an array of opened panel indices (Integers)
- s = showValue.split('-').map(Number);
- }
- }
- if (s === undefined) {
- // If 's' is not given in URL, then we open all the panels which are given in config
- urlConfig.show = Array.from({ length: numberPanels }, (value, index) => index);
- } else {
- urlConfig.show = s;
- }
- return urlConfig;
-}
-
-function discoverDefaultConfig(config) {
- return {
- ...JSON.parse(JSON.stringify(config)),
- activeViews: createDefaultActiveViews(config.panels),
- };
-}
-
-export const load = ({ commit, getters, dispatch }, config) => {
- const customConfig = discoverCustomConfig(config, getters.config);
- const urlConfig = discoverUrlConfig(config);
- const defaultConfig = discoverDefaultConfig(getters.config);
-
- const header = {
- ...defaultConfig.header,
- ...customConfig.header,
- };
-
- if (customConfig.panels) {
- // If the custom config provide panels config, we still need to check if it's valid.
- // Here we enhance the potentially missing parts with default panel/view config.
- // Hint: Not to confuse with the "defaultConfig" which provides an out-of-the-box panels setup
-
- customConfig.panels = customConfig.panels.map((panel) => {
- if (panel.views) {
- panel.views = panel.views.map((view) => ({
- ...defaultView,
- ...view,
- }));
- }
-
- return {
- ...defaultPanel,
- ...panel,
- };
- });
- }
-
- const resultConfig = {
- ...defaultConfig,
- ...customConfig,
- ...urlConfig,
- header,
- };
-
- const activeViews = urlConfig.activeViews || defaultConfig.activeViews;
- commit('setActiveViews', activeViews);
-
- if (resultConfig.show && resultConfig.show.length > 0) {
- // Set visible panels
- // First hide all
- resultConfig.panels.map((panel, i) => resultConfig.panels[i].show = false);
-
- // Next show configured
- resultConfig.show.forEach((panelIndex) => {
- if (!Number.isInteger(panelIndex)) return;
- const panel = resultConfig.panels[panelIndex];
- if (!panel) return;
-
- resultConfig.panels[panelIndex].show = true;
- });
- }
-
- if (resultConfig.translations) {
- const locales = Object.keys(resultConfig.translations);
-
- locales.forEach((locale) => {
- i18n.global.setLocaleMessage(locale, { ...(messages[locale] ? messages[locale] : {}), ...resultConfig.translations[locale] });
- });
- }
- commit('setConfig', resultConfig);
-
- if (urlConfig.activeViews) commit('setActiveViews', activeViews);
- else dispatch('setDefaultActiveViews', false);
-};
-
-export const setActivePanelView = async ({ commit, getters }, { panelIndex, viewIndex }) => {
- commit('setActivePanelView', { panelIndex, viewIndex });
- await BookmarkService.updatePanels(getters.activeViews);
-};
-
-export const setShowPanel = ({ commit, getters }, { index, show }) => {
- commit('setShowPanel', { index, show });
-
- let panelIndexes = getters.config.panels.reduce((acc, cur, i) => (cur.show ? [...acc, i] : acc), []);
- if (panelIndexes.length === getters.config.panels.length) panelIndexes = [];
-
- BookmarkService.updateShow(panelIndexes);
-};
-
-export const setContentType = ({ commit, getters }, type) => {
- const { config } = getters;
- const newConfig = { ...config };
-
- newConfig.panels[3].views[0].connector.options = { type };
- commit('setConfig', newConfig);
-};
-
-export const setDefaultActiveViews = async ({ commit, getters }, bookmark = true) => {
- const { config } = getters;
- const activeViews = [];
-
- config.panels.forEach(({ views }, panelIndex) => {
- let defaultViewIndex = views.findIndex((view) => !!(view.default));
- if (defaultViewIndex === -1) defaultViewIndex = 0;
- activeViews[panelIndex] = defaultViewIndex;
- });
-
- if (bookmark) await BookmarkService.updatePanels(activeViews);
-
- commit('config/setActiveViews', activeViews, { root: true });
-};
-
-export const setInstanceId = ({ commit }, id) => {
- commit('config/setInstanceId', id);
-};
diff --git a/src/store/config/getters.js b/src/store/config/getters.js
deleted file mode 100644
index 229ad806..00000000
--- a/src/store/config/getters.js
+++ /dev/null
@@ -1,26 +0,0 @@
-export const config = (state) => state.config;
-
-export const activeViews = (state) => state.activeViews;
-
-// eslint-disable-next-line no-shadow
-export const activeContentType = ({ config, activeViews }) => {
- const contentConnectorId = 4;
- const panelIndex = config.panels.findIndex(({ views }) => views.find(({ connector }) => contentConnectorId === connector.id));
-
- if (panelIndex === -1) return -1;
-
- const viewIndex = activeViews[panelIndex];
- return config.panels[panelIndex].views[viewIndex].connector.options.type;
-};
-
-// eslint-disable-next-line no-shadow
-export const getIconByType = ({ config, activeViews }) => (type) => {
- const annotationsConnectorId = 5;
- const panelIndex = config.panels.findIndex(({ views }) => views.find(({ connector }) => annotationsConnectorId === connector.id));
-
- if (panelIndex === -1) return -1;
-
- const viewIndex = activeViews[panelIndex];
- const types = config.panels[panelIndex].views[viewIndex].connector.options?.types;
- return types.find(({ name }) => name === type)?.icon || 'biPencilSquare';
-};
diff --git a/src/store/config/index.js b/src/store/config/index.js
deleted file mode 100644
index b0beb1b9..00000000
--- a/src/store/config/index.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import state from './state';
-import * as getters from './getters';
-import * as mutations from './mutations';
-import * as actions from './actions';
-
-export default {
- namespaced: true,
- state,
- getters,
- mutations,
- actions,
-};
diff --git a/src/store/config/mutations.js b/src/store/config/mutations.js
deleted file mode 100644
index 3119ddc3..00000000
--- a/src/store/config/mutations.js
+++ /dev/null
@@ -1,31 +0,0 @@
-export const setConfig = (state, payload) => {
- state.config = payload;
-};
-
-export const setActivePanelView = (state, { panelIndex, viewIndex }) => {
- const { activeViews } = state;
- if (activeViews[panelIndex] !== undefined) {
- activeViews[panelIndex] = viewIndex;
- }
-};
-
-export const setPanels = (state, panels) => {
- state.config.panels = panels;
-};
-
-export const setShowPanel = (state, { index, show }) => {
- state.config.panels[index].show = show;
-};
-
-export const loadConfig = (state, { config, isValid }) => {
- state.config = config;
- state.isValid = isValid;
-};
-
-export const setActiveViews = (state, payload) => {
- state.activeViews = payload;
-};
-
-export const setInstanceId = (state, payload) => {
- state.instanceId = payload;
-};
diff --git a/src/store/config/state.js b/src/store/config/state.js
deleted file mode 100644
index 96900b5b..00000000
--- a/src/store/config/state.js
+++ /dev/null
@@ -1,137 +0,0 @@
-export default function ConfigState() {
- return {
- instanceId: null,
- config: {
- container: '#app',
- collection: '',
- manifest: '',
- item: '',
- panels: [
- {
- label: 'contents',
- toggle: true,
- show: true,
- views: [
- {
- id: 'tree',
- label: 'contents',
- connector: {
- id: 1,
- options: {
- labels: {
- item: 'Sheet',
- manifest: 'Manuscript',
- },
- },
- },
- },
- ],
- },
- {
- label: 'metadata',
- show: true,
- toggle: true,
- views: [
- {
- id: 'metadata',
- label: 'metadata',
- connector: {
- id: 2,
- options: {
- collection: {
- all: true,
- },
- manifest: {
- all: true,
- },
- item: {
- all: true,
- },
- },
- },
- },
- ],
- },
- {
- label: 'image',
- show: true,
- toggle: true,
- views: [
- {
- id: 'image',
- label: 'Image',
- connector: {
- id: 3,
- },
- }],
- },
- {
- label: 'text',
- show: true,
- toggle: true,
- views: [
- {
- id: 'text1',
- label: 'Transcription',
- default: true,
- connector: {
- id: 4,
- },
- },
- ],
- },
- {
- label: 'annotations',
- show: true,
- toggle: true,
- views: [
- {
- id: 'annotations1',
- label: 'annotations',
- connector: {
- id: 5,
- options: {
- types: [],
- },
- },
- },
- ],
- },
- ],
- colors: {
- forceMode: 'none',
- primary: '',
- secondary: '',
- accent: '',
- },
- header: {
- show: true,
- navigation: true,
- panelsToggle: true,
- languageSwitch: false,
- },
- labels: {
- item: 'Sheet',
- manifest: 'Manuscript',
- },
- lang: 'en',
- meta: {
- collection: {
- all: true,
- },
- manifest: {
- all: true,
- },
- item: {
- all: true,
- },
- },
- notificationColors: {
- info: 'blue-400',
- warning: 'red-400',
- },
- },
- activeViews: [],
- isValid: false,
- };
-}
diff --git a/src/store/contents/actions.js b/src/store/contents/actions.js
index 59136ef9..a3497882 100644
--- a/src/store/contents/actions.js
+++ b/src/store/contents/actions.js
@@ -3,6 +3,9 @@ import { i18n } from '@/i18n';
import BookmarkService from '@/services/bookmark';
import { loadCss, loadFont } from '../../utils';
+import { useConfigStore } from '@/stores/config';
+
+
export const getItemIndex = async ({ getters }, itemUrl) => {
const { manifest } = getters;
if (!manifest) return -1;
@@ -44,9 +47,10 @@ function isItemPartInsideRangeValue(i, numberItems) {
export const initCollection = async ({
commit, dispatch, getters, rootGetters,
}, url) => {
- const { item } = getters;
- const resultConfig = rootGetters['config/config'];
- let { item: itemUrl } = rootGetters['config/config'];
+ const configStore = useConfigStore()
+ const resultConfig = configStore.config;
+ let item = resultConfig.item;
+ let itemUrl;
let collection = '';
let activeManifest = '';
let manifestIndex;
@@ -105,22 +109,20 @@ export const initCollection = async ({
activeManifest = manifests[manifestIndex];
itemUrl = activeManifest.sequence[itemIndex].id;
- if ('p' in resultConfig) commit('setPanels', resultConfig.p);
- if ('s' in resultConfig) commit('setShow', resultConfig.s);
const { support } = activeManifest;
if (support && support.length > 0) {
await dispatch('getSupport', support);
}
commit('setManifest', activeManifest);
-
- if (!item) dispatch('initItem', itemUrl);
+ dispatch('initItem', itemUrl);
}
};
export const initManifest = async ({
commit, dispatch, rootGetters,
}, url) => {
+ const configStore = useConfigStore()
let manifest = '';
try {
manifest = await request(url);
@@ -133,7 +135,7 @@ export const initManifest = async ({
const numberItems = manifest.sequence.length;
commit('setManifest', manifest);
- const resultConfig = rootGetters['config/config'];
+ const resultConfig = configStore.config;
const { item } = resultConfig;
let itemIndex;
@@ -195,6 +197,7 @@ export const initItem = async ({ commit, dispatch, getters }, url) => {
m,
i,
} : { i };
+
await BookmarkService.updateQuery(query);
};
@@ -208,11 +211,12 @@ export const initAnnotations = async ({ commit }, url) => {
};
export const getSupport = ({ rootGetters }, support) => {
- const { container } = rootGetters['config/config'];
+ const configStore = useConfigStore()
+ const { container } = configStore.config;
support.forEach((s) => {
const hasElement = document.getElementById(s.url);
if (s.type === 'font' && !hasElement) loadFont(s.url, container);
if (s.type !== 'font' && !hasElement) loadCss(s.url);
});
-};
+};
\ No newline at end of file
diff --git a/src/store/contents/getters.js b/src/store/contents/getters.js
index a8120db0..69489916 100644
--- a/src/store/contents/getters.js
+++ b/src/store/contents/getters.js
@@ -21,37 +21,6 @@ export const itemUrl = (state) => state.itemUrl;
export const manifests = (state) => state.manifests;
-export const selectedItemIndex = (state) => {
- const selectedItemUrl = encodeURI(decodeURI(state.itemUrl));
- const index = state.itemUrls.findIndex((item) => encodeURI(decodeURI(item)) === selectedItemUrl);
- return index > -1 ? index : 0;
-};
-
-export const selectedManifest = (state) => state.manifests.find((manifest) => {
- manifest = { ...manifest };
- const selectedItemUrl = encodeURI(decodeURI(state.itemUrl));
- if (!Array.isArray(manifest.sequence)) {
- manifest.sequence = [manifest.sequence];
- }
- return manifest.sequence.find((manifestItem) => encodeURI(decodeURI(manifestItem.id)) === selectedItemUrl);
-});
-
-export const selectedSequenceIndex = (state, getters) => {
- const item = getters.selectedManifest;
- if (!item) {
- return null;
- }
-
- const { label } = item;
- let index = null;
- state.manifests.forEach((manifest, idx) => {
- if (manifest.label === label) {
- index = idx;
- }
- });
- return index;
-};
-
export const item = (state) => state.item;
export const manifest = (state) => state.manifest;
diff --git a/src/store/contents/mutations.js b/src/store/contents/mutations.js
index 692eeb7f..02ec2133 100644
--- a/src/store/contents/mutations.js
+++ b/src/store/contents/mutations.js
@@ -2,14 +2,6 @@ export const setCollection = (state, payload) => {
state.collection = { ...payload };
};
-export const setCollectionTitle = (state, title) => {
- state.collectionTitle = title;
-};
-
-export const setConnectorValues = (state, payload) => {
- state.connectorValues = payload;
-};
-
export const setItem = (state, payload) => {
state.item = payload;
};
@@ -22,10 +14,6 @@ export const setManifests = (state, payload) => {
state.manifests = payload;
};
-export const setPanels = (state, payload) => {
- state.panels = payload;
-};
-
export const setManifest = (state, payload) => {
if (!Array.isArray(payload.sequence)) payload.sequence = [payload.sequence];
state.manifest = { ...payload };
diff --git a/src/store/index.js b/src/store/index.js
index ba610719..eb35387f 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -1,13 +1,11 @@
import { createStore } from 'vuex';
import annotations from './annotations';
-import config from './config';
import contents from './contents';
export default createStore({
modules: {
annotations,
- config,
contents,
},
});
diff --git a/src/stores/config.ts b/src/stores/config.ts
new file mode 100644
index 00000000..8f01433e
--- /dev/null
+++ b/src/stores/config.ts
@@ -0,0 +1,595 @@
+import { defineStore } from 'pinia'
+import {
+ computed, ref,
+ } from 'vue';
+
+import messages from 'src/i18n';
+import BookmarkService from '@/services/bookmark';
+import { i18n } from '@/i18n';
+
+ export const useConfigStore = defineStore('config', () => {
+ // States ('Setup Pinia': refs)
+ const instanceId = ref(null)
+ const config = ref({
+ container: '#app',
+ collection: '',
+ manifest: '',
+ item: '',
+ panels: [
+ {
+ label: 'contents',
+ toggle: true,
+ show: true,
+ views: [
+ {
+ id: 'tree',
+ label: 'contents',
+ connector: {
+ id: 1,
+ options: {
+ labels: {
+ item: 'Sheet',
+ manifest: 'Manuscript',
+ },
+ },
+ },
+ },
+ ],
+ },
+ {
+ label: 'metadata',
+ show: true,
+ toggle: true,
+ views: [
+ {
+ id: 'metadata',
+ label: 'metadata',
+ connector: {
+ id: 2,
+ options: {
+ collection: {
+ all: true,
+ },
+ manifest: {
+ all: true,
+ },
+ item: {
+ all: true,
+ },
+ },
+ },
+ },
+ ],
+ },
+ {
+ label: 'image',
+ show: true,
+ toggle: true,
+ views: [
+ {
+ id: 'image',
+ label: 'Image',
+ connector: {
+ id: 3,
+ },
+ }],
+ },
+ {
+ label: 'text',
+ show: true,
+ toggle: true,
+ views: [
+ {
+ id: 'text1',
+ label: 'Transcription',
+ default: true,
+ connector: {
+ id: 4,
+ },
+ },
+ ],
+ },
+ {
+ label: 'annotations',
+ show: true,
+ toggle: true,
+ views: [
+ {
+ id: 'annotations1',
+ label: 'annotations',
+ connector: {
+ id: 5,
+ options: {
+ types: [],
+ },
+ },
+ },
+ ],
+ },
+ ],
+ colors: {
+ forceMode: 'none',
+ primary: '',
+ secondary: '',
+ accent: '',
+ },
+ header: {
+ show: true,
+ navigation: true,
+ panelsToggle: true,
+ languageSwitch: false,
+ },
+ labels: {
+ item: 'Sheet',
+ manifest: 'Manuscript',
+ },
+ lang: 'en',
+ meta: {
+ collection: {
+ all: true,
+ },
+ manifest: {
+ all: true,
+ },
+ item: {
+ all: true,
+ },
+ },
+ notificationColors: {
+ info: 'blue-400',
+ warning: 'red-400',
+ },
+ })
+ const activeViews = ref([])
+ const isValid = ref(false)
+
+
+ const defaultPanel = {
+ label: 'Panel',
+ show: true,
+ toggle: true,
+ views: [],
+ };
+
+ const defaultView = {
+ id: 'view',
+ name: 'View',
+ default: false,
+ connector: {
+ id: 1,
+ options: {},
+ },
+ };
+
+ // Getters ('Setup Pinia' computed())
+ const activeContentType = computed(() => {
+ const contentConnectorId = 4;
+ const panelIndex = config.value.panels.findIndex(({ views }) => views.find(({ connector }) => contentConnectorId === connector.id));
+
+ if (panelIndex === -1) return -1;
+
+ const viewIndex = activeViews.value[panelIndex];
+ return config.value.panels[panelIndex].views[viewIndex].connector.options.type;
+ })
+
+ // I think it doesn't matter whether getIconByType is a function or a computed property, since its only being called by actions in config
+ function getIconByType(type) {
+ const annotationsConnectorId = 5;
+ const panelIndex = config.value.panels.findIndex(({ views }) => views.find(({ connector }) => annotationsConnectorId === connector.id));
+
+ if (panelIndex === -1) return -1;
+
+ const viewIndex = activeViews.value[panelIndex];
+ const types = config.value.panels[panelIndex].views[viewIndex].connector.options?.types;
+ return types.find(({ name }) => name === type)?.icon || 'biPencilSquare';
+ }
+
+
+
+ // Functions (mutators and actions in Vuex are now converted to functions in Pinia)
+
+ function setConfig(payload) {
+ config.value = payload;
+ }
+
+ async function setActivePanelView(viewIndex, panelIndex) {
+ if(activeViews.value[panelIndex] !== undefined) {
+ activeViews.value[panelIndex] = viewIndex;
+ }
+ await BookmarkService.updatePanels(activeViews.value);
+ }
+
+ function setPanels(panels) {
+ config.value.panels = panels;
+ }
+
+ function setShowPanelSetter(index, show) {
+ config.value.panels[index].show = show;
+ }
+
+ function loadConfig(config, isValid) {
+ config.value = config;
+ isValid.value = isValid;
+ }
+
+ function setActiveViews(payload) {
+ activeViews.value = payload;
+ }
+
+ function setInstanceId(payload) {
+ instanceId.value = payload;
+ }
+
+ // Actions to functions
+
+
+ function validateCollection(value) {
+ return !!(value);
+ }
+
+ function validateManifest(value) {
+ return !!(value);
+ }
+
+ function validateItem(value) {
+ return !!(value);
+ }
+
+ function validateTranslations(value) {
+ return !!(value) && Object.keys(value).every((key) => key === 'en' || key === 'de');
+ }
+
+ function validatePanels(value) {
+ return !!(value) && Array.isArray(value);
+ }
+
+ function validateLang(value) {
+ return !!(value);
+ }
+
+ function validateColors(value) {
+ return !!(value);
+ }
+
+ function validateContainer(value) {
+ return !!(value);
+ }
+
+ function validateHeader(value, defaultValue) {
+ if (!value) return false;
+
+ const defaultKeys = Object.keys(defaultValue);
+ const invalidKeys = Object.keys(value)
+ .filter((key) => defaultKeys.findIndex((defaultKey) => defaultKey === key) === -1);
+ return invalidKeys.length === 0;
+ }
+
+ function validateLabels(labels, validLabels: Labels) {
+ // valid labels are the labels from the default config
+ // we consider the custom labels, in the case when all the keys have a value, otherwise we would have the button with empty text i.e for the following scenario
+ // when the item is ''
+ if (!labels || !validLabels) return false;
+
+ let isValid = true;
+ Object.keys(labels).forEach((key) => {
+ if (!(key in validLabels) || labels[key] === '') {
+ isValid = false;
+ }
+ });
+
+ return isValid;
+ }
+
+ function createDefaultActiveViews(panelsConfig) {
+ return panelsConfig
+ .filter((p) => p.views && p.views.length > 0)
+ .map((panel) => {
+ const defaultIndex = panel.views.findIndex((view) => view.default === true);
+ return defaultIndex > -1 ? defaultIndex : 0;
+ })
+ .reduce((acc, cur, i) => {
+ acc[i] = cur;
+ return acc;
+ }, {});
+ }
+
+
+ // URL Config
+
+ function splitUrlParts(urlQuery, attributes) {
+ if (urlQuery === '') {
+ return [undefined, undefined, undefined, undefined];
+ }
+ const arrayAttributes = urlQuery.split('_');
+ const manifestPart = arrayAttributes.find((element) => element[0].includes(attributes[0])); // index of manifest part in the splitted array: element[0] is 'm' the first letter of the part ?
+ const itemPart = arrayAttributes.find((element) => element[0].includes(attributes[1]));
+ const panelsPart = arrayAttributes.find((element) => element[0].includes(attributes[2]));
+ const showPart = arrayAttributes.find((element) => element[0].includes(attributes[3]));
+ return [manifestPart, itemPart, panelsPart, showPart];
+ }
+
+ function isManifestPartValid(manifestPart) {
+ const regexManifest = /m\d+$/;
+ return regexManifest.exec(manifestPart) !== null;
+ }
+
+ function isItemPartValid(itemPart) {
+ const regexItem = /i\d+$/;
+ return regexItem.exec(itemPart) !== null;
+ }
+
+ function isPanelsPartValid(panelsPart, panelsValue, numberPanels) {
+ const numbersPartArray = panelsValue.split('-');
+ const regexNumber = /^\d+$/;
+ if (panelsPart[0] !== 'p' || numbersPartArray.length !== numberPanels) {
+ return false;
+ }
+
+ for (let i = 0; i < numbersPartArray.length; i++) {
+ const panelTabPair = numbersPartArray[i];
+ if (panelTabPair.length !== 3
+ || regexNumber.test(panelTabPair[0]) === false
+ || regexNumber.test(panelTabPair[2]) === false
+ || panelTabPair[1] !== '.') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ function isShowPartValid(showValue, numberPanels) {
+ const showValueAsArray = showValue.split('-');
+ const regexNumbersPart = /\d\-/;
+ if (showValueAsArray.length > numberPanels) {
+ return false;
+ }
+ for (let i = 0; i < showValueAsArray.length - 1; i++) {
+ // if s0-2 is given and there are in total 4 panels, then it is still fine, since we can show less number of panels than the total one
+ // match the couples of (d-) -> a digit followed by a "-" character. In total there are (s.length - 1) - so number of panels we want to open - 1
+ const groupMatch = showValue.slice(i * 2, i * 2 + 2).match(regexNumbersPart);
+ if (groupMatch === null) {
+ return false;
+ }
+ }
+ const lastNumberString = showValue.slice(-1)[0];
+ const lastNumberInt = parseInt(lastNumberString, 10);
+ // last character must have only digits and not be greater than number of max panels
+ if (/^\d+$/.test(lastNumberString) === false || (lastNumberInt >= numberPanels || lastNumberInt < 0)) {
+ return false;
+ }
+ return true;
+ }
+
+ function createDefaultPanelValue(numberPanels) {
+ // get the number of panels and then create as many couples of (panel_index.0) until n_panels-1, the last couple need not have the '-' symbol
+ let p = '';
+ for (let j = 0; j < numberPanels; j++) {
+ if (j !== numberPanels - 1) {
+ p += `${j}.0-`;
+ } else {
+ p += `${j}.0`;
+ }
+ }
+ return p;
+ }
+
+ function createActiveViewsFromPanelsArray(panelsArray) {
+ // converts 'panelsArray' to an object with key, value: 'panel index: visible tab index'
+ return panelsArray.reduce((acc, cur) => {
+ // eslint-disable-next-line no-shadow
+ const [panelIndex, viewIndex] = cur.split('.').map((i) => parseInt(i, 10));
+ acc[panelIndex] = viewIndex;
+ return acc;
+ }, {});
+ }
+
+ function discoverCustomConfig(customConfig, defaultConfig) {
+ const {
+ container, translations, collection, manifest, item, panels, lang, colors, header, labels
+ } = customConfig;
+
+ return {
+ ...(validateContainer(container) && { container }),
+ ...(validateCollection(collection) && { collection }),
+ ...(validateManifest(manifest) && { manifest }),
+ ...(validateItem(item) && { item }),
+ ...(validateTranslations(translations) && { translations }),
+ ...(validatePanels(panels) && { panels }),
+ ...(validateLang(lang) && { lang }),
+ ...(validateColors(colors) && { colors }),
+ ...(validateHeader(header, defaultConfig.header) && { header }),
+ ...(validateLabels(labels, defaultConfig.labels) && { labels }),
+ };
+ }
+
+ function discoverUrlConfig(config) {
+ // split the url based on '_'
+ // get the part of attribute: get the attribute name and the value based on the type of attribute
+ // add each attribute to UrlConfig as key value
+ let urlConfig = {};
+ const urlQuery = BookmarkService.getQuery();
+ const attributes = ['m', 'i', 'p', 's'];
+ // values of manifest, item Indices ...
+ let [m, i, p, s] = [undefined, undefined, undefined, undefined];
+ const numberPanels = config.panels ? config.panels.length : 0;
+ const [manifestPart, itemPart, panelsPart, showPart] = splitUrlParts(urlQuery, attributes);
+ /*
+ if (isUrl(item)) urlConfig.item = item;
+ if (isUrl(manifest)) urlConfig.manifest = manifest;
+ if (isUrl(collection)) urlConfig.collection = collection;
+ */
+ // here we will validate for the structure of each component:, not their value range
+ if (manifestPart !== undefined) {
+ if (!isManifestPartValid(manifestPart)) {
+ throw new Error(i18n.global.t('error_manifestpart_tido_url'));
+ } else {
+ urlConfig.m = parseInt(manifestPart.slice(1), 10);
+ }
+ }
+ if (itemPart !== undefined) {
+ if (!isItemPartValid(itemPart)) {
+ throw new Error(i18n.global.t('error_itempart_tido_url'));
+ } else {
+ urlConfig.i = parseInt(itemPart.slice(1), 10);
+ }
+ }
+ if (panelsPart !== undefined) {
+ const panelsValue = panelsPart.slice(1);
+ if (!isPanelsPartValid(panelsPart, panelsValue, numberPanels)) {
+ throw new Error(i18n.global.t('error_panelspart_tido_url'));
+ } else {
+ p = panelsValue;
+ }
+ } else {
+ p = createDefaultPanelValue(numberPanels);
+ }
+ const panelsArray = p !== '' ? p.split('-') : [];
+ urlConfig.activeViews = createActiveViewsFromPanelsArray(panelsArray);
+
+ if (showPart !== undefined) {
+ const showValue = showPart.slice(1);
+ if (!isShowPartValid(showValue, numberPanels)) {
+ throw new Error(i18n.global.t('error_showpart_tido_url'));
+ } else {
+ // showValue needs to be an array of opened panel indices (Integers)
+ s = showValue.split('-').map(Number);
+ }
+ }
+ if (s === undefined) {
+ // If 's' is not given in URL, then we open all the panels which are given in config
+ urlConfig.show = Array.from({ length: numberPanels }, (value, index) => index);
+ } else {
+ urlConfig.show = s;
+ }
+ return urlConfig;
+ }
+
+ function discoverDefaultConfig(config) {
+ return {
+ ...JSON.parse(JSON.stringify(config)),
+ activeViews: createDefaultActiveViews(config.panels),
+ };
+ }
+
+ function load(custConfig) {
+ const customConfig = discoverCustomConfig(custConfig, config.value);
+ const urlConfig = discoverUrlConfig(custConfig);
+ const defaultConfig = discoverDefaultConfig(config.value);
+
+ const header: Header = {
+ ...defaultConfig.header,
+ ...customConfig.header,
+ };
+
+ if (customConfig.panels) {
+ // If the custom config provide panels config, we still need to check if it's valid.
+ // Here we enhance the potentially missing parts with default panel/view config.
+ // Hint: Not to confuse with the "defaultConfig" which provides an out-of-the-box panels setup
+
+ customConfig.panels = customConfig.panels.map((panel) => {
+ if (panel.views) {
+ panel.views = panel.views.map((view) => ({
+ ...defaultView,
+ ...view,
+ }));
+ }
+
+ return {
+ ...defaultPanel,
+ ...panel,
+ };
+ });
+ }
+
+ const resultConfig = {
+ ...defaultConfig,
+ ...customConfig,
+ ...urlConfig,
+ header,
+ };
+
+ const activeViews = urlConfig.activeViews || defaultConfig.activeViews;
+ setActiveViews(activeViews)
+
+ if (resultConfig.show && resultConfig.show.length > 0) {
+ // Set visible panels
+ // First hide all
+ resultConfig.panels.map((panel, i) => resultConfig.panels[i].show = false);
+
+ // Next show configured
+ resultConfig.show.forEach((panelIndex) => {
+ if (!Number.isInteger(panelIndex)) return;
+ const panel = resultConfig.panels[panelIndex];
+ if (!panel) return;
+
+ resultConfig.panels[panelIndex].show = true;
+ });
+ }
+
+ if (resultConfig.translations) {
+ const locales = Object.keys(resultConfig.translations);
+
+ locales.forEach((locale) => {
+ i18n.global.setLocaleMessage(locale, { ...(messages[locale] ? messages[locale] : {}), ...resultConfig.translations[locale] });
+ });
+ }
+ setConfig(resultConfig)
+
+ if (urlConfig.activeViews) setActiveViews(activeViews)
+ else setDefaultActiveViews(false) //dispatch('setDefaultActiveViews', false);
+ }
+
+
+ function setShowPanel( {index, show} ) {
+ setShowPanelSetter(index, show)
+
+ let panelIndexes = config.value.panels.reduce((acc, cur, i) => (cur.show ? [...acc, i] : acc), []);
+ if (panelIndexes.length === config.value.panels.length) panelIndexes = [];
+
+ BookmarkService.updateShow(panelIndexes);
+ };
+
+ function setContentType( type) {
+ const newConfig = { ...config.value };
+
+ newConfig.panels[3].views[0].connector.options = { type };
+ setConfig(newConfig)
+ };
+
+
+ async function setDefaultActiveViews (bookmark = true){
+ const activeViews = [];
+
+ config.value.panels.forEach(({ views }, panelIndex) => {
+ let defaultViewIndex = views.findIndex((view) => !!(view.default));
+ if (defaultViewIndex === -1) defaultViewIndex = 0;
+ activeViews[panelIndex] = defaultViewIndex;
+ });
+
+ if (bookmark) await BookmarkService.updatePanels(activeViews);
+ setActiveViews(activeViews)
+ }
+
+ return {
+ instanceId, config, activeViews, isValid,
+ activeContentType, getIconByType,
+ setConfig, setActivePanelView, setPanels, setShowPanelSetter, loadConfig, setActiveViews, setInstanceId,
+ validateCollection, validateManifest, validateItem, validateTranslations, validatePanels, validateLang, validateColors, validateContainer, validateLabels, validateHeader,
+ createDefaultActiveViews, splitUrlParts, isManifestPartValid, isItemPartValid, isShowPartValid, isPanelsPartValid,
+ createDefaultPanelValue, createActiveViewsFromPanelsArray, discoverCustomConfig, discoverUrlConfig, discoverDefaultConfig,
+ load, setShowPanel, setContentType, setDefaultActiveViews
+ }
+ })
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/types.d.ts b/src/types.d.ts
index 66a51234..c6fd36ac 100644
--- a/src/types.d.ts
+++ b/src/types.d.ts
@@ -7,6 +7,7 @@ declare global {
id: string
}
+
interface ActiveAnnotation {
[key: string]: Annotation
}
@@ -48,9 +49,16 @@ declare global {
total?: number,
annotationCollection?: string,
modules?: Module[]
-
}
+ interface Colors {
+ forceMode: string,
+ primary: string,
+ secondary: string,
+ accent: string
+ }
+
+
interface Content {
'@context': string,
url: string,
@@ -69,6 +77,13 @@ declare global {
value: string
}
+ interface Header {
+ show: boolean,
+ navigation: boolean,
+ panelsToggle: boolean,
+ languageSwitch: boolean
+ }
+
interface Idref {
'@context': string,
base?: string,
@@ -82,6 +97,7 @@ declare global {
manifest?: string,
license: License
}
+
interface Item {
'@context': string,
@@ -136,11 +152,18 @@ declare global {
metadata?: Metadata[]
}
+
interface Module {
editionManuscripts?: boolean,
editionPrints?: boolean
}
+ interface NotificationColors {
+ info: string,
+ warning: string
+ }
+
+
type RangeSelector = {
type: 'RangeSelector',
startSelector: CssSelector,
@@ -178,7 +201,6 @@ declare global {
type: TitleType
}
type TitleType = 'main' | 'sub';
-
}
export {}
\ No newline at end of file