@museumwnf/viewer-core 1.14.0 → 1.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +44 -44
- package/README.md +658 -635
- package/package.json +50 -50
- package/src/AppRoot.vue +58 -58
- package/src/catalogue/dates.js +209 -209
- package/src/catalogue/facets.js +61 -61
- package/src/catalogue/keywordIndex.js +289 -289
- package/src/catalogue/listQuery.js +100 -100
- package/src/catalogue/pagination.js +57 -57
- package/src/catalogue/searchLanguage.js +32 -32
- package/src/catalogue/timeline.js +220 -220
- package/src/composables/useCatalogueData.js +141 -141
- package/src/composables/useDataPackage.js +98 -98
- package/src/composables/useEntities.js +100 -100
- package/src/composables/useRecordLanguage.js +71 -71
- package/src/conventions.js +252 -148
- package/src/createViewer.js +29 -29
- package/src/i18n/I18nText.vue +17 -17
- package/src/i18n/I18nTextInline.vue +17 -17
- package/src/i18n/index.js +88 -88
- package/src/i18n/language.js +127 -127
- package/src/i18n/markdown.js +265 -265
- package/src/index.js +51 -50
- package/src/injectionKeys.js +1 -1
- package/src/languages.js +49 -49
- package/src/legacy/index.js +58 -58
- package/src/record/collectionTree.js +307 -307
- package/src/record/derivations.js +154 -154
- package/src/record/partners.js +87 -87
- package/src/record/recordSheet.js +231 -231
- package/src/rights.js +53 -53
- package/src/router/index.js +138 -138
- package/src/siteConfig.js +37 -37
- package/src/styles/base.css +49 -49
- package/src/testing/checkOfferedLanguages.js +67 -67
- package/src/testing/index.js +3 -3
- package/src/testing/smoke.js +103 -103
- package/src/testing/viteConfig.js +72 -90
- package/src/views/DetailView.vue +53 -53
- package/src/views/HomeView.vue +20 -20
- package/src/views/ListView.vue +61 -61
- package/src/views/NotFoundView.vue +8 -8
- package/src/vite.js +8 -8
|
@@ -1,100 +1,100 @@
|
|
|
1
|
-
import { computed, shallowRef } from 'vue'
|
|
2
|
-
import { useDataPackage } from './useDataPackage.js'
|
|
3
|
-
|
|
4
|
-
// The records of an entity are one thing: loaded once, from the chunk Vite
|
|
5
|
-
// emits for that one file, and shared by every page that asks for them. A
|
|
6
|
-
// website reads them through here and keeps no copy of its own — the alias
|
|
7
|
-
// `@inventory-data` is this package's, and a site file importing from it
|
|
8
|
-
// directly ships the whole dataset in its first bundle.
|
|
9
|
-
|
|
10
|
-
const records = new Map() // entity name → shallowRef(null | records)
|
|
11
|
-
const pending = new Map() // entity name → the load in flight
|
|
12
|
-
const indexes = new Map() // `${entity}.${key}` → computed Map
|
|
13
|
-
|
|
14
|
-
function refFor(name) {
|
|
15
|
-
let entry = records.get(name)
|
|
16
|
-
if (!entry) {
|
|
17
|
-
entry = shallowRef(null)
|
|
18
|
-
records.set(name, entry)
|
|
19
|
-
}
|
|
20
|
-
return entry
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* The shared ref of one entity, without asking for its load — `null` until a
|
|
25
|
-
* route's `meta.entities` or `loadEntities` brings the chunk in. This is what
|
|
26
|
-
* a website's data composable exports at module level, so that importing the
|
|
27
|
-
* composable loads nothing: the pages that read an entity are the ones that
|
|
28
|
-
* declare it.
|
|
29
|
-
*/
|
|
30
|
-
export function entityRef(name) {
|
|
31
|
-
return refFor(name)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Load the named entities, once each. Resolves with their records in the
|
|
36
|
-
* order asked for; a name the package does not have rejects, as `loadEntity`
|
|
37
|
-
* does.
|
|
38
|
-
*/
|
|
39
|
-
export function loadEntities(names) {
|
|
40
|
-
const { loadEntity } = useDataPackage()
|
|
41
|
-
return Promise.all(
|
|
42
|
-
names.map((name) => {
|
|
43
|
-
const entry = refFor(name)
|
|
44
|
-
if (entry.value !== null) return entry.value
|
|
45
|
-
let load = pending.get(name)
|
|
46
|
-
if (!load) {
|
|
47
|
-
load = loadEntity(name)
|
|
48
|
-
.then((data) => {
|
|
49
|
-
entry.value = data
|
|
50
|
-
pending.delete(name)
|
|
51
|
-
return data
|
|
52
|
-
})
|
|
53
|
-
.catch((error) => {
|
|
54
|
-
pending.delete(name)
|
|
55
|
-
throw error
|
|
56
|
-
})
|
|
57
|
-
pending.set(name, load)
|
|
58
|
-
}
|
|
59
|
-
return load
|
|
60
|
-
}),
|
|
61
|
-
)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* A record map for one entity, keyed on `key` (the package id by default) —
|
|
66
|
-
* empty until the entity is loaded, and rebuilt if it ever reloads.
|
|
67
|
-
*/
|
|
68
|
-
export function byId(name, key = 'id') {
|
|
69
|
-
const cacheKey = `${name}.${key}`
|
|
70
|
-
let index = indexes.get(cacheKey)
|
|
71
|
-
if (!index) {
|
|
72
|
-
const entry = refFor(name)
|
|
73
|
-
index = computed(() => new Map((entry.value ?? []).map((record) => [record[key], record])))
|
|
74
|
-
indexes.set(cacheKey, index)
|
|
75
|
-
}
|
|
76
|
-
return index
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* The standard way a website reads its records.
|
|
81
|
-
*
|
|
82
|
-
* `useEntities(['items', 'partners'])` returns one reactive ref per name —
|
|
83
|
-
* `null` until that entity's chunk has arrived, the records after — plus
|
|
84
|
-
* `ready`, a promise for all of them, `loaded`, the same as a boolean, and
|
|
85
|
-
* `byId`. The refs are the same objects for every caller, so a composable
|
|
86
|
-
* may export them at module level and a view may read them at any time.
|
|
87
|
-
*
|
|
88
|
-
* A route that declares `meta: { entities: [...] }` has them loaded by the
|
|
89
|
-
* router before its view is created, so that view never sees `null`.
|
|
90
|
-
*/
|
|
91
|
-
export function useEntities(names) {
|
|
92
|
-
const list = Array.isArray(names) ? names : [names]
|
|
93
|
-
const result = { ready: loadEntities(list) }
|
|
94
|
-
for (const name of list) {
|
|
95
|
-
result[name] = refFor(name)
|
|
96
|
-
}
|
|
97
|
-
result.loaded = computed(() => list.every((name) => refFor(name).value !== null))
|
|
98
|
-
result.byId = byId
|
|
99
|
-
return result
|
|
100
|
-
}
|
|
1
|
+
import { computed, shallowRef } from 'vue'
|
|
2
|
+
import { useDataPackage } from './useDataPackage.js'
|
|
3
|
+
|
|
4
|
+
// The records of an entity are one thing: loaded once, from the chunk Vite
|
|
5
|
+
// emits for that one file, and shared by every page that asks for them. A
|
|
6
|
+
// website reads them through here and keeps no copy of its own — the alias
|
|
7
|
+
// `@inventory-data` is this package's, and a site file importing from it
|
|
8
|
+
// directly ships the whole dataset in its first bundle.
|
|
9
|
+
|
|
10
|
+
const records = new Map() // entity name → shallowRef(null | records)
|
|
11
|
+
const pending = new Map() // entity name → the load in flight
|
|
12
|
+
const indexes = new Map() // `${entity}.${key}` → computed Map
|
|
13
|
+
|
|
14
|
+
function refFor(name) {
|
|
15
|
+
let entry = records.get(name)
|
|
16
|
+
if (!entry) {
|
|
17
|
+
entry = shallowRef(null)
|
|
18
|
+
records.set(name, entry)
|
|
19
|
+
}
|
|
20
|
+
return entry
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The shared ref of one entity, without asking for its load — `null` until a
|
|
25
|
+
* route's `meta.entities` or `loadEntities` brings the chunk in. This is what
|
|
26
|
+
* a website's data composable exports at module level, so that importing the
|
|
27
|
+
* composable loads nothing: the pages that read an entity are the ones that
|
|
28
|
+
* declare it.
|
|
29
|
+
*/
|
|
30
|
+
export function entityRef(name) {
|
|
31
|
+
return refFor(name)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Load the named entities, once each. Resolves with their records in the
|
|
36
|
+
* order asked for; a name the package does not have rejects, as `loadEntity`
|
|
37
|
+
* does.
|
|
38
|
+
*/
|
|
39
|
+
export function loadEntities(names) {
|
|
40
|
+
const { loadEntity } = useDataPackage()
|
|
41
|
+
return Promise.all(
|
|
42
|
+
names.map((name) => {
|
|
43
|
+
const entry = refFor(name)
|
|
44
|
+
if (entry.value !== null) return entry.value
|
|
45
|
+
let load = pending.get(name)
|
|
46
|
+
if (!load) {
|
|
47
|
+
load = loadEntity(name)
|
|
48
|
+
.then((data) => {
|
|
49
|
+
entry.value = data
|
|
50
|
+
pending.delete(name)
|
|
51
|
+
return data
|
|
52
|
+
})
|
|
53
|
+
.catch((error) => {
|
|
54
|
+
pending.delete(name)
|
|
55
|
+
throw error
|
|
56
|
+
})
|
|
57
|
+
pending.set(name, load)
|
|
58
|
+
}
|
|
59
|
+
return load
|
|
60
|
+
}),
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A record map for one entity, keyed on `key` (the package id by default) —
|
|
66
|
+
* empty until the entity is loaded, and rebuilt if it ever reloads.
|
|
67
|
+
*/
|
|
68
|
+
export function byId(name, key = 'id') {
|
|
69
|
+
const cacheKey = `${name}.${key}`
|
|
70
|
+
let index = indexes.get(cacheKey)
|
|
71
|
+
if (!index) {
|
|
72
|
+
const entry = refFor(name)
|
|
73
|
+
index = computed(() => new Map((entry.value ?? []).map((record) => [record[key], record])))
|
|
74
|
+
indexes.set(cacheKey, index)
|
|
75
|
+
}
|
|
76
|
+
return index
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The standard way a website reads its records.
|
|
81
|
+
*
|
|
82
|
+
* `useEntities(['items', 'partners'])` returns one reactive ref per name —
|
|
83
|
+
* `null` until that entity's chunk has arrived, the records after — plus
|
|
84
|
+
* `ready`, a promise for all of them, `loaded`, the same as a boolean, and
|
|
85
|
+
* `byId`. The refs are the same objects for every caller, so a composable
|
|
86
|
+
* may export them at module level and a view may read them at any time.
|
|
87
|
+
*
|
|
88
|
+
* A route that declares `meta: { entities: [...] }` has them loaded by the
|
|
89
|
+
* router before its view is created, so that view never sees `null`.
|
|
90
|
+
*/
|
|
91
|
+
export function useEntities(names) {
|
|
92
|
+
const list = Array.isArray(names) ? names : [names]
|
|
93
|
+
const result = { ready: loadEntities(list) }
|
|
94
|
+
for (const name of list) {
|
|
95
|
+
result[name] = refFor(name)
|
|
96
|
+
}
|
|
97
|
+
result.loaded = computed(() => list.every((name) => refFor(name).value !== null))
|
|
98
|
+
result.byId = byId
|
|
99
|
+
return result
|
|
100
|
+
}
|
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
import { computed, ref, toValue, watch } from 'vue'
|
|
2
|
-
import { useI18n } from '../i18n/index.js'
|
|
3
|
-
import { isRtl } from '../i18n/language.js'
|
|
4
|
-
import { useDataPackage } from './useDataPackage.js'
|
|
5
|
-
|
|
6
|
-
const BASE_LANGUAGE = 'en'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* The language a record is read in, given the languages it carries and the
|
|
10
|
-
* language the website is in: the site language when the record has it,
|
|
11
|
-
* English when it does not, the record's first language when it has neither.
|
|
12
|
-
*/
|
|
13
|
-
export function resolveRecordLanguage(languages, siteLanguage) {
|
|
14
|
-
const carried = languages ?? []
|
|
15
|
-
if (carried.length === 0) return siteLanguage
|
|
16
|
-
return (
|
|
17
|
-
carried.find((code) => code === siteLanguage) ??
|
|
18
|
-
carried.find((code) => code === BASE_LANGUAGE) ??
|
|
19
|
-
carried[0]
|
|
20
|
-
)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* The site language and the record language are two different things. The
|
|
25
|
-
* site language is negotiated once and holds for the whole visit; a record
|
|
26
|
-
* may carry languages the site does not offer, and the visitor may read the
|
|
27
|
-
* record they are on in any of them. That choice is view state: it is not in
|
|
28
|
-
* the URL, it does not touch the site language, and it is forgotten when the
|
|
29
|
-
* site language changes or another record is shown.
|
|
30
|
-
*
|
|
31
|
-
* `record` is a ref, a computed or a getter to the record; its `languages`
|
|
32
|
-
* field says what it carries. A record without one carries every language
|
|
33
|
-
* `entity` has a translation file for, when `entity` is given; `languages`
|
|
34
|
-
* (a getter) overrides both.
|
|
35
|
-
*/
|
|
36
|
-
export function useRecordLanguage(record, { entity, languages: given } = {}) {
|
|
37
|
-
const { locale } = useI18n()
|
|
38
|
-
const { availableLanguages } = useDataPackage()
|
|
39
|
-
|
|
40
|
-
const languages = computed(() => {
|
|
41
|
-
const own = given ? toValue(given) : toValue(record)?.languages
|
|
42
|
-
if (Array.isArray(own) && own.length > 0) return own
|
|
43
|
-
return entity ? availableLanguages(entity) : []
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
const chosen = ref(null)
|
|
47
|
-
const language = computed(() =>
|
|
48
|
-
chosen.value && languages.value.includes(chosen.value)
|
|
49
|
-
? chosen.value
|
|
50
|
-
: resolveRecordLanguage(languages.value, locale.value),
|
|
51
|
-
)
|
|
52
|
-
const dir = computed(() => (isRtl(language.value) ? 'rtl' : 'ltr'))
|
|
53
|
-
|
|
54
|
-
function select(code) {
|
|
55
|
-
if (languages.value.includes(code)) chosen.value = code
|
|
56
|
-
}
|
|
57
|
-
function reset() {
|
|
58
|
-
chosen.value = null
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
watch(locale, reset)
|
|
62
|
-
watch(
|
|
63
|
-
() => {
|
|
64
|
-
const current = toValue(record)
|
|
65
|
-
return current?.id ?? current
|
|
66
|
-
},
|
|
67
|
-
reset,
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
return { language, languages, dir, select, reset }
|
|
71
|
-
}
|
|
1
|
+
import { computed, ref, toValue, watch } from 'vue'
|
|
2
|
+
import { useI18n } from '../i18n/index.js'
|
|
3
|
+
import { isRtl } from '../i18n/language.js'
|
|
4
|
+
import { useDataPackage } from './useDataPackage.js'
|
|
5
|
+
|
|
6
|
+
const BASE_LANGUAGE = 'en'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The language a record is read in, given the languages it carries and the
|
|
10
|
+
* language the website is in: the site language when the record has it,
|
|
11
|
+
* English when it does not, the record's first language when it has neither.
|
|
12
|
+
*/
|
|
13
|
+
export function resolveRecordLanguage(languages, siteLanguage) {
|
|
14
|
+
const carried = languages ?? []
|
|
15
|
+
if (carried.length === 0) return siteLanguage
|
|
16
|
+
return (
|
|
17
|
+
carried.find((code) => code === siteLanguage) ??
|
|
18
|
+
carried.find((code) => code === BASE_LANGUAGE) ??
|
|
19
|
+
carried[0]
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The site language and the record language are two different things. The
|
|
25
|
+
* site language is negotiated once and holds for the whole visit; a record
|
|
26
|
+
* may carry languages the site does not offer, and the visitor may read the
|
|
27
|
+
* record they are on in any of them. That choice is view state: it is not in
|
|
28
|
+
* the URL, it does not touch the site language, and it is forgotten when the
|
|
29
|
+
* site language changes or another record is shown.
|
|
30
|
+
*
|
|
31
|
+
* `record` is a ref, a computed or a getter to the record; its `languages`
|
|
32
|
+
* field says what it carries. A record without one carries every language
|
|
33
|
+
* `entity` has a translation file for, when `entity` is given; `languages`
|
|
34
|
+
* (a getter) overrides both.
|
|
35
|
+
*/
|
|
36
|
+
export function useRecordLanguage(record, { entity, languages: given } = {}) {
|
|
37
|
+
const { locale } = useI18n()
|
|
38
|
+
const { availableLanguages } = useDataPackage()
|
|
39
|
+
|
|
40
|
+
const languages = computed(() => {
|
|
41
|
+
const own = given ? toValue(given) : toValue(record)?.languages
|
|
42
|
+
if (Array.isArray(own) && own.length > 0) return own
|
|
43
|
+
return entity ? availableLanguages(entity) : []
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const chosen = ref(null)
|
|
47
|
+
const language = computed(() =>
|
|
48
|
+
chosen.value && languages.value.includes(chosen.value)
|
|
49
|
+
? chosen.value
|
|
50
|
+
: resolveRecordLanguage(languages.value, locale.value),
|
|
51
|
+
)
|
|
52
|
+
const dir = computed(() => (isRtl(language.value) ? 'rtl' : 'ltr'))
|
|
53
|
+
|
|
54
|
+
function select(code) {
|
|
55
|
+
if (languages.value.includes(code)) chosen.value = code
|
|
56
|
+
}
|
|
57
|
+
function reset() {
|
|
58
|
+
chosen.value = null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
watch(locale, reset)
|
|
62
|
+
watch(
|
|
63
|
+
() => {
|
|
64
|
+
const current = toValue(record)
|
|
65
|
+
return current?.id ?? current
|
|
66
|
+
},
|
|
67
|
+
reset,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
return { language, languages, dir, select, reset }
|
|
71
|
+
}
|