@museumwnf/viewer-layout 2.13.0 → 2.15.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 CHANGED
@@ -13,7 +13,7 @@ full slot list: [`docs/designer-contract.md`](docs/designer-contract.md) (the
13
13
  four roles — components, tokens/CSS, site designer, translator — and the
14
14
  token → overrides → slot → own-view decision ladder) and
15
15
  [`docs/slot-catalogue.md`](docs/slot-catalogue.md) (every prop and slot of
16
- `PageShell`, `SiteShell` and the nine composed views, plus a one-line purpose
16
+ `PageShell`, `SiteShell` and the ten composed views, plus a one-line purpose
17
17
  for every `/content` component).
18
18
 
19
19
  ## Install
@@ -273,9 +273,15 @@ the application registered, without any router coupling here.
273
273
  | `DynastyPopout` | `dynasty` (record: `from_ah`/`to_ah`/`from_ad`/`to_ad`), `text` (its translation: `name`, `also_known_as`, `area`, `history`), `dir` | one dynasty, collapsed behind a native `<details>` toggle: name, also known as, area, AH/AD dates, history as Markdown |
274
274
  | `DynastyList` | `heading`, `dynasties`, `tr` (dynasty → its translation), `dir` | one `DynastyPopout` per dynasty of a record, `RelatedRecords`'s shape |
275
275
  | `TimelineEventList` | `events: [{ id, date, caption?, description? (block HTML), media?: [{ image, alt?, to? \| href?, caption? }], actions?: [{ label, to? \| href? }] }]` | one row per event; `caption`/`description` are rendered output, the same convention `RecordList`'s `name` follows; `#date`, `#caption`, `#media`, `#actions` slots (each given `{ event }`), `#empty` slot |
