@museumwnf/viewer-core 2.3.0 → 2.5.0
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/README.md +4 -4
- package/package.json +1 -1
- package/src/dxa/countryCodes.js +92 -0
- package/src/dxa/exhibition/useExhibitionCollection.js +10 -14
- package/src/dxa/exhibition/useExhibitionData.js +6 -1
- package/src/dxa/exhibition/useExhibitionItemDetail.js +21 -20
- package/src/dxa/exhibition/useExhibitionSheet.js +1 -1
- package/src/dxa/exhibition/useExhibitionTimeline.js +39 -184
- package/src/dxa/gallery/useGalleryCollection.js +10 -13
- package/src/dxa/gallery/useGalleryData.js +6 -0
- package/src/dxa/gallery/useGalleryItemDetail.js +7 -7
- package/src/dxa/gallery/useGalleryPartner.js +1 -1
- package/src/dxa/gallery/useGallerySheet.js +1 -1
- package/src/dxa/gallery/useGalleryTimeline.js +10 -96
package/README.md
CHANGED
|
@@ -402,7 +402,7 @@ Timeline pages are all one of these three axes over the same event shape.
|
|
|
402
402
|
|
|
403
403
|
| Export | Meaning |
|
|
404
404
|
| --- | --- |
|
|
405
|
-
| `useTimelineEvents({ scope, countryLabel, countryIdForCode, tr, timelinesEntity, eventsEntity })` | `{ countries, yearRange, yearBuckets(t), hasTimeline, usesLocalTimeline, findEvents({ country, collection, begin, end }) }`. `scope` is `'country'` (the worldwide merge), `'local'` (an exhibition's own `thg_local` chronology in place of the merge — `countries` is empty, there is nothing to filter by) or `'collection'` (the merge again, plus a `collection` filter on the exhibition a timeline is bound to, `null` meaning the Permanent Collection). `countries` is one entry per country with a chronology, `{ value, label }`, alphabetized, with an `{ value: 'all', label: null }` marker first (never, for `'local'`) — the marker carries no label of its own so a picker names it through its own catalogue entry. `countryLabel(id)` labels a country; the DXA legacy 2-letter code table stays out of
|
|
405
|
+
| `useTimelineEvents({ scope, countryLabel, countryIdForCode, tr, timelinesEntity, eventsEntity })` | `{ countries, yearRange, yearBuckets(t), hasTimeline, usesLocalTimeline, findEvents({ country, collection, begin, end }) }`. `scope` is `'country'` (the worldwide merge), `'local'` (an exhibition's own `thg_local` chronology in place of the merge — `countries` is empty, there is nothing to filter by) or `'collection'` (the merge again, plus a `collection` filter on the exhibition a timeline is bound to, `null` meaning the Permanent Collection). `countries` is one entry per country with a chronology, `{ value, label }`, alphabetized, with an `{ value: 'all', label: null }` marker first (never, for `'local'`) — the marker carries no label of its own so a picker names it through its own catalogue entry. `countryLabel(id)` labels a country; the DXA legacy 2-letter code table stays out of the engine (the DXA families' data layer carries it, `/dxa`), so a caller passing one instead resolves it through `countryIdForCode(code)`. `tr(id)` returns an event's translated fields, already resolved for entity and language, the same way a site's own data composable already binds it. `findEvents` returns the scope's events in `[begin, end]`, chronological, `display_order` breaking a tie on `year_from`, an event with no year at all last. |
|
|
406
406
|
| `overlapsRange(event, begin, end)` | legacy `class.hcr.inc.php`'s overlap rule, standalone: a dated event overlaps once its known end is past `begin` and its start is before `end`; an open-ended event (no known end) overlaps on its start alone; an undated event matches nothing but an unbounded search. |
|
|
407
407
|
| `effectiveYearTo(event)` | `event.year_to`, or `null` when it is `0` or absent — legacy's convention for an open-ended period. |
|
|
408
408
|
| `eventDateLabel(event, text, t)` | the label under an event, trying in order: a narrative chronology's own period name (`text.name` — a sort key there, not a label), a curated description pair (`text.date_from_description` / `text.date_to_description`), and the derived year range through `eraLabel`, for the worldwide merge which has neither. |
|
|
@@ -560,9 +560,9 @@ below for whichever future site needs its own).
|
|
|
560
560
|
|
|
561
561
|
| Export (gallery / exhibition) | Reads | Returns |
|
|
562
562
|
| --- | --- | --- |
|
|
563
|
-
| `useGalleryData(config)` / `useExhibitionData(config)` | `config.eager` (entity names `loadEnglish` loads eagerly; default the family's own list, the exhibition's also carrying `themes`) | the family's whole data layer: `tr`/`md`/`mdInline`/`mdStrip`/`labelOf`/`loadEnglish`/`translations`, every entity and lookup map (`items`, `partners`, `countries`, `tags`, `dynasties`, `timelines`, `timelineEvents`, …), the routes (`itemRoute`, `partnerRoute`, `partnerObjectsRoute`), the legacy URL wrappers (`itemFromUidPath`, `partnerFromKey` — gallery only), `chromeImage`, `manifest`, `defaultLang`, plus family-specific fields (gallery: `siblingGalleries`/`pickSiblings`; exhibition: `isHiddenPartner`, `isInstitution`, `projectName`, `exhibitionTitle`/`exhibitionSubtitle`/`exhibitionHeadline`/`bannerCaption`, `siblingSites`) |
|
|
564
|
-
| `useGalleryCollection(data, config)` / `useExhibitionCollection(data, config)` | the `data` object above; `config` unused today | `FACETS`, `tile(item, t)`, `collectionResults` (the `CatalogueResultsView` spec), plus the facet-value helpers (`countryIdForCode
|
|
565
|
-
| `useGalleryTimeline(data, collection, config)` / `useExhibitionTimeline(data, collection, config)` | `data`, the `collection` object above (for its `tile`); `config` unused today | the timeline specs (`timelineResults`/`timelineSpec`, `timelineGallery`/`timelineGallerySpec`) and
|
|
563
|
+
| `useGalleryData(config)` / `useExhibitionData(config)` | `config.eager` (entity names `loadEnglish` loads eagerly; default the family's own list, the exhibition's also carrying `themes`) | the family's whole data layer: `tr`/`md`/`mdInline`/`mdStrip`/`labelOf`/`loadEnglish`/`translations`, every entity and lookup map (`items`, `partners`, `countries`, `tags`, `dynasties`, `timelines`, `timelineEvents`, …), the routes (`itemRoute`, `partnerRoute`, `partnerObjectsRoute`), the legacy URL wrappers (`itemFromUidPath`, `partnerFromKey` — gallery only), the DXA legacy country code both ways (`countryIdForCode(code)`, idempotent on an id, and `countryLabel(countryId)`, one table the collection and the timeline both read), `chromeImage`, `manifest`, `defaultLang`, plus family-specific fields (gallery: `siblingGalleries`/`pickSiblings`; exhibition: `isHiddenPartner`, `isInstitution`, `projectName`, `exhibitionTitle`/`exhibitionSubtitle`/`exhibitionHeadline`/`bannerCaption`, `siblingSites`) |
|
|
564
|
+
| `useGalleryCollection(data, config)` / `useExhibitionCollection(data, config)` | the `data` object above; `config` unused today | `FACETS`, `tile(item, t)`, `collectionResults` (the `CatalogueResultsView` spec), plus the facet-value helpers (`countryIdForCode` — the data layer's —, `tagLabelForLegacy`, gallery's own `tagIdForLegacy`, `hasEveryTag`) |
|
|
565
|
+
| `useGalleryTimeline(data, collection, config)` / `useExhibitionTimeline(data, collection, config)` | `data`, the `collection` object above (for its `tile`); `config` unused today | the timeline specs (`timelineResults`/`timelineSpec`, `timelineGallery`/`timelineGallerySpec`), `timelineEvents` (viewer-core's `useTimelineEvents` engine — for an exhibition, the one its flags put in force, country or local), and the data layer's `countryLabel`/`countryIdForCode`; the gallery adds `timelineGalleryItems`, the exhibition `usesLocalTimeline` and `hasTimeline` for its local-vs-country chronology switch |
|
|
566
566
|
| `useGalleryPartner(data, collection, config)` / `useExhibitionPartner(data, config)` | `data`, and (gallery only) `collection` for its `tile` | `partnerList`/`partnerListSpec`, `partnerSheet`/`partnerSheetSpec`, and (gallery only) `partnerObjects` — the exhibition shape's `partnerSpecs.js` never carried an objects-page spec |
|
|
567
567
|
| `useGallerySheet(data, config)` / `useExhibitionSheet(data, config)` | `data`; `config` unused today | `{ itemSheet }`, the `RecordView` spec — the exhibition shape's `citation.project` reads `data.projectName`, absent from the gallery shape |
|
|
568
568
|
| `PAGE_SIZE`, `DATE_MODE`, `FACET_CATEGORIES`, `FACET_LABEL_KEYS`, `useFacetLabels()` | — | the five THG catalogue facets, shared verbatim by both families (nine tiles a page, containment dates, `type`/`dynasty`/`subject`/`material`/`artist`) |
|
package/package.json
CHANGED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { computed } from 'vue'
|
|
2
|
+
|
|
3
|
+
// The DXA legacy country code, the one a gallery's or an exhibition's URLs
|
|
4
|
+
// carry (`?country=eg`, the facet values, the timeline's own links), and the
|
|
5
|
+
// inventory id the records are keyed by (`egy`). Both families read it for
|
|
6
|
+
// their collection and their timeline through this one table
|
|
7
|
+
// (inventory-app#2056); each of the four once defined its own.
|
|
8
|
+
//
|
|
9
|
+
// Names and legacy codes both come from countries.json. The exporter scopes
|
|
10
|
+
// that file to "member item countries ∪ their holders' countries ∪ the
|
|
11
|
+
// global timeline's countries", so it covers every timeline country — the
|
|
12
|
+
// `Intl.DisplayNames` fallback below is therefore dead code for them, and is
|
|
13
|
+
// kept only so a regressed package degrades to a rendered ISO code rather
|
|
14
|
+
// than a raw id. The `code` countries.json ships is the country's
|
|
15
|
+
// `backward_compatibility`, which is exactly the code legacy's own URLs
|
|
16
|
+
// used, including the ones that are not ISO 3166-1 alpha-2 (`uk`, `pa`,
|
|
17
|
+
// `qt`, `rm`, `sb`, `ua`, …). Those are the reason the fallback must stay
|
|
18
|
+
// unreachable rather than merely rare: read as ISO, `ua` is Ukraine and `sb`
|
|
19
|
+
// is the Solomon Islands, where legacy means the UAE and Serbia. Only
|
|
20
|
+
// countries.json can name them correctly.
|
|
21
|
+
|
|
22
|
+
// Two legacy codes are not ISO 3166-1 alpha-2.
|
|
23
|
+
const LEGACY_TO_ISO = { uk: 'GB', pa: 'PS' }
|
|
24
|
+
|
|
25
|
+
const regionNames = (() => {
|
|
26
|
+
try {
|
|
27
|
+
return new Intl.DisplayNames(['en'], { type: 'region' })
|
|
28
|
+
} catch {
|
|
29
|
+
return null
|
|
30
|
+
}
|
|
31
|
+
})()
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The legacy country code both ways, over a family data layer's
|
|
35
|
+
* `countries`, `countryById`, `timelines` and `labelOf`.
|
|
36
|
+
*
|
|
37
|
+
* - `countryIdForCode(code)`: the inventory id for a legacy code; idempotent
|
|
38
|
+
* on an id (the composed views' own country controls write ids), `null`
|
|
39
|
+
* for none, `'all'` or an unknown code.
|
|
40
|
+
* - `countryLabel(countryId)`: a country's name, for one countries.json
|
|
41
|
+
* lacks too (read off its timeline row).
|
|
42
|
+
*/
|
|
43
|
+
export function useLegacyCountryCodes({ countries, countryById, timelines, labelOf }) {
|
|
44
|
+
const countryIdSet = computed(() => new Set((countries.value ?? []).map((c) => c.id)))
|
|
45
|
+
|
|
46
|
+
// A lookup, not a parse. The fallback exists only for the regressed-package
|
|
47
|
+
// case described above, and it must agree with GLOBAL_TIMELINE_LIKE_PATTERNS
|
|
48
|
+
// in the exporter's timeline exporter: the country sits after the literal
|
|
49
|
+
// `country` segment in BOTH keyspaces (`mwnf3:hcr:country:<cc>` and
|
|
50
|
+
// `mwnf3_sharing_history:sh_hcr:country:<cc>:exhibition:2`), and it is the
|
|
51
|
+
// last segment in only the first — taking the last one yields `2` on the
|
|
52
|
+
// second.
|
|
53
|
+
function legacyCodeOf(timeline) {
|
|
54
|
+
const fromPackage = countryById.value.get(timeline.country_id)?.code
|
|
55
|
+
if (fromPackage) return fromPackage
|
|
56
|
+
const parts = (timeline.backward_compatibility ?? '').split(':')
|
|
57
|
+
const at = parts.indexOf('country')
|
|
58
|
+
return (at >= 0 ? parts[at + 1] : null) || timeline.country_id
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function nameFor(timeline) {
|
|
62
|
+
if (countryIdSet.value.has(timeline.country_id)) return labelOf('countries', timeline.country_id)
|
|
63
|
+
const legacy = legacyCodeOf(timeline)
|
|
64
|
+
const iso = LEGACY_TO_ISO[legacy] ?? String(legacy).toUpperCase()
|
|
65
|
+
try {
|
|
66
|
+
return regionNames?.of(iso) ?? iso
|
|
67
|
+
} catch {
|
|
68
|
+
return iso
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function countryLabel(countryId) {
|
|
73
|
+
if (countryIdSet.value.has(countryId)) return labelOf('countries', countryId)
|
|
74
|
+
const timeline = (timelines.value ?? []).find((t) => t.country_id === countryId)
|
|
75
|
+
return timeline ? nameFor(timeline) : countryId
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// A country served by both chronologies has two timeline rows carrying the
|
|
79
|
+
// same code and the same `country_id`, so either row answers.
|
|
80
|
+
function countryIdForCode(code) {
|
|
81
|
+
if (!code || code === 'all') return null
|
|
82
|
+
// Already an id: pass it through unchanged, before the legacy-code
|
|
83
|
+
// lookups below get a chance to find nothing and match every record.
|
|
84
|
+
if (countryIdSet.value.has(code)) return code
|
|
85
|
+
const timeline = (timelines.value ?? []).find((t) => legacyCodeOf(t) === code)
|
|
86
|
+
if (timeline) return timeline.country_id
|
|
87
|
+
// A country with no chronology: countries.json's own code.
|
|
88
|
+
return (countries.value ?? []).find((c) => c.code === code)?.id ?? null
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { countryIdForCode, countryLabel }
|
|
92
|
+
}
|
|
@@ -22,26 +22,22 @@ export { DATE_MODE, FACET_CATEGORIES, FACET_LABEL_KEYS, PAGE_SIZE, useFacetLabel
|
|
|
22
22
|
* The exhibition's collection spec, over its own data layer's `data` (the
|
|
23
23
|
* return value of {@link import('./useExhibitionData.js').useExhibitionData}).
|
|
24
24
|
*
|
|
25
|
-
* Reads `data.
|
|
26
|
-
* `data.
|
|
27
|
-
* `data.
|
|
28
|
-
* nothing today.
|
|
25
|
+
* Reads `data.tags`, `data.countryById`, `data.tagById`, `data.itemById`,
|
|
26
|
+
* `data.labelOf`, `data.itemRoute`, `data.tr`, `data.defaultLang`,
|
|
27
|
+
* `data.mdInline`, `data.projectName`, `data.countryIdForCode`. `config`
|
|
28
|
+
* carries nothing today.
|
|
29
29
|
*/
|
|
30
30
|
export function useExhibitionCollection(data, config = {}) {
|
|
31
31
|
void config
|
|
32
32
|
const {
|
|
33
|
-
|
|
33
|
+
tags, countryById, tagById, itemById, labelOf, itemRoute, tr, defaultLang, mdInline, projectName, countryIdForCode,
|
|
34
34
|
} = data
|
|
35
35
|
|
|
36
36
|
// ── What the URL carries ─────────────────────────────────────────────
|
|
37
37
|
//
|
|
38
|
-
// The legacy 2-letter country code for `country
|
|
39
|
-
//
|
|
40
|
-
// resolves now.
|
|
41
|
-
|
|
42
|
-
function countryIdForCode(code) {
|
|
43
|
-
return code ? (countries.value ?? []).find((c) => c.code === code)?.id ?? null : null
|
|
44
|
-
}
|
|
38
|
+
// The legacy 2-letter country code for `country` (the data layer's
|
|
39
|
+
// `countryIdForCode`), the legacy tag id for a facet: the values legacy's
|
|
40
|
+
// URLs carried, so a link shared then still resolves now.
|
|
45
41
|
|
|
46
42
|
function tagLabelForLegacy(legacyId) {
|
|
47
43
|
return (tags.value ?? []).find((t) => t.legacy_tag_id === legacyId)?.label ?? legacyId
|
|
@@ -160,14 +156,14 @@ export function useExhibitionCollection(data, config = {}) {
|
|
|
160
156
|
pageSize: PAGE_SIZE,
|
|
161
157
|
variant: 'grid',
|
|
162
158
|
recordRoute: 'item',
|
|
163
|
-
actionLabel: '
|
|
159
|
+
actionLabel: 'catalogue.results.seeDatabaseEntry',
|
|
164
160
|
empty: 'catalogue.results.noResults',
|
|
165
161
|
pagination: { jump: true },
|
|
166
162
|
|
|
167
163
|
record: (item, { t }) => tile(item, t),
|
|
168
164
|
|
|
169
165
|
summary: ({ filters, pageInfo, t }) => [
|
|
170
|
-
{ label: t('
|
|
166
|
+
{ label: t('core.section.collection'), value: filterSummary(filters, t) },
|
|
171
167
|
{ count: pageInfo.total, value: `${t('catalogue.results.outOf')} ${itemById.value.size} ${t('catalogue.results.objects')}` },
|
|
172
168
|
],
|
|
173
169
|
}
|
|
@@ -4,6 +4,7 @@ import { useCatalogueData } from '../../composables/useCatalogueData.js'
|
|
|
4
4
|
import { useDataPackage } from '../../composables/useDataPackage.js'
|
|
5
5
|
import { byId, entityRef } from '../../composables/useEntities.js'
|
|
6
6
|
import { projectLabel } from '../../conventions.js'
|
|
7
|
+
import { useLegacyCountryCodes } from '../countryCodes.js'
|
|
7
8
|
|
|
8
9
|
// The data layer every DXA exhibition site (the-use-of-colours-in-art,
|
|
9
10
|
// water-in-islam, …) wrote for itself: the entities an exhibition reads,
|
|
@@ -134,6 +135,10 @@ export function useExhibitionData(config = {}) {
|
|
|
134
135
|
return country ? labelOf('countries', country.id) : code
|
|
135
136
|
}
|
|
136
137
|
|
|
138
|
+
// The legacy code both ways, the one table the collection and the
|
|
139
|
+
// timeline read.
|
|
140
|
+
const { countryIdForCode, countryLabel } = useLegacyCountryCodes({ countries, countryById, timelines, labelOf })
|
|
141
|
+
|
|
137
142
|
/** The canonical item route: the package id, and no language in the path. */
|
|
138
143
|
function itemRoute(item) {
|
|
139
144
|
return { name: 'item', params: { id: item.id } }
|
|
@@ -236,7 +241,7 @@ export function useExhibitionData(config = {}) {
|
|
|
236
241
|
tr, md, mdInline, mdStrip, labelOf, loadEnglish, availableLanguages, loadTranslations, translations,
|
|
237
242
|
chromeImage,
|
|
238
243
|
partnerById, countryById, tagById, dynastyById, glossaryById, languageByCode,
|
|
239
|
-
countryByCode, countryLabelFromCode,
|
|
244
|
+
countryByCode, countryLabelFromCode, countryIdForCode, countryLabel,
|
|
240
245
|
itemRoute, isInstitution, partnerRoute, partnerObjectsRoute,
|
|
241
246
|
isExploreRecord, projectName,
|
|
242
247
|
exhibitionTitle, exhibitionSubtitle, exhibitionHeadline, bannerCaption,
|
|
@@ -25,14 +25,15 @@ import { useExhibitionSheet } from './useExhibitionSheet.js'
|
|
|
25
25
|
* Reads `data.defaultLang`, `data.dynastyById`, `data.isExploreRecord`,
|
|
26
26
|
* `data.isHiddenPartner`, `data.labelOf`, `data.partnerById`,
|
|
27
27
|
* `data.partnerRoute`, `data.translations`, what `useExhibitionSheet` reads,
|
|
28
|
-
* and `timeline.countryIdForCode`, `timeline.
|
|
29
|
-
* `timeline.
|
|
28
|
+
* and `timeline.countryIdForCode`, `timeline.hasTimeline`,
|
|
29
|
+
* `timeline.timelineEvents` (`countries`, `findEvents`), `timeline.timelineSpec`.
|
|
30
30
|
*/
|
|
31
31
|
export function useExhibitionItemDetail(data, timeline) {
|
|
32
32
|
const {
|
|
33
33
|
defaultLang, dynastyById, isExploreRecord, isHiddenPartner, labelOf, partnerById, partnerRoute, translations,
|
|
34
34
|
} = data
|
|
35
|
-
const { countryIdForCode,
|
|
35
|
+
const { countryIdForCode, hasTimeline, timelineEvents, timelineSpec } = timeline
|
|
36
|
+
const { countries: timelineCountries, findEvents } = timelineEvents
|
|
36
37
|
const { itemSheet: platformSheet } = useExhibitionSheet(data)
|
|
37
38
|
|
|
38
39
|
function chipClass(record) {
|
|
@@ -50,11 +51,11 @@ export function useExhibitionItemDetail(data, timeline) {
|
|
|
50
51
|
.filter((d) => d && dynastyTr(d, language).history)
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
// The country the "Timeline for this item" popout opens on: the
|
|
54
|
-
//
|
|
55
|
-
function
|
|
56
|
-
for (const
|
|
57
|
-
if (countryIdForCode(
|
|
54
|
+
// The country the "Timeline for this item" popout opens on: the picker's
|
|
55
|
+
// entry for the record's own country.
|
|
56
|
+
function countryOf(countryId) {
|
|
57
|
+
for (const { value } of timelineCountries.value) {
|
|
58
|
+
if (countryIdForCode(value) === countryId) return value
|
|
58
59
|
}
|
|
59
60
|
return null
|
|
60
61
|
}
|
|
@@ -63,9 +64,9 @@ export function useExhibitionItemDetail(data, timeline) {
|
|
|
63
64
|
// the country and the period, under the keys the results page's controls
|
|
64
65
|
// read — never a key it has no control for (a local timeline has no
|
|
65
66
|
// country), which it would ignore (inventory-app#2022).
|
|
66
|
-
function periodSearch(
|
|
67
|
+
function periodSearch(country, range) {
|
|
67
68
|
const keys = new Set((timelineSpec.value?.controls ?? []).map((control) => control.key))
|
|
68
|
-
const query = { country
|
|
69
|
+
const query = { country, begin: range[0], end: range[1] }
|
|
69
70
|
return { name: 'timeline-results', query: Object.fromEntries(Object.entries(query).filter(([key]) => keys.has(key))) }
|
|
70
71
|
}
|
|
71
72
|
|
|
@@ -94,14 +95,14 @@ export function useExhibitionItemDetail(data, timeline) {
|
|
|
94
95
|
|
|
95
96
|
related: {
|
|
96
97
|
...platformSheet.related,
|
|
97
|
-
title: '
|
|
98
|
-
description: '
|
|
98
|
+
title: 'record.related.title',
|
|
99
|
+
description: 'record.related.description',
|
|
99
100
|
outsideChip: (ref) => chipClass(ref),
|
|
100
101
|
notInPackageLabel: 'exhibition.results.notInThisExhibition',
|
|
101
|
-
artisticIntroductionLabel: '
|
|
102
|
-
databaseLabel: '
|
|
103
|
-
overallDatabase: { label: '
|
|
104
|
-
onDisplayIn: { linkPendingLabel: '
|
|
102
|
+
artisticIntroductionLabel: 'core.nav.artisticIntroduction',
|
|
103
|
+
databaseLabel: 'catalogue.search.relatedDatabase',
|
|
104
|
+
overallDatabase: { label: 'catalogue.search.overallDatabase', linkLabel: 'core.nav.overallDatabase' },
|
|
105
|
+
onDisplayIn: { linkPendingLabel: 'record.related.linkPending' },
|
|
105
106
|
dynasties: (record, language) => ({
|
|
106
107
|
records: dynastiesWithHistory(record, language),
|
|
107
108
|
tr: (d) => dynastyTr(d, language),
|
|
@@ -114,10 +115,10 @@ export function useExhibitionItemDetail(data, timeline) {
|
|
|
114
115
|
const range = roundOutward(record.start_date, record.end_date)
|
|
115
116
|
if (range[0] == null) return null
|
|
116
117
|
return {
|
|
117
|
-
heading: '
|
|
118
|
-
countries: timelineCountries.value.map((
|
|
119
|
-
defaultCountry: () =>
|
|
120
|
-
events: (
|
|
118
|
+
heading: 'core.section.timeline',
|
|
119
|
+
countries: timelineCountries.value.map((c) => ({ value: c.value, label: c.label ?? ctx.t('timeline.form.allCountries') })),
|
|
120
|
+
defaultCountry: () => countryOf(record.country_id) ?? 'all',
|
|
121
|
+
events: (country) => findEvents({ country, begin: range[0], end: range[1] }),
|
|
121
122
|
range,
|
|
122
123
|
era: (year) => eraLabel(year, ctx.t),
|
|
123
124
|
searchTo: periodSearch,
|
|
@@ -113,7 +113,7 @@ export function useExhibitionSheet(data, config = {}) {
|
|
|
113
113
|
related: {
|
|
114
114
|
variant: 'grid',
|
|
115
115
|
heading: 'exhibition.related.objects',
|
|
116
|
-
actionLabel: '
|
|
116
|
+
actionLabel: 'catalogue.results.seeDatabaseEntry',
|
|
117
117
|
record: ({ record: other, justification }) => ({
|
|
118
118
|
id: other.id,
|
|
119
119
|
image: other.images?.[0]?.url ?? '',
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { computed } from 'vue'
|
|
2
2
|
import { eraLabel, roundOutward, yearBucketsFromRange } from '../../catalogue/dates.js'
|
|
3
|
+
import { useTimelineEvents } from '../../catalogue/timeline.js'
|
|
3
4
|
import { PAGE_SIZE } from '../shared.js'
|
|
4
5
|
|
|
5
6
|
export { eraLabel, roundOutward }
|
|
@@ -9,13 +10,13 @@ export { eraLabel, roundOutward }
|
|
|
9
10
|
* `PAGE_SIZE`/`tile` its {@link import('./useExhibitionCollection.js').useExhibitionCollection}
|
|
10
11
|
* already declared.
|
|
11
12
|
*
|
|
12
|
-
* Reads `data.exhibition`, `data.timelines`, `data.
|
|
13
|
-
* `data.
|
|
14
|
-
* `
|
|
13
|
+
* Reads `data.exhibition`, `data.timelines`, `data.items`, `data.tr`,
|
|
14
|
+
* `data.defaultLang`, `data.countryIdForCode`, `data.countryLabel`.
|
|
15
|
+
* `config` carries nothing today.
|
|
15
16
|
*/
|
|
16
17
|
export function useExhibitionTimeline(data, collection, config = {}) {
|
|
17
18
|
void config
|
|
18
|
-
const { exhibition, timelines,
|
|
19
|
+
const { exhibition, timelines, items, tr, defaultLang, countryIdForCode, countryLabel } = data
|
|
19
20
|
const { tile } = collection
|
|
20
21
|
|
|
21
22
|
// ── Which chronology this site's Timeline section shows ─────────────────
|
|
@@ -33,13 +34,8 @@ export function useExhibitionTimeline(data, collection, config = {}) {
|
|
|
33
34
|
// full — every DXA site gets the worldwide merge whatever its flags say —
|
|
34
35
|
// possibly with no `thg_local` row at all. The flags gate NAVIGATION, not
|
|
35
36
|
// data.
|
|
36
|
-
//
|
|
37
|
-
// The `thg_local` row has no `country_id`, which is exactly why it must
|
|
38
|
-
// be separated by `source` rather than left to the country filter: on
|
|
39
|
-
// "All Countries" it would otherwise be interleaved into the worldwide
|
|
40
|
-
// list.
|
|
41
37
|
const localTimeline = computed(
|
|
42
|
-
() => timelines.value.find((t) => t.source === 'thg_local') ?? null,
|
|
38
|
+
() => (timelines.value ?? []).find((t) => t.source === 'thg_local') ?? null,
|
|
43
39
|
)
|
|
44
40
|
|
|
45
41
|
/** True when the Timeline section is the exhibition's own chronology. */
|
|
@@ -61,180 +57,39 @@ export function useExhibitionTimeline(data, collection, config = {}) {
|
|
|
61
57
|
() => Boolean(exhibition.value?.has_timeline || exhibition.value?.has_country_timeline),
|
|
62
58
|
)
|
|
63
59
|
|
|
64
|
-
/** The events this site's manual Timeline reads (ItemSheet's own popout). */
|
|
65
|
-
const eventPool = computed(() => {
|
|
66
|
-
const local = localTimeline.value
|
|
67
|
-
if (usesLocalTimeline.value) {
|
|
68
|
-
return timelineEvents.value.filter((e) => e.timeline_id === local.id)
|
|
69
|
-
}
|
|
70
|
-
if (!local) return timelineEvents.value
|
|
71
|
-
return timelineEvents.value.filter((e) => e.timeline_id !== local.id)
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
// The global country timeline, served by legacy `/v2/events`. It is
|
|
75
|
-
// country-scoped and project-independent, and also a MERGE of two
|
|
76
|
-
// chronologies rather than one table: legacy's `App\MWNF\DAO\v2\Events`
|
|
77
|
-
// unions `mwnf3.hcr` (the Discover Islamic Art country chronologies) with
|
|
78
|
-
// `mwnf3_sharing_history.sh_hcr` restricted to exhibition 2, "Political
|
|
79
|
-
// Context", and sorts the result by year. The package mirrors that: each
|
|
80
|
-
// row of `timelines.json` is tagged `source: 'mwnf3' | 'sharing_history'`,
|
|
81
|
-
// and `timeline_events.json` keys every event by `country_id`.
|
|
82
|
-
//
|
|
83
|
-
// A country can be served by BOTH sources, so anything user-facing must
|
|
84
|
-
// key on the country and never on the timeline row: `findEvents` filters
|
|
85
|
-
// on `country_id`, which is what merges the two chronologies into one
|
|
86
|
-
// year-ordered list the way legacy did.
|
|
87
|
-
//
|
|
88
|
-
// Names and legacy codes both come from countries.json. The exporter
|
|
89
|
-
// scopes that file to "member item countries ∪ their holders' countries ∪
|
|
90
|
-
// the global timeline's countries", so it covers every timeline country —
|
|
91
|
-
// the `Intl.DisplayNames` fallback below is therefore dead code for them,
|
|
92
|
-
// and is kept only so a regressed package degrades to a rendered ISO code
|
|
93
|
-
// rather than a raw id. The `code` countries.json ships is the country's
|
|
94
|
-
// `backward_compatibility`, which is exactly the code legacy's own
|
|
95
|
-
// timeline URLs used, including the ones that are not ISO 3166-1
|
|
96
|
-
// alpha-2 (`uk`, `pa`, `qt`, `rm`, `sb`, `ua`, …). Those are the reason
|
|
97
|
-
// the fallback must stay unreachable rather than merely rare: read as
|
|
98
|
-
// ISO, `ua` is Ukraine and `sb` is the Solomon Islands, where legacy
|
|
99
|
-
// means the UAE and Serbia. Only countries.json can name them correctly.
|
|
100
|
-
const regionNames = (() => {
|
|
101
|
-
try {
|
|
102
|
-
return new Intl.DisplayNames(['en'], { type: 'region' })
|
|
103
|
-
} catch {
|
|
104
|
-
return null
|
|
105
|
-
}
|
|
106
|
-
})()
|
|
107
|
-
|
|
108
|
-
// Two legacy codes are not ISO 3166-1 alpha-2.
|
|
109
|
-
const LEGACY_TO_ISO = { uk: 'GB', pa: 'PS' }
|
|
110
|
-
|
|
111
|
-
// The set of inventory country ids present in countries.json. Ids and
|
|
112
|
-
// legacy codes are distinct — legacy codes are backward_compatibility
|
|
113
|
-
// values, while ids are the canonical keys in the inventory system.
|
|
114
|
-
const countryIdSet = computed(
|
|
115
|
-
() => new Set(countries.value.map((c) => c.id)),
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
// A lookup, not a parse. The fallback exists only for the
|
|
119
|
-
// regressed-package case described above, and it must agree with
|
|
120
|
-
// GLOBAL_TIMELINE_LIKE_PATTERNS in the exporter's timeline exporter: the
|
|
121
|
-
// country sits after the literal `country` segment in BOTH keyspaces
|
|
122
|
-
// (`mwnf3:hcr:country:<cc>` and
|
|
123
|
-
// `mwnf3_sharing_history:sh_hcr:country:<cc>:exhibition:2`), and it is
|
|
124
|
-
// the last segment in only the first — taking the last one yields `2` on
|
|
125
|
-
// the second.
|
|
126
|
-
function legacyCodeOf(timeline) {
|
|
127
|
-
const fromPackage = countryById.value.get(timeline.country_id)?.code
|
|
128
|
-
if (fromPackage) return fromPackage
|
|
129
|
-
const parts = (timeline.backward_compatibility ?? '').split(':')
|
|
130
|
-
const at = parts.indexOf('country')
|
|
131
|
-
return (at >= 0 ? parts[at + 1] : null) || timeline.country_id
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function nameFor(timeline) {
|
|
135
|
-
const fromPackage = countries.value.some((c) => c.id === timeline.country_id)
|
|
136
|
-
? labelOf('countries', timeline.country_id)
|
|
137
|
-
: null
|
|
138
|
-
if (fromPackage) return fromPackage
|
|
139
|
-
const legacy = legacyCodeOf(timeline)
|
|
140
|
-
const iso = LEGACY_TO_ISO[legacy] ?? String(legacy).toUpperCase()
|
|
141
|
-
try {
|
|
142
|
-
return regionNames?.of(iso) ?? iso
|
|
143
|
-
} catch {
|
|
144
|
-
return iso
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Countries that actually have a chronology, alphabetized, "All" first.
|
|
150
|
-
*
|
|
151
|
-
* One entry per COUNTRY, not per timeline row: a country served by BOTH
|
|
152
|
-
* `mwnf3` and `sharing_history` has two rows, and would otherwise appear
|
|
153
|
-
* twice in every country picker on the site. Only the item sheet's own
|
|
154
|
-
* manual "Timeline for this item" picker reads this now — `timelineSpec`
|
|
155
|
-
* below leaves the picker to viewer-core's `useTimelineEvents`.
|
|
156
|
-
*/
|
|
157
|
-
const timelineCountries = computed(() => {
|
|
158
|
-
// No picker at all when the section is the exhibition's own chronology
|
|
159
|
-
// — legacy hides the select on `countriesAvailable === false`.
|
|
160
|
-
if (usesLocalTimeline.value) return []
|
|
161
|
-
const byCountry = new Map()
|
|
162
|
-
for (const timeline of timelines.value) {
|
|
163
|
-
if (!timeline.country_id || byCountry.has(timeline.country_id)) continue
|
|
164
|
-
byCountry.set(timeline.country_id, [legacyCodeOf(timeline), nameFor(timeline)])
|
|
165
|
-
}
|
|
166
|
-
const rows = [...byCountry.values()].sort((a, b) => a[1].localeCompare(b[1]))
|
|
167
|
-
return [['all', 'All Countries'], ...rows]
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
/** Display name for an event's country — also `timelineSpec`'s `countryLabel`. */
|
|
171
|
-
function timelineCountryName(countryId) {
|
|
172
|
-
if (countries.value.some((c) => c.id === countryId)) return labelOf('countries', countryId)
|
|
173
|
-
const timeline = timelines.value.find((t) => t.country_id === countryId)
|
|
174
|
-
return timeline ? nameFor(timeline) : countryId
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Legacy 2-letter code → the inventory country id the events are keyed
|
|
179
|
-
* by. A country served by both chronologies has two rows carrying the
|
|
180
|
-
* same code and the same `country_id`, so either row answers. Idempotent
|
|
181
|
-
* on an id already: `TimelineResultsView`'s own country picker offers
|
|
182
|
-
* ids, so this must resolve BOTH the legacy code a bookmarked or
|
|
183
|
-
* cross-page link carries and the id the picker itself round-trips.
|
|
184
|
-
*/
|
|
185
|
-
function countryIdForCode(code) {
|
|
186
|
-
if (!code || code === 'all') return null
|
|
187
|
-
if (countryIdSet.value.has(code)) return code
|
|
188
|
-
if (countryById.value.has(code)) return code
|
|
189
|
-
const timeline = timelines.value.find((t) => legacyCodeOf(t) === code)
|
|
190
|
-
if (timeline) return timeline.country_id
|
|
191
|
-
// Countries with no chronology still reach here from the collection
|
|
192
|
-
// page's "Timeline for this Search" link, which uses countries.json's
|
|
193
|
-
// own codes.
|
|
194
|
-
return countries.value.find((c) => c.code === code)?.id ?? null
|
|
195
|
-
}
|
|
196
|
-
|
|
197
60
|
const eventsTr = (id) => tr('timeline_events', id, defaultLang)
|
|
198
61
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
})
|
|
220
|
-
.map((e) => ({
|
|
221
|
-
...e,
|
|
222
|
-
countryName: timelineCountryName(e.country_id),
|
|
223
|
-
text: eventsTr(e.id),
|
|
224
|
-
}))
|
|
225
|
-
.sort((a, b) => (a.year_from - b.year_from) || (a.display_order ?? 0) - (b.display_order ?? 0))
|
|
62
|
+
// ── The events ───────────────────────────────────────────────────────
|
|
63
|
+
//
|
|
64
|
+
// viewer-core's `useTimelineEvents` is the one engine over both
|
|
65
|
+
// chronologies: the worldwide country merge (`scope: 'country'`, which
|
|
66
|
+
// keeps the `thg_local` row out of it by `source`, since that row has no
|
|
67
|
+
// country to filter on) and the exhibition's own narrative one
|
|
68
|
+
// (`scope: 'local'`). The exhibition's flags pick between them once the
|
|
69
|
+
// exhibition record has loaded, so both are built and `timelineEvents`
|
|
70
|
+
// reads the one in force. The item page's "Timeline for this item" popout
|
|
71
|
+
// reads it; the Timeline pages build their own from `timelineSpec` below.
|
|
72
|
+
const engine = { countryLabel, countryIdForCode, tr: eventsTr }
|
|
73
|
+
const countryEvents = useTimelineEvents({ scope: 'country', ...engine })
|
|
74
|
+
const localEvents = useTimelineEvents({ scope: 'local', ...engine })
|
|
75
|
+
const inForce = () => (usesLocalTimeline.value ? localEvents : countryEvents)
|
|
76
|
+
|
|
77
|
+
const timelineEvents = {
|
|
78
|
+
countries: computed(() => inForce().countries.value),
|
|
79
|
+
yearRange: computed(() => inForce().yearRange.value),
|
|
80
|
+
yearBuckets: (t) => inForce().yearBuckets(t),
|
|
81
|
+
findEvents: (filters) => inForce().findEvents(filters),
|
|
226
82
|
}
|
|
227
83
|
|
|
228
84
|
// ── The Timeline entrance/results spec ───────────────────────────────
|
|
229
85
|
//
|
|
230
|
-
// Feeds `TimelineResultsView`: the engine
|
|
231
|
-
//
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
//
|
|
235
|
-
//
|
|
236
|
-
//
|
|
237
|
-
// counts as "in this period") is the site's, not the package's.
|
|
86
|
+
// Feeds `TimelineResultsView`: the engine owns the country/local axis,
|
|
87
|
+
// the year buckets and the pagination; an exhibition supplies only what
|
|
88
|
+
// is its own — the local/country scope switch and the controls it drives
|
|
89
|
+
// (no country picker when the section is the exhibition's own
|
|
90
|
+
// chronology), and the "See Gallery" join against member items, which
|
|
91
|
+
// stays here because an item's own shape (what counts as "in this
|
|
92
|
+
// period") is the site's, not the package's.
|
|
238
93
|
|
|
239
94
|
function yearOptions({ years, t }) {
|
|
240
95
|
return yearBucketsFromRange(years.min, years.max, t)
|
|
@@ -258,7 +113,7 @@ export function useExhibitionTimeline(data, collection, config = {}) {
|
|
|
258
113
|
|
|
259
114
|
const timelineSpec = computed(() => ({
|
|
260
115
|
scope: usesLocalTimeline.value ? 'local' : 'country',
|
|
261
|
-
countryLabel
|
|
116
|
+
countryLabel,
|
|
262
117
|
countryIdForCode,
|
|
263
118
|
tr: eventsTr,
|
|
264
119
|
controls: usesLocalTimeline.value
|
|
@@ -275,7 +130,7 @@ export function useExhibitionTimeline(data, collection, config = {}) {
|
|
|
275
130
|
// results spec's own `begin`/`end` (viewer-layout's `TimelineResultsView`
|
|
276
131
|
// renders those controls by that name, not `start`/`end`), so the legacy
|
|
277
132
|
// `/timeline-gallery/:country/:start/:end/:page` redirect is renamed to
|
|
278
|
-
// match on the way in
|
|
133
|
+
// match on the way in.
|
|
279
134
|
const timelineGallerySpec = {
|
|
280
135
|
entity: 'items',
|
|
281
136
|
keys: ['country', 'begin', 'end'],
|
|
@@ -289,14 +144,14 @@ export function useExhibitionTimeline(data, collection, config = {}) {
|
|
|
289
144
|
pageSize: PAGE_SIZE,
|
|
290
145
|
variant: 'grid',
|
|
291
146
|
recordRoute: 'item',
|
|
292
|
-
actionLabel: '
|
|
147
|
+
actionLabel: 'catalogue.results.seeDatabaseEntry',
|
|
293
148
|
empty: 'exhibition.results.noObjectsInPeriod',
|
|
294
149
|
record: (item, { t }) => tile(item, t),
|
|
295
150
|
summary: ({ filters, pageInfo, t }) => [
|
|
296
151
|
{
|
|
297
152
|
label: t('timeline.results.galleryHeading'),
|
|
298
153
|
value: filters.country
|
|
299
|
-
?
|
|
154
|
+
? countryLabel(countryIdForCode(filters.country))
|
|
300
155
|
: t('timeline.form.allCountries'),
|
|
301
156
|
},
|
|
302
157
|
{ count: pageInfo.total, value: t('catalogue.results.objects') },
|
|
@@ -304,7 +159,7 @@ export function useExhibitionTimeline(data, collection, config = {}) {
|
|
|
304
159
|
}
|
|
305
160
|
|
|
306
161
|
return {
|
|
307
|
-
usesLocalTimeline, hasTimeline,
|
|
308
|
-
|
|
162
|
+
usesLocalTimeline, hasTimeline, countryLabel, countryIdForCode,
|
|
163
|
+
timelineEvents, timelineSpec, timelineGallerySpec,
|
|
309
164
|
}
|
|
310
165
|
}
|
|
@@ -23,23 +23,20 @@ export { DATE_MODE, FACET_CATEGORIES, FACET_LABEL_KEYS, PAGE_SIZE, useFacetLabel
|
|
|
23
23
|
* The gallery's collection spec, over its own data layer's `data` (the
|
|
24
24
|
* return value of {@link import('./useGalleryData.js').useGalleryData}).
|
|
25
25
|
*
|
|
26
|
-
* Reads `data.
|
|
27
|
-
* `data.
|
|
28
|
-
* `data.
|
|
26
|
+
* Reads `data.tags`, `data.countryById`, `data.tagById`, `data.labelOf`,
|
|
27
|
+
* `data.itemRoute`, `data.tr`, `data.defaultLang`, `data.mdInline`,
|
|
28
|
+
* `data.manifest` and `data.countryIdForCode`. `config` carries nothing
|
|
29
|
+
* today.
|
|
29
30
|
*/
|
|
30
31
|
export function useGalleryCollection(data, config = {}) {
|
|
31
32
|
void config
|
|
32
|
-
const {
|
|
33
|
+
const { tags, countryById, tagById, labelOf, itemRoute, tr, defaultLang, mdInline, manifest, countryIdForCode } = data
|
|
33
34
|
|
|
34
35
|
// ── What the URL carries ─────────────────────────────────────────────
|
|
35
36
|
//
|
|
36
|
-
// The legacy 2-letter country code for `country
|
|
37
|
-
//
|
|
38
|
-
// resolves now.
|
|
39
|
-
|
|
40
|
-
function countryIdForCode(code) {
|
|
41
|
-
return code ? (countries.value ?? []).find((c) => c.code === code)?.id ?? null : null
|
|
42
|
-
}
|
|
37
|
+
// The legacy 2-letter country code for `country` (the data layer's
|
|
38
|
+
// `countryIdForCode`), the legacy tag id for a facet: the values legacy's
|
|
39
|
+
// URLs carried, so a link shared then still resolves now.
|
|
43
40
|
|
|
44
41
|
function tagIdForLegacy(legacyId) {
|
|
45
42
|
return legacyId ? (tags.value ?? []).find((t) => t.legacy_tag_id === legacyId)?.id ?? null : null
|
|
@@ -156,14 +153,14 @@ export function useGalleryCollection(data, config = {}) {
|
|
|
156
153
|
pageSize: PAGE_SIZE,
|
|
157
154
|
variant: 'grid',
|
|
158
155
|
recordRoute: 'item',
|
|
159
|
-
actionLabel: '
|
|
156
|
+
actionLabel: 'catalogue.results.seeDatabaseEntry',
|
|
160
157
|
empty: 'catalogue.results.noResults',
|
|
161
158
|
pagination: { jump: true },
|
|
162
159
|
|
|
163
160
|
record: (item, { t }) => tile(item, t),
|
|
164
161
|
|
|
165
162
|
summary: ({ filters, pageInfo, t, total }) => [
|
|
166
|
-
{ label: t('
|
|
163
|
+
{ label: t('core.section.collection'), value: filterSummary(filters, t) },
|
|
167
164
|
{ count: pageInfo.total, value: `${t('catalogue.results.outOf')} ${total} ${t('catalogue.results.objects')}` },
|
|
168
165
|
],
|
|
169
166
|
}
|
|
@@ -3,6 +3,7 @@ import { mediaUrl } from '../../siteConfig.js'
|
|
|
3
3
|
import { useCatalogueData } from '../../composables/useCatalogueData.js'
|
|
4
4
|
import { useDataPackage } from '../../composables/useDataPackage.js'
|
|
5
5
|
import { itemFromUidPath as legacyItemFromUidPath, partnerFromKey as legacyPartnerFromKey } from '../../legacy/index.js'
|
|
6
|
+
import { useLegacyCountryCodes } from '../countryCodes.js'
|
|
6
7
|
|
|
7
8
|
// The data layer every DXA gallery site (carpets, amulets, …) wrote for
|
|
8
9
|
// itself: the entities a gallery reads, the lookup maps over them, its
|
|
@@ -62,6 +63,10 @@ export function useGalleryData(config = {}) {
|
|
|
62
63
|
const timelineById = catalogue.index('timelines')
|
|
63
64
|
const languageByCode = catalogue.index('languages', 'code')
|
|
64
65
|
|
|
66
|
+
// The legacy country code both ways, the one table the collection and the
|
|
67
|
+
// timeline read.
|
|
68
|
+
const { countryIdForCode, countryLabel } = useLegacyCountryCodes({ countries, countryById, timelines, labelOf })
|
|
69
|
+
|
|
65
70
|
// ── Routes ─────────────────────────────────────────────────────────────
|
|
66
71
|
//
|
|
67
72
|
// The canonical routes carry the package id; the language never travels
|
|
@@ -137,6 +142,7 @@ export function useGalleryData(config = {}) {
|
|
|
137
142
|
availableLanguages, loadTranslations, translations,
|
|
138
143
|
gallery, items, tags, partners, countries, languages, dynasties, glossary, timelines, timelineEvents,
|
|
139
144
|
itemById, partnerById, countryById, tagById, dynastyById, timelineById, languageByCode,
|
|
145
|
+
countryIdForCode, countryLabel,
|
|
140
146
|
itemRoute, partnerRoute, partnerObjectsRoute, itemFromUidPath, partnerFromKey,
|
|
141
147
|
chromeImage, siblingGalleries, siblingUrl, pickSiblings,
|
|
142
148
|
}
|
|
@@ -61,7 +61,7 @@ export function useGalleryItemDetail(data, timeline) {
|
|
|
61
61
|
const [from, to] = roundOutward(record.start_date, record.end_date)
|
|
62
62
|
if (from == null) return null
|
|
63
63
|
return {
|
|
64
|
-
heading: '
|
|
64
|
+
heading: 'core.section.timeline',
|
|
65
65
|
countries: timelineCountries.value.map((c) => ({ value: c.value, label: c.label ?? ctx.t('timeline.form.allCountries') })),
|
|
66
66
|
defaultCountry: () => {
|
|
67
67
|
for (const { value: code } of timelineCountries.value) {
|
|
@@ -91,16 +91,16 @@ export function useGalleryItemDetail(data, timeline) {
|
|
|
91
91
|
},
|
|
92
92
|
related: {
|
|
93
93
|
...platformSheet.related,
|
|
94
|
-
title: '
|
|
95
|
-
description: '
|
|
94
|
+
title: 'record.related.title',
|
|
95
|
+
description: 'record.related.description',
|
|
96
96
|
notInPackageLabel: 'gallery.results.notInThisGallery',
|
|
97
97
|
// An outside `related_items` reference carries its `project_id`
|
|
98
98
|
// (inventory-app#1807), so it reads the same `projectColors` map.
|
|
99
99
|
outsideChip: chipClass,
|
|
100
|
-
artisticIntroductionLabel: '
|
|
101
|
-
databaseLabel: '
|
|
102
|
-
overallDatabase: { label: '
|
|
103
|
-
onDisplayIn: { linkPendingLabel: '
|
|
100
|
+
artisticIntroductionLabel: 'core.nav.artisticIntroduction',
|
|
101
|
+
databaseLabel: 'catalogue.search.relatedDatabase',
|
|
102
|
+
overallDatabase: { label: 'catalogue.search.overallDatabase', linkLabel: 'core.nav.overallDatabase' },
|
|
103
|
+
onDisplayIn: { linkPendingLabel: 'record.related.linkPending' },
|
|
104
104
|
dynasties: (record, language) => itemDynasties(record, language),
|
|
105
105
|
timeline: itemTimeline,
|
|
106
106
|
},
|
|
@@ -66,7 +66,7 @@ export function useGalleryPartner(data, collection, config = {}) {
|
|
|
66
66
|
pageSize: PAGE_SIZE,
|
|
67
67
|
variant: 'grid',
|
|
68
68
|
recordRoute: 'item',
|
|
69
|
-
actionLabel: '
|
|
69
|
+
actionLabel: 'catalogue.results.seeDatabaseEntry',
|
|
70
70
|
empty: 'gallery.partner.noObjects',
|
|
71
71
|
pagination: { jump: true },
|
|
72
72
|
record: (item, { t }) => tile(item, t),
|
|
@@ -105,7 +105,7 @@ export function useGallerySheet(data, config = {}) {
|
|
|
105
105
|
related: {
|
|
106
106
|
variant: 'grid',
|
|
107
107
|
heading: 'record.related.items',
|
|
108
|
-
actionLabel: '
|
|
108
|
+
actionLabel: 'catalogue.results.seeDatabaseEntry',
|
|
109
109
|
record: ({ record: other, justification }) => ({
|
|
110
110
|
id: other.id,
|
|
111
111
|
image: other.images?.[0]?.url ?? '',
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { computed } from 'vue'
|
|
2
1
|
import { dateRange, eraLabel, roundOutward, yearBucketsFromRange } from '../../catalogue/dates.js'
|
|
3
2
|
import { useTimelineEvents } from '../../catalogue/timeline.js'
|
|
4
3
|
import { PAGE_SIZE } from '../shared.js'
|
|
@@ -10,13 +9,13 @@ export { eraLabel, roundOutward }
|
|
|
10
9
|
* `PAGE_SIZE`/`tile` its {@link import('./useGalleryCollection.js').useGalleryCollection}
|
|
11
10
|
* already declared.
|
|
12
11
|
*
|
|
13
|
-
* Reads `data.
|
|
14
|
-
* `data.
|
|
15
|
-
* `
|
|
12
|
+
* Reads `data.labelOf`, `data.items`, `data.tr`, `data.loadTranslations`,
|
|
13
|
+
* `data.defaultLang`, `data.countryIdForCode`, `data.countryLabel`.
|
|
14
|
+
* `config` carries nothing today.
|
|
16
15
|
*/
|
|
17
16
|
export function useGalleryTimeline(data, collection, config = {}) {
|
|
18
17
|
void config
|
|
19
|
-
const {
|
|
18
|
+
const { labelOf, items, tr, loadTranslations, defaultLang, countryIdForCode, countryLabel } = data
|
|
20
19
|
const { tile } = collection
|
|
21
20
|
|
|
22
21
|
// `TimelineResultsView` takes `tr` as a plain callback rather than an
|
|
@@ -28,96 +27,11 @@ export function useGalleryTimeline(data, collection, config = {}) {
|
|
|
28
27
|
|
|
29
28
|
// The merge itself — the worldwide country chronology, `mwnf3.hcr`
|
|
30
29
|
// combined with Sharing History's exhibition-2 rows the way legacy's
|
|
31
|
-
// `/v2/events` served it — is viewer-core's `useTimelineEvents
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
// Names and legacy codes both come from countries.json. The exporter
|
|
37
|
-
// scopes that file to "member item countries ∪ their holders' countries ∪
|
|
38
|
-
// the global timeline's countries", so it covers every timeline country —
|
|
39
|
-
// the `Intl.DisplayNames` fallback below is therefore dead code for them,
|
|
40
|
-
// and is kept only so a regressed package degrades to a rendered ISO code
|
|
41
|
-
// rather than a raw id. The `code` countries.json ships is the country's
|
|
42
|
-
// `backward_compatibility`, which is exactly the code legacy's own
|
|
43
|
-
// timeline URLs used, including the ones that are not ISO 3166-1
|
|
44
|
-
// alpha-2 (`uk`, `pa`, `qt`, `rm`, `sb`, `ua`, …). Those are the reason
|
|
45
|
-
// the fallback must stay unreachable rather than merely rare: read as
|
|
46
|
-
// ISO, `ua` is Ukraine and `sb` is the Solomon Islands, where legacy
|
|
47
|
-
// means the UAE and Serbia. Only countries.json can name them correctly.
|
|
48
|
-
const regionNames = (() => {
|
|
49
|
-
try {
|
|
50
|
-
return new Intl.DisplayNames(['en'], { type: 'region' })
|
|
51
|
-
} catch {
|
|
52
|
-
return null
|
|
53
|
-
}
|
|
54
|
-
})()
|
|
55
|
-
|
|
56
|
-
// Two legacy codes are not ISO 3166-1 alpha-2.
|
|
57
|
-
const LEGACY_TO_ISO = { uk: 'GB', pa: 'PS' }
|
|
58
|
-
|
|
59
|
-
// `TimelineResultsView`'s own country control (`countries` from
|
|
60
|
-
// viewer-core) writes the inventory id, not the legacy code
|
|
61
|
-
// (`?country=dza`, not `?country=dz`); `countryIdForCode` used to answer
|
|
62
|
-
// nothing for one, silently matching every event. Built once, next to
|
|
63
|
-
// `LEGACY_TO_ISO`, rather than inside the function it is read from.
|
|
64
|
-
const countryIdSet = computed(() => new Set(countries.value.map((c) => c.id)))
|
|
65
|
-
|
|
66
|
-
// A lookup, not a parse. The fallback exists only for the
|
|
67
|
-
// regressed-package case described above, and it must agree with
|
|
68
|
-
// GLOBAL_TIMELINE_LIKE_PATTERNS in the exporter's timeline exporter: the
|
|
69
|
-
// country sits after the literal `country` segment in BOTH keyspaces
|
|
70
|
-
// (`mwnf3:hcr:country:<cc>` and
|
|
71
|
-
// `mwnf3_sharing_history:sh_hcr:country:<cc>:exhibition:2`), and it is
|
|
72
|
-
// the last segment in only the first — taking the last one yields `2` on
|
|
73
|
-
// the second.
|
|
74
|
-
function legacyCodeOf(timeline) {
|
|
75
|
-
const fromPackage = countryById.value.get(timeline.country_id)?.code
|
|
76
|
-
if (fromPackage) return fromPackage
|
|
77
|
-
const parts = (timeline.backward_compatibility ?? '').split(':')
|
|
78
|
-
const at = parts.indexOf('country')
|
|
79
|
-
return (at >= 0 ? parts[at + 1] : null) || timeline.country_id
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function nameFor(timeline) {
|
|
83
|
-
const fromPackage = countries.value.some((c) => c.id === timeline.country_id)
|
|
84
|
-
? labelOf('countries', timeline.country_id)
|
|
85
|
-
: null
|
|
86
|
-
if (fromPackage) return fromPackage
|
|
87
|
-
const legacy = legacyCodeOf(timeline)
|
|
88
|
-
const iso = LEGACY_TO_ISO[legacy] ?? String(legacy).toUpperCase()
|
|
89
|
-
try {
|
|
90
|
-
return regionNames?.of(iso) ?? iso
|
|
91
|
-
} catch {
|
|
92
|
-
return iso
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/** Display name for a country the chronology carries, keyed by inventory id. */
|
|
97
|
-
function countryLabel(countryId) {
|
|
98
|
-
if (countries.value.some((c) => c.id === countryId)) return labelOf('countries', countryId)
|
|
99
|
-
const timeline = timelines.value.find((t) => t.country_id === countryId)
|
|
100
|
-
return timeline ? nameFor(timeline) : countryId
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Legacy 2-letter code → the inventory country id the events are keyed
|
|
105
|
-
* by. A country served by both chronologies has two rows carrying the
|
|
106
|
-
* same code and the same `country_id`, so either row answers.
|
|
107
|
-
*/
|
|
108
|
-
function countryIdForCode(code) {
|
|
109
|
-
if (!code || code === 'all') return null
|
|
110
|
-
// Already an id (the composed view's own control writes one): pass it
|
|
111
|
-
// through unchanged, before the legacy-code lookups below get a chance
|
|
112
|
-
// to find nothing and silently match every event.
|
|
113
|
-
if (countryIdSet.value.has(code)) return code
|
|
114
|
-
const timeline = timelines.value.find((t) => legacyCodeOf(t) === code)
|
|
115
|
-
if (timeline) return timeline.country_id
|
|
116
|
-
// Countries with no chronology still reach here from the collection
|
|
117
|
-
// page's "Timeline for this Search" link, which uses countries.json's
|
|
118
|
-
// own codes.
|
|
119
|
-
return countries.value.find((c) => c.code === code)?.id ?? null
|
|
120
|
-
}
|
|
30
|
+
// `/v2/events` served it — is viewer-core's `useTimelineEvents`, and the
|
|
31
|
+
// DXA legacy 2-letter country code a gallery's Timeline URLs are keyed on
|
|
32
|
+
// is the data layer's (`countryIdForCode`, `countryLabel`). What stays
|
|
33
|
+
// here is what is genuinely a gallery's own: the two specs the composed
|
|
34
|
+
// views render from.
|
|
121
35
|
|
|
122
36
|
/**
|
|
123
37
|
* The one merge engine every Timeline page on a gallery shares —
|
|
@@ -198,7 +112,7 @@ export function useGalleryTimeline(data, collection, config = {}) {
|
|
|
198
112
|
pageSize: PAGE_SIZE,
|
|
199
113
|
variant: 'grid',
|
|
200
114
|
recordRoute: 'item',
|
|
201
|
-
actionLabel: '
|
|
115
|
+
actionLabel: 'catalogue.results.seeDatabaseEntry',
|
|
202
116
|
empty: 'gallery.results.noObjectsInPeriod',
|
|
203
117
|
pagination: { jump: true },
|
|
204
118
|
|