276
+ | `PictureGallery` | `pictures: [{ id, image, imageAlt?, name (inline HTML), imageCaption?, detail?, fields?: [{ label, value }], to (route \| null), backRelated?: [{ picture, reciprocalText }] }]`, `selectedId` (v-model), `seeItemEntry`/`unresolvedEntry`/`emptyEntry`/`addRelatedEntry`/`hideRelatedEntry` (entry names) | a DXA exhibition theme page's curated-picture side panel + thumbnail strip: the selected picture large, its fields, a link to the parent record (or the "not in this exhibition" message when `to` is `null`); a picture that is the target of some other picture's related link (`backRelated`) starts hidden behind an "Add related works" toggle |
277
+ | `PictureNarrative` | `picture` (the selected picture, `PictureGallery`'s shape plus `related: [{ picture, text }]` and `backRelated: [{ picture, reciprocalText }]`), `contextualDescription` (Markdown), `relatedHeadingEntry`/`reciprocalFallbackEntry` (entry names); emits `select(id)` | the same page's narrative body, below `EssayView`'s own prose: the selected picture's own curated text, then the pictures it relates to (forward) and, when one points at it, the one it is related to (backward) — either direction may cross a theme boundary, which is why the website resolves the target/source picture rather than this component |
278
+ | `TimelineLookup` | `info: { trigger? (entry, default `record.related.timelineForItem`), heading (entry), countries: [{ value, label }], defaultCountry?() => value, events(value) => [{ id?, year_from, text: { description } }], range: [from, to], era?(year) => string, searchTo(value, range) => to }` | the timeline popout `RecordSheetView`'s related block opens onto (see below): a trigger line, a country select, the events for the chosen country within the record's own date range (rendered through `md()`), and a "begin a full search" link — the DXA gallery/exhibition pair's shared shape, parameterized by the site's own timeline composable |
276
279
 
277
280
  The record contract `RecordList`, `RecordGrid` and `RelatedRecords` share:
278
281
  `{ id, image?, imageAlt?, name (inline HTML), meta: [string], badge?, href? | to? }`.
282
+ `PictureGallery` and `PictureNarrative` share a narrower one — a curated
283
+ picture is not a catalogue record (several can share one parent, or none) —
284
+ documented above and in [`docs/theme-components.md`](docs/theme-components.md).
279
285
 
280
286
  The texts they read — `core.action.apply`, `.reset`, `.close`,
281
287
  `core.pagination.*`, `catalogue.pagination.*`, `record.action.*ShortDescription`,
@@ -286,8 +292,13 @@ every bundle of `@museumwnf/viewer-i18n` from 1.7.0. `GlossaryTool` also reads
286
292
  (2.3.0); `DynastyPopout` reads `record.dynasty.heading` (2.3.0) and
287
293
  `sheet.field.alsoKnownAs`, `.area`, `.history`. `SourceCredit` reads
288
294
  `record.source.label` (2.5.0); the footer attribution (`SiteShell`) reads
289
- `record.source.rightsHolder` and `.termsOfUse` (2.5.0). Every other text is a
290
- prop.
295
+ `record.source.rightsHolder` and `.termsOfUse` (2.5.0). `PictureGallery`/
296
+ `PictureNarrative` default to `exhibition.theme.seeItemEntry`, `.recordNotInSite`,
297
+ `.additionalContent`, `.addRelatedWorks`, `.hideRelatedWorks` and
298
+ `exhibition.related.items`, `.reciprocal` — already in every bundle of
299
+ `@museumwnf/viewer-i18n`'s `exhibition` namespace, the same entries
300
+ the-use-of-colours-in-art's and water-in-islam's `Theme.vue` already read.
301
+ Every other text is a prop.
291
302
 
292
303
  ## Content classes
293
304
 
@@ -308,7 +319,7 @@ Building blocks copied from the legacy sites' own `site.css` files, available as
308
319
 
309
320
  ## Composed views
310
321
 
311
- Nine whole pages, made of the content components on viewer-core's
322
+ Ten whole pages, made of the content components on viewer-core's
312
323
  composables and driven by a declaration the website writes instead of a
313
324
  page. They are exported from `@museumwnf/viewer-layout/views` — and only from
314
325
  there: they read the records and the engine from `@museumwnf/viewer-core`
@@ -353,6 +364,7 @@ export default {
353
364
  | `HomeView` | `config.home` or the same as props: `title`, `intro` (Markdown), `cards: [{ title, description, action, to \| href }]`, `featured: { entity, heading, action, route, eyebrow, meta, seed }` — the pick is `useFeaturedRecord` | `before`, default, `after` |
354
365
  | `CatalogueResultsView` | `spec`: `entity`, `keys`, `facets` (viewer-core's facet spec), `facetScope: 'all' \| 'matching'`, `controls: [{ key, type: 'select' \| 'year' \| 'query' \| 'checkbox', label, placeholder, anyLabel, hideEmpty }]`, `filterMode: 'apply' \| 'immediate'`, `scope(record, filters)`, `match(record, filters)`, `narrow(list, filters, helpers)` (a rule over the whole list — a keyword index, say), `dates: { mode, begin, end }`, `sort`, `pageSize`, `variant: 'list' \| 'grid'`, `record(record, helpers)`, `recordRoute`, `summary(context)`, `title`, `filterTitle`, `empty`, `actionLabel`, `pagination` | `before`, `filters`, `actions`, `aside`, `empty`, `after` — each given `{ filters, active, apply, reset, goToPage, matching, pageInfo, options }`, enough to compose the panel in the aside or a second pagination |
355
366
  | `RecordView` | `spec` and `id`: `entity`, `translations`, `attribution`, `fields` (viewer-core's `sheetRows` spec, or a function of the context), `sections`, `layout`, `shortDescription`, `media(record, ctx)`, `mediaVariant`, `credits`, `workingNumber`, `citation: { project, permalink: true \| false \| string, heading } \| false` (the permalink is `sourceUrl(currentRoute)` — null, so no address, until the website declares `site.origin` — unless the spec disables it with `false` or overrides it with a string), `related: { variant, heading, record, route } \| false`, `back: { label, to \| href }`, `title(ctx)` | `header`, `before-sheet`, `after-sheet`, `aside`, `source` (under the citation; default `SourceCredit`), `related`, `after`, and one named after every `custom` or `link` row — each given `{ record, text, language, languages, select, dir, glossary, ready, attribution, t, tr }`; `related` also `records` (the rows) and `outside` (the related records the package does not carry) |
367
+ | `RecordSheetView` | Every `RecordView` prop (`spec`, `id`, `entity`) plus `dataGetter(id) => record \| null \| undefined` — a website's own language-subset record lookup (an exhibition's per-build `itemById.get`); omitted, every `id` `RecordView` itself finds is treated as present (the gallery shape); supplied and it reports `id` missing, `NotFoundView` renders before `RecordView` ever mounts (the exhibition shape's per-language-build 404), and it also narrows the related block's `records`/outside-reference split the same way. The DXA gallery/exhibition item sheet's own blocks, on top of `spec`: `sourceDatabase: { label?, chipClass(record, ctx), addToCollection: { label? } \| false } \| false` (the source-database line + `backward_compatibility` code + "add to my collection" link — `chipClass` is a site's own project→colour map, the name comes from `useProjects()`), `notice: { show(record, ctx), label } \| false` (the Explore-partner notice), `museum: { route(partnerId, ctx), label(partnerId, ctx) } \| false` (the holding-museum row — a site's own partner lookup; `route` returning null renders plain text, for a hidden partner), and on `spec.related`: `title`, `description` (entries, above the block), `notInPackageLabel`, `outsideChip(ref, ctx)` (an outside reference's chip — `TODO(#1727)`, no `project_id` on a stub yet), `artisticIntroductionLabel` (with `useProjects().links(record.project_id).artisticIntroductionUrl`), `databaseLabel` (ditto, `.relatedDatabaseUrl`), `overallDatabase: { label, linkLabel } \| false` (`mwnfLinks.overallDatabase`), `onDisplayIn: { linkPendingLabel } \| false` (`record.gallery_references`, by `kind`), `download: false` (default on, `record.action.download`/`.downloadPdf`, prints), `glossary: false` (default on, `GlossaryTool`), `dynasties(record, language, ctx) => { records, tr } \| null` (`DynastyList`), `media: false` (default on, `record.media`, a `SheetSection`), `timeline(record, ctx) => TimelineInfo \| null` (`TimelineLookup` — see `docs/slot-catalogue.md`) | Every `RecordView` slot, forwarded straight through by name, unmodified, **except** `before-sheet`/`museum`/`related`: each always has this view's own default content (above), which a website's own slot still replaces entirely — "a slot fills, the default content renders otherwise" |
356
368
  | `EssayView` | `spec` and `id`: `tree` (a `useCollectionTree` result, or `{ purpose \| rootId, childType?, entity?, order? }` / `{ themes: true \| 'themes', childType? }` for the view to build one), `entity` (the tree's items — also the tree's own translations entity when `tree` carries none of its own, a pre-built tree or a declarative form with no `entity`/`themes`; falls back to `'collections'`), `route` (a node's own page: a route name or `(node, ctx) => to \| href`), `heading(ctx)`, `placeholder` (a regex a synthesized title matches, falling back to the English title, then the internal name), `quote` / `body` (a field of the node's translation, a dotted path into it such as `'extra.intro_text'`, or a function `(ctx) => Markdown`; default `'quote'`/`'description'`, `false` to drop), `glossary`, `items: { of(node) => ids, caption(item, node, ctx) => override, meta(item, ctx) => [string] (the grid's caption lines), badge(item, ctx) => string, route }`, `panel: { variants(item, ctx) => [{ id, image, alt, caption: { title, justification, fields: [{ label (entry name), value }] } }], fields(item, node, ctx) => [{ label, value }] (the fallback when a variant carries no fields of its own) } \| false`, `navigation: 'tree' \| 'siblings' \| false` (`tree` crosses a branch boundary, `siblings` stays inside the parent), `breadcrumb`, `tabs: true \| 'siblings' \| 'children'` (the strip: `true`/`'siblings'` — the node's own siblings, the default; `'children'` — the node's own children, e.g. a theme's chapters), `about(node) => boolean \| { panel?, navigation? }` (essay only; a plain boolean drops both the picture panel and the navigation, an object keeps the named piece instead), `aboutKeeps: ['panel' \| 'navigation']` (the spec-wide equivalent, when every about page in this spec keeps the same piece), `numbering: 'roman' \| 'decimal' \| false` (counts a node among its true siblings — for a themes-package tree, whose `root` is `null`, that's every node whose own `tree.parents(id)` is also empty, so a themes tree numbers its top-level themes I, II, III); `previous`, `next`, `backTo`, `inThisTheme`, `seeAll` (entry names, defaulting to `exhibition.theme.previous`/`.next`/`.inThisTheme`/`.seeAllInTheme` and `record.action.backToResults`) | `header`, `before-body`, `after-body`, `panel`, `thumbnails`, `aside`, `justifications`, `navigation`, `after` (default `SourceCredit` — nothing until the website declares `site.origin`) — each given `{ node, text, language, tree, items, selected, select, selectedVariant, selectVariant, breadcrumb, previous, next, t, tr }` |
357
369
  | `LinkListView` | `spec`: `title` (entry), `groups: [{ heading (entry), links: [{ label, href \| to, note? }] }] \| (ctx) => groups` (`label`/`note` are Markdown, rendered inline through `mdInline`), `back: { label, to \| href } \| false`, `empty` (entry) | `before`, `group` (given `{ group }`; the default renders the heading and its links — replace it for a citation list that is not link-shaped), `after` |
358
370
  | `PartnerListView` | `spec`: `entity` (default `'partners'`), `scope(partner, ctx)` (a site's own axis — museum/institution, a curated project — filtered before grouping), `group: { tier: 'level' \| false, order: 'country' \| 'name' }` (over viewer-core's `groupByCountry`; `'name'`: one flat, alphabetical group, no country carries this today), `orderToggle` (an A-Z/Z-A control, mirrored in the query as `order`; every tier is always sorted by name, the toggle reverses the country order or the one flat list), `nested` (an associated partner rendered under its own parent through `partnerHierarchy`, instead of the flat tier column; one whose parent is missing or in another group stays flat), `label(countryId, ctx)` (a group's own country label, required when `group.order` is `'country'`), `record(partner, ctx) => { name, city, logo, count, route }` (the row), `route` (a route name or `(partner, ctx) => to \| href`, read by the default row only), `variant: 'accordion' \| 'open'`, `count` (the "Partners found: N" line), `associatedLabel`/`foundLabel`/`objectsLabel`/`sortAscendingLabel`/`sortDescendingLabel`/`empty`/`title` (entry names, overriding the shared `partner.*` defaults) | `before`, `group-heading` (given `{ group }`; the default renders `group.label`), `row` (given `{ group, partner, row }`, once per partner — main, associated and, when nested, a child row too), `after` — every slot also given `{ t, locale, groups, orderDir, toggleOrder }` |
@@ -411,6 +423,30 @@ except where noted:
411
423
  | islamicart/baroqueart `TimelineEntrance`/`TimelineResults` | the same controls, `event: (event, ctx) => ({ ..., actions: [{ label: 'timeline.action.viewItemsFromPeriod', to: itemsLink(event) }] })` for the per-event "View items from this period" link |
412
424
  | sharinghistory `TimelineResults` (no separate entrance page in legacy; `entrance: true` on the same spec shape covers it) | `scope: 'collection'`, `controls` adding `{ key: 'collection', label: 'sharinghistory.nav.timeline' }` fed by `collections: (ctx) => [{ value: 'pc', label: t('sharinghistory.nav.permanentCollection') }, ...exhibitions]`, `event(...)` composing the "Country \| Theme" `caption` and the image/item strip as `media` |
413
425
 
426
+ ## DXA family pages
427
+
428
+ `@museumwnf/viewer-layout/dxa` ships the gallery/exhibition thin pages
429
+ confirmed byte-identical within each DXA site pair on `origin/main` —
430
+ 11 gallery pages (from carpets/amulets), 9 exhibition pages (from
431
+ the-use-of-colours-in-art/water-in-islam) — plus `standardRoutes(family,
432
+ config)`, the RouteRecord factory a site spreads into `extraViews`:
433
+
434
+ ```js
435
+ import { standardRoutes } from '@museumwnf/viewer-layout/dxa'
436
+
437
+ extraViews: [
438
+ ...standardRoutes('gallery', { creditsBody: 'carpets.credits.body' }),
439
+ // + this gallery's own routes: home, item, collection entrance,
440
+ // timeline entrance, partners entrance
441
+ ]
442
+ ```
443
+
444
+ Route names and paths are pinned to what every live DXA site already
445
+ registers, so adopting the factory keeps every existing deep link (and
446
+ every `legacyRoutes` resolver targeting those names) working unmodified.
447
+ Full page-by-page prop/slot/entry table in
448
+ [`docs/slot-catalogue.md`](docs/slot-catalogue.md#dxa-family-pages-museumwnfviewer-layoutdxa).
449
+
414
450
  ## Theming
415
451
 
416
452
  Every color, font, spacing, radius comes from a `--mwnf-*` CSS custom property with a neutral fallback. Full list: [`tokens.reference.css`](src/tokens.reference.css) (also exported as `@museumwnf/viewer-layout/tokens.reference.css`) — copy it into your website as `theme/tokens.css` and set values.
@@ -0,0 +1,106 @@
1
+ import { g as e } from "./TimelineEventList-BmfLBaTT.js";
2
+ import { computed as t, createBlock as n, createCommentVNode as r, createElementBlock as i, createElementVNode as a, createTextVNode as o, openBlock as s, toDisplayString as c, withCtx as l } from "vue";
3
+ import { useRouter as u } from "vue-router";
4
+ //#region src/content/PartnerMap.vue
5
+ var d = {
6
+ key: 0,
7
+ class: "mwnf-partner-map"
8
+ }, f = { class: "mwnf-partner-map__title" }, p = ["src", "title"], m = { class: "mwnf-partner-map__link" }, h = ["href"], g = {
9
+ __name: "PartnerMap",
10
+ props: {
11
+ latitude: {
12
+ type: Number,
13
+ default: null
14
+ },
15
+ longitude: {
16
+ type: Number,
17
+ default: null
18
+ },
19
+ zoom: {
20
+ type: Number,
21
+ default: 15
22
+ },
23
+ mapTitleEntry: {
24
+ type: String,
25
+ default: "partner.map.map"
26
+ },
27
+ mapOfEntry: {
28
+ type: String,
29
+ default: "partner.map.mapOf"
30
+ },
31
+ openMapLinkEntry: {
32
+ type: String,
33
+ default: "partner.map.openInOpenStreetMap"
34
+ },
35
+ label: {
36
+ type: String,
37
+ default: ""
38
+ }
39
+ },
40
+ setup(e) {
41
+ let n = e, o = t(() => Number.isFinite(n.latitude) && Number.isFinite(n.longitude)), l = t(() => {
42
+ let e = 360 / 2 ** Math.max(1, Math.min(19, n.zoom || 15)), [t, r] = [n.latitude, n.longitude];
43
+ return [
44
+ r - e,
45
+ t - e / 2,
46
+ r + e,
47
+ t + e / 2
48
+ ].join("%2C");
49
+ }), u = t(() => `https://www.openstreetmap.org/export/embed.html?bbox=${l.value}&layer=mapnik&marker=${n.latitude}%2C${n.longitude}`), g = t(() => `https://www.openstreetmap.org/?mlat=${n.latitude}&mlon=${n.longitude}#map=${n.zoom || 15}/${n.latitude}/${n.longitude}`);
50
+ return (t, n) => o.value ? (s(), i("div", d, [
51
+ a("p", f, c(t.$t(e.mapTitleEntry)), 1),
52
+ a("iframe", {
53
+ src: u.value,
54
+ title: e.label ? t.$t(e.mapOfEntry, { label: e.label }) : t.$t(e.mapTitleEntry),
55
+ loading: "lazy",
56
+ referrerpolicy: "no-referrer",
57
+ class: "mwnf-partner-map__embed"
58
+ }, null, 8, p),
59
+ a("p", m, [a("a", {
60
+ href: g.value,
61
+ target: "_blank",
62
+ rel: "noopener"
63
+ }, "↗ " + c(t.$t(e.openMapLinkEntry)), 9, h)])
64
+ ])) : r("", !0);
65
+ }
66
+ }, _ = { class: "mwnf-back-link" }, v = {
67
+ __name: "BackLink",
68
+ props: {
69
+ label: {
70
+ type: String,
71
+ default: "core.action.back"
72
+ },
73
+ to: {
74
+ type: [String, Object],
75
+ default: null
76
+ },
77
+ href: {
78
+ type: String,
79
+ default: ""
80
+ }
81
+ },
82
+ setup(t) {
83
+ let r = u();
84
+ function a() {
85
+ return window.history.length > 1;
86
+ }
87
+ function d(e) {
88
+ a() && (e.preventDefault(), r.back());
89
+ }
90
+ return (r, u) => (s(), i("div", _, [!a() && (t.to || t.href) ? (s(), n(e, {
91
+ key: 0,
92
+ to: t.to,
93
+ href: t.href,
94
+ class: "mwnf-back-link__button"
95
+ }, {
96
+ default: l(() => [o(" ↩ " + c(r.$t(t.label)), 1)]),
97
+ _: 1
98
+ }, 8, ["to", "href"])) : (s(), i("button", {
99
+ key: 1,
100
+ class: "mwnf-back-link__button",
101
+ onClick: d
102
+ }, " ↩ " + c(r.$t(t.label)), 1))]));
103
+ }
104
+ };
105
+ //#endregion
106
+ export { g as n, v as t };