@museumwnf/viewer-layout 2.17.0 → 3.0.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
@@ -17,7 +17,7 @@ all the packages:
17
17
  | `@museumwnf/viewer-layout/content` | building blocks | the blocks alone, without the shell | the same |
18
18
  | `@museumwnf/viewer-layout/views` | composed views | whole pages driven by a spec the website declares, with slots it fills | viewer-core's data layer, the site config and the router |
19
19
  | `@museumwnf/viewer-layout/components` | composed views | `SiteShell`, the frame composed from `dataset.config.js` | the site config and the current route |
20
- | `@museumwnf/viewer-layout/dxa` | DXA family layer | the gallery and exhibition pages, `standardRoutes` | the entries above, `@museumwnf/viewer-core` and `@museumwnf/viewer-core/dxa` |
20
+ | `@museumwnf/viewer-layout/dxa` | DXA family layer | the gallery and exhibition pages and shells, the family-only blocks, `standardRoutes`, `galleryConfig` / `exhibitionConfig` | the entries above, `@museumwnf/viewer-core` and `@museumwnf/viewer-core/dxa` |
21
21
  | `@museumwnf/viewer-layout/style.css` | — | one stylesheet for every entry, the family styles included | — |
22
22
  | `@museumwnf/viewer-layout/tokens.reference.css` | — | every token the package reads, with its fallback | — |
23
23
 
@@ -46,14 +46,16 @@ entries publish, and `@museumwnf/viewer-core`. A component that moves
46
46
  between entries keeps its old export as an alias until the websites have
47
47
  moved; a major release then removes the alias.
48
48
 
49
- Where the package does not follow these rules yet, milestone M10 moves it:
50
49
  `FeaturedPartners`, `SiblingGalleries`, `PopupLogo`, `PictureGallery`,
51
- `PictureNarrative` (blocks) and `RecordSheetView` (a composed view) are used
52
- only by the DXA family and move under `/dxa`
53
- ([inventory-app#2017](https://github.com/museumwithnofrontiers/inventory-app/issues/2017));
54
- `PictureGallery`, `PictureNarrative` and `EssayView` default to
55
- `exhibition.*` entries, and `FeaturedPartners` to `partner.featured`, which
56
- does not exist.
50
+ `PictureNarrative` (blocks) and `ItemDetailView` (the composed view that was
51
+ `RecordSheetView`) are used only by the DXA family and live under `/dxa`
52
+ only: they moved there in 2.18.0
53
+ ([inventory-app#2055](https://github.com/museumwithnofrontiers/inventory-app/issues/2055)),
54
+ and their old `/content` and `/views` names went in 3.0.0
55
+ ([inventory-app#2058](https://github.com/museumwithnofrontiers/inventory-app/issues/2058)).
56
+ Where the package does not follow the rules yet: `PictureGallery`,
57
+ `PictureNarrative` and `EssayView` default to `exhibition.*` entries
58
+ (inventory-app#2019).
57
59
 
58
60
  Two companion docs give a more scannable version of who-owns-what and the
59
61
  full slot list: [`docs/designer-contract.md`](docs/designer-contract.md) (the
@@ -329,7 +331,7 @@ the application registered, without any router coupling here.
329
331
  | `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 |
330
332
  | `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 |
331
333
  | `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 |
332
- | `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 |
334
+ | `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 `ItemDetailView`'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 |
333
335
  | `SpecialFeatures` | `features` (the record's `details`: `{ id, internal_name, display_order, images, artist_names }`), `tr` (feature → its translation: `name`, `location`, `dates`, `description`), `language` (the pictures' captions), `glossary` (`RecordView`'s), `heading` ('sheet.field.specialFeatures'), `dir` | a monument's sub-details, as the data package embeds them on it: each one's name, location, dates and artists, its description as Markdown with the record's glossary terms, its pictures in a row; `DynastyList`'s shape |
334
336
  | `RelatedMedia` | `media` (the record's `media`: `{ title, description, url, language }`), `language` (the entries in it when there are any, all otherwise), `descriptions` (true), `heading` ('record.related.audioVideo'), `dir` | a record's audio and video, each a ↗ link opening elsewhere, its description under it |
335
337
  | `OnDisplayIn` | `groups: [{ heading? (entry), links: [{ id?, label (inline HTML), to? \| href?, external? }] }]`, `heading` ('record.related.onDisplayIn'; '' renders the groups alone), `pendingLabel` (entry, after a name with no address), `dir` | the exhibitions, chapters and galleries that show a record: a route links inside the site, an `external` address opens elsewhere marked ↗, a name with neither is plain text |
@@ -355,14 +357,14 @@ every bundle of `@museumwnf/viewer-i18n` from 1.7.0. `GlossaryTool` also reads
355
357
  `.additionalContent`, `.addRelatedWorks`, `.hideRelatedWorks` and
356
358
  `exhibition.related.items`, `.reciprocal`, which only the `exhibition` and
357
359
  `standalone` bundles of `@museumwnf/viewer-i18n` carry — a family default
358
- in a generic entry, which ends when the two move under `/dxa` (see
360
+ in the family's entry now, where the two live since 2.18.0 (see
359
361
  [Entry points and layers](#entry-points-and-layers)). Every other text is a
360
362
  prop.
361
363
 
362
364
  `FeaturedPartners`, `SiblingGalleries`, `PopupLogo`, `PictureGallery` and
363
- `PictureNarrative` are used only by the DXA family, and move under `/dxa`
364
- in milestone M10; their exports here stay as aliases until the websites have
365
- moved.
365
+ `PictureNarrative` are used only by the DXA family, and live under `/dxa`
366
+ (since 2.18.0; their exports here went in 3.0.0). `FeaturedPartners`' heading defaults to `partner.list.featured`
367
+ (viewer-i18n 4.3.0).
366
368
 
367
369
  ## Content classes
368
370
 
@@ -376,7 +378,7 @@ Building blocks copied from the legacy sites' own `site.css` files, available as
376
378
  | `.mwnf-button` | Styled button for primary actions; variant `.mwnf-button--secondary` for secondary/destructive actions |
377
379
  | `.mwnf-select` | Legacy-styled select element with fixed height and padding |
378
380
  | `.mwnf-back-bar` | Container for back-link buttons with standard padding and hover styling; `.mwnf-back-bar--link` is `BackLink`'s `bar` variant, the link itself as the bar (`--mwnf-back-bar-margin`) |
379
- | `.mwnf-chip` | Inline project/source chip; variants `.mwnf-chip--<family>` where family is one of `ISLandEPM`, `DBA`, `AWE`, `DCA`, `DGA`, `EXH`, or `Galleries`, each with its own project-colour token |
381
+ | `.mwnf-chip` | Inline project/source chip; variants `.mwnf-chip--<family>` where family is one of `ISLandEPM`, `DBA`, `AWE`, `DCA`, `DGA`, `EXH`, `Galleries`, or `Explore` (a record with no project), each with its own project-colour token |
380
382
  | `.mwnf-popout` | Detail box for sheet data; used in record detail popups and panels |
381
383
  | `.mwnf-loader` | Loading/spinner message container with centered text and standard padding |
382
384
  | `.mwnf-link` | Link text colour for DXA sites; not a interactive element wrapper, a colour utility |
@@ -386,8 +388,10 @@ Building blocks copied from the legacy sites' own `site.css` files, available as
386
388
  Whole pages, made of the content components on viewer-core's
387
389
  composables and driven by a declaration the website writes instead of a
388
390
  page. They are the broad page structures every kind of website shares;
389
- `RecordSheetView`, used only by the DXA family, moves under `/dxa` with a
390
- Detail name in milestone M10. They are exported from `@museumwnf/viewer-layout/views` — and only from
391
+ `ItemDetailView`, the item page's view (`RecordSheetView` before 2.18.0), is used
392
+ only by the DXA family and exported from `/dxa` alone; its row stays in the
393
+ table below.
394
+ They are exported from `@museumwnf/viewer-layout/views` — and only from
391
395
  there: they read the records and the engine from `@museumwnf/viewer-core`
392
396
  itself, whose entry point carries `.vue` files, and a website's test runner
393
397
  that loads this package natively would fail on the first one if the package
@@ -430,7 +434,7 @@ export default {
430
434
  | `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, filter }` — the pick is `useFeaturedRecord`, among the records `filter` keeps (viewer-core 2.2.0) — `panels` (the welcome and the record as `.mwnf-panel` boxes) | `before`, default, `after` |
431
435
  | `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 |
432
436
  | `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) |
433
- | `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) } \| false` (the holding-museum row: the item's holder text, then the partner as `PartnerPanel`'s `summary` — "About {name}, {city}, {country}" — linked through `route`; `route` returning null keeps the name without a link, for a hidden partner; `false` keeps the holder text alone; decision D3), 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" |
437
+ | `ItemDetailView` (`/dxa`) | 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) } \| false` (the holding-museum row: the item's holder text, then the partner as `PartnerPanel`'s `summary` — "About {name}, {city}, {country}" — linked through `route`; `route` returning null keeps the name without a link, for a hidden partner; `false` keeps the holder text alone; decision D3), 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" |
434
438
  | `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 }` |
435
439
  | `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` |
436
440
  | `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, when a site builds it itself), `route` (a route name or `(partner, ctx) => to \| href`, read by the default row only), `objectsRoute(partner, ctx)` (the line's View objects link), `actions: true` (the line's Read more · View objects links), `emptyLabel` (the line for a partner holding nothing), `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, view }`, once per partner — main, associated and, when nested, a child row too; the default is `PartnerPanel`'s `line` over `view`, viewer-core's `partnerView()`), `after` — every slot also given `{ t, locale, groups, orderDir, toggleOrder }` |
@@ -505,24 +509,48 @@ it, and a website can still replace any family page with its own component
505
509
  on the same route name. Texts stay in `@museumwnf/viewer-i18n`'s `gallery`
506
510
  and `exhibition` sections.
507
511
 
508
- It ships each family's pages plus `standardRoutes(family, config)`, the
509
- RouteRecord factory a site spreads into `extraViews`. One page serves both
510
- families: `PartnerDetail`, the partner page (`PartnerPanel`'s `full`
511
- variant), fed by each family's own half — `galleryPartnerDetail` /
512
- `exhibitionPartnerDetail`, which `standardRoutes` passes as its `family`
513
- prop; `GalleryPartnerProfile`/`ExhibitionPartnerProfile` are the same page
514
- under their old names.
512
+ Since 2.18.0 it holds a whole DXA site but its values. A gallery's or an
513
+ exhibition's `dataset.config.js` is one call:
515
514
 
516
515
  ```js
517
- import { standardRoutes } from '@museumwnf/viewer-layout/dxa'
518
-
519
- extraViews: [
520
- ...standardRoutes('gallery', { creditsBody: 'carpets.credits.body' }),
521
- // + this gallery's own routes: home, item, collection entrance,
522
- // timeline entrance, partners entrance
523
- ]
516
+ import { galleryConfig } from '@museumwnf/viewer-layout/dxa'
517
+
518
+ export default galleryConfig({
519
+ datasetPackage: '@museumwnf/carpets-data',
520
+ siteName: 'Carpets', // for a package without manifest.site
521
+ origin: 'https://museumwithnofrontiers.github.io/carpets',
522
+ projectColors: { '<project id>': 'mwnf-chip--DCA' }, // one of the mwnf-chip--* classes
523
+ noticeProjects: ['<project id>'], // sheets with the Explore-partner notice
524
+ creditsBody: 'carpets.credits.body',
525
+ })
524
526
  ```
525
527
 
528
+ `exhibitionConfig` takes the same values (and `partnerObjects`, overrides of
529
+ the shared `exhibition.partnerObjects.*` entries). The config carries the
530
+ family's shell (`GalleryShell` / `ExhibitionShell`), menu, banner, sponsor
531
+ strip, legacy redirects, and every page through
532
+ `standardRoutes(family, { pages: true, … })`:
533
+
534
+ - the eleven pages every site of a family already served from here
535
+ (search, partners, partner and institution pages, timeline results and
536
+ gallery, collection search and results, and a gallery's about and
537
+ credits);
538
+ - with `pages: true`, the pages each site used to carry itself: the home
539
+ page, the item page (`GalleryItemDetail` / `ExhibitionItemDetail`, on
540
+ `ItemDetailView` and viewer-core's `useGalleryItemDetail` /
541
+ `useExhibitionItemDetail`), the timeline entrance, and an exhibition's
542
+ about page, themes, theme pages, theme galleries, related content and
543
+ credits.
544
+
545
+ One page serves both families: `PartnerDetail`, the partner page
546
+ (`PartnerPanel`'s `full` variant), fed by each family's own half —
547
+ `galleryPartnerDetail` / `exhibitionPartnerDetail`, which `standardRoutes`
548
+ passes as its `family` prop.
549
+
550
+ An exhibition's theme pages read its three theme colours as
551
+ `--mwnf-dxa-theme-*` tokens; a record with no project gets the
552
+ `mwnf-chip--Explore` chip.
553
+
526
554
  Route names and paths are pinned to what every live DXA site already
527
555
  registers, so adopting the factory keeps every existing deep link (and
528
556
  every `legacyRoutes` resolver targeting those names) working unmodified.
@@ -0,0 +1,88 @@
1
+ import { y as e } from "./TimelineEventList-BhkWyY5-.js";
2
+ import { Fragment as t, computed as n, createCommentVNode as r, createElementBlock as i, createElementVNode as a, createVNode as o, openBlock as s, renderList as c, renderSlot as l, toDisplayString as u, useSlots as d, withCtx as f } from "vue";
3
+ //#region src/content/FeaturedRecord.vue
4
+ var p = {
5
+ key: 0,
6
+ class: "mwnf-featured"
7
+ }, m = {
8
+ key: 0,
9
+ class: "mwnf-featured__heading"
10
+ }, h = {
11
+ key: 0,
12
+ class: "mwnf-featured__media"
13
+ }, g = ["src", "alt"], _ = { class: "mwnf-featured__body" }, v = {
14
+ key: 0,
15
+ class: "mwnf-featured__eyebrow"
16
+ }, y = ["innerHTML"], b = {
17
+ key: 1,
18
+ class: "mwnf-featured__action"
19
+ }, x = {
20
+ __name: "FeaturedRecord",
21
+ props: {
22
+ image: {
23
+ type: String,
24
+ default: ""
25
+ },
26
+ imageAlt: {
27
+ type: String,
28
+ default: ""
29
+ },
30
+ eyebrow: {
31
+ type: String,
32
+ default: ""
33
+ },
34
+ name: {
35
+ type: String,
36
+ default: ""
37
+ },
38
+ meta: {
39
+ type: Array,
40
+ default: () => []
41
+ },
42
+ action: {
43
+ type: String,
44
+ default: ""
45
+ },
46
+ to: {
47
+ type: [String, Object],
48
+ default: null
49
+ },
50
+ href: {
51
+ type: String,
52
+ default: ""
53
+ },
54
+ heading: {
55
+ type: String,
56
+ default: ""
57
+ }
58
+ },
59
+ setup(x) {
60
+ let S = x, C = d(), w = n(() => !!(S.name || S.image || C.default));
61
+ return (n, d) => w.value ? (s(), i("section", p, [x.heading ? (s(), i("h2", m, u(x.heading), 1)) : r("", !0), l(n.$slots, "default", {}, () => [o(e, {
62
+ class: "mwnf-featured__link",
63
+ to: x.to,
64
+ href: x.href
65
+ }, {
66
+ default: f(() => [x.image ? (s(), i("span", h, [a("img", {
67
+ class: "mwnf-featured__image",
68
+ src: x.image,
69
+ alt: x.imageAlt,
70
+ loading: "eager"
71
+ }, null, 8, g)])) : r("", !0), a("span", _, [
72
+ x.eyebrow ? (s(), i("span", v, u(x.eyebrow), 1)) : r("", !0),
73
+ a("span", {
74
+ class: "mwnf-featured__name",
75
+ innerHTML: x.name
76
+ }, null, 8, y),
77
+ (s(!0), i(t, null, c(x.meta, (e, t) => (s(), i("span", {
78
+ key: t,
79
+ class: "mwnf-featured__meta"
80
+ }, u(e), 1))), 128)),
81
+ x.action ? (s(), i("span", b, u(x.action) + " →", 1)) : r("", !0)
82
+ ])]),
83
+ _: 1
84
+ }, 8, ["to", "href"])])])) : r("", !0);
85
+ }
86
+ };
87
+ //#endregion
88
+ export { x as t };
@@ -0,0 +1,284 @@
1
+ import { n as e, o as t } from "./TimelineEventList-BhkWyY5-.js";
2
+ import { i as n, n as r, o as i, r as a, t as o } from "./OnDisplayIn-eSG5r48j.js";
3
+ import { s } from "./TimelineResultsView-CgGVsJcp.js";
4
+ import { Fragment as c, computed as l, createBlock as u, createCommentVNode as d, createElementBlock as f, createElementVNode as p, createSlots as ee, createTextVNode as m, createVNode as h, guardReactiveProps as g, normalizeClass as _, normalizeProps as v, openBlock as y, renderList as b, renderSlot as x, toDisplayString as S, unref as C, useSlots as te, withCtx as w } from "vue";
5
+ import { NotFoundView as ne, byId as T, loadEntities as re, mdInline as E, mdStrip as ie, mwnfLinks as D, partnerView as ae, useDataPackage as oe, useI18n as se, useProjects as ce } from "@museumwnf/viewer-core";
6
+ //#region src/dxa/ItemDetailView.vue
7
+ var le = {
8
+ key: 0,
9
+ class: "mwnf-sheet-source"
10
+ }, ue = {
11
+ key: 0,
12
+ class: "mwnf-sheet-source__line"
13
+ }, O = {
14
+ key: 1,
15
+ class: "mwnf-sheet-source__uid"
16
+ }, k = {
17
+ key: 2,
18
+ class: "mwnf-sheet-source__collection"
19
+ }, A = ["href"], j = {
20
+ key: 1,
21
+ class: "mwnf-sheet-notice"
22
+ }, M = {
23
+ key: "museum",
24
+ class: "mwnf-sheet-museum"
25
+ }, N = ["innerHTML"], P = {
26
+ key: 0,
27
+ class: "mwnf-sheet-related"
28
+ }, F = {
29
+ key: 0,
30
+ class: "mwnf-sheet-related__heading mwnf-sheet-related__heading--caps"
31
+ }, I = {
32
+ key: 1,
33
+ class: "mwnf-sheet-related__description"
34
+ }, L = {
35
+ key: 0,
36
+ class: "mwnf-sheet-related__references"
37
+ }, R = { key: 2 }, z = {
38
+ key: 3,
39
+ class: "mwnf-sheet-related__unresolved"
40
+ }, B = {
41
+ key: 2,
42
+ class: "mwnf-sheet-related__line"
43
+ }, V = ["href"], H = { key: 6 }, U = { class: "mwnf-sheet-related__heading" }, de = { key: 7 }, fe = { class: "mwnf-sheet-related__heading" }, pe = { class: "mwnf-sheet-related__line" }, me = ["href"], W = { key: 8 }, he = { class: "mwnf-sheet-related__heading" }, ge = { class: "mwnf-sheet-related__line" }, _e = ["href"], ve = { key: 9 }, ye = { class: "mwnf-sheet-related__heading" }, G = {
44
+ __name: "ItemDetailView",
45
+ props: {
46
+ spec: {
47
+ type: Object,
48
+ required: !0
49
+ },
50
+ id: {
51
+ type: String,
52
+ required: !0
53
+ },
54
+ entity: {
55
+ type: String,
56
+ default: ""
57
+ },
58
+ dataGetter: {
59
+ type: Function,
60
+ default: null
61
+ }
62
+ },
63
+ setup(G) {
64
+ let K = G, be = te(), xe = /* @__PURE__ */ new Set([
65
+ "before-sheet",
66
+ "museum",
67
+ "related"
68
+ ]), Se = l(() => Object.keys(be).filter((e) => !xe.has(e))), Ce = l(() => !K.dataGetter || K.dataGetter(K.id) != null), { t: q } = se(), J = l(() => K.spec), Y = ce(), X = (e, t) => (e.name ?? "").localeCompare(t.name ?? ""), we = (e) => (e.gallery_references ?? []).filter((e) => e.kind === "gallery").sort(X), Te = (e) => (e.gallery_references ?? []).filter((e) => e.kind === "exhibition").sort(X), Z = (e) => Y.links(e.project_id)?.artisticIntroductionUrl ?? null, Q = (e) => Y.links(e.project_id)?.relatedDatabaseUrl ?? null;
69
+ function Ee(e, t) {
70
+ if (!e) return null;
71
+ let n = J.value.sourceDatabase === !1 ? null : J.value.sourceDatabase ?? null, r = null;
72
+ if (n) {
73
+ let i = n.chipClass ? n.chipClass(e, t) : null, a = Y.label(e.project_id);
74
+ (i || a) && (r = {
75
+ chipClass: i,
76
+ name: a,
77
+ label: q(n.label ?? "record.sheet.sourceDatabase")
78
+ });
79
+ }
80
+ let i = n && n.addToCollection !== !1 ? q(n.addToCollection?.label ?? "record.action.addToCollection") : null, a = n ? e.backward_compatibility ?? "" : "", o = J.value.notice === !1 ? null : J.value.notice ?? null, s = o && o.show(e, t) ? q(o.label) : null;
81
+ return {
82
+ source: r,
83
+ addToCollectionLabel: i,
84
+ notice: s,
85
+ backwardCompatibility: a
86
+ };
87
+ }
88
+ let $ = oe(), De = T("partners"), Oe = T("countries");
89
+ re(["partners", "countries"]), $.loadTranslations("countries", "en");
90
+ function ke(e, t) {
91
+ let n = $.tr("countries", e, t, "en");
92
+ return ie(n.name ?? Oe.value?.get(e)?.internal_name ?? "");
93
+ }
94
+ function Ae(e) {
95
+ let t = e.text?.holder ? E(String(e.text.holder)) : "", n = J.value.museum === !1 ? null : J.value.museum ?? null, r = n ? De.value?.get(e.record?.partner_id) : null;
96
+ return {
97
+ holder: t,
98
+ view: r ? ae(r, $.tr("partners", r.id, e.language, "en"), {
99
+ countryLabel: (t) => ke(t, e.language),
100
+ route: (t) => n.route ? n.route(t.id, e) : null
101
+ }) : null
102
+ };
103
+ }
104
+ function je(e, t) {
105
+ if (!e) return null;
106
+ let n = J.value.related ?? {}, r = t.records ?? [], i = t.outside ?? [], a = K.dataGetter ? r.filter((e) => K.dataGetter(e.id) != null) : r, o = K.dataGetter ? r.filter((e) => K.dataGetter(e.id) == null).map((t) => (e.related_items ?? []).find((e) => e.id === t.id)).filter(Boolean) : [], s = [...i, ...o].map((e) => ({
107
+ ...e,
108
+ name: e.project_id ? Y.label(e.project_id) : null,
109
+ chipClass: n.outsideChip ? n.outsideChip(e, t) : null
110
+ })), c = (e) => ({
111
+ id: e.id,
112
+ label: E(e.name ?? ""),
113
+ href: e.legacy_host || "",
114
+ external: !!e.legacy_host
115
+ }), l = n.onDisplayIn && n.onDisplayIn !== !1 ? {
116
+ pendingLabel: n.onDisplayIn.linkPendingLabel ?? "",
117
+ groups: [{
118
+ heading: "record.related.exhibitions",
119
+ links: Te(e).map(c)
120
+ }, {
121
+ heading: "record.related.galleries",
122
+ links: we(e).map(c)
123
+ }].filter((e) => e.links.length)
124
+ } : null, u = n.databaseLabel && Q(e) ? {
125
+ url: Q(e),
126
+ label: q(n.databaseLabel),
127
+ name: Y.label(e.project_id)
128
+ } : null, d = n.overallDatabase && n.overallDatabase !== !1 ? {
129
+ label: q(n.overallDatabase.label),
130
+ linkLabel: q(n.overallDatabase.linkLabel)
131
+ } : null;
132
+ return {
133
+ title: n.title ? q(n.title) : "",
134
+ description: n.description ? q(n.description) : "",
135
+ heading: q(n.heading ?? "record.related.items"),
136
+ variant: n.variant ?? "grid",
137
+ actionLabel: n.actionLabel ? q(n.actionLabel) : "",
138
+ inPackage: a,
139
+ references: s,
140
+ notInPackageLabel: n.notInPackageLabel ? q(n.notInPackageLabel) : "",
141
+ artisticIntroduction: n.artisticIntroductionLabel && Z(e) ? {
142
+ url: Z(e),
143
+ label: q(n.artisticIntroductionLabel)
144
+ } : null,
145
+ timeline: n.timeline ? n.timeline(e, t) : null,
146
+ glossary: n.glossary !== !1,
147
+ dynasties: n.dynasties ? n.dynasties(e, t.language, t) : null,
148
+ media: n.media === !1 ? [] : e.media ?? [],
149
+ onDisplayIn: l,
150
+ database: u,
151
+ overallDatabase: d,
152
+ download: n.download === !1 ? null : {
153
+ label: q("record.action.download"),
154
+ pdfLabel: q("record.action.downloadPdf")
155
+ }
156
+ };
157
+ }
158
+ function Me() {
159
+ window.print();
160
+ }
161
+ return (l, te) => Ce.value ? (y(), u(s, {
162
+ key: 0,
163
+ spec: J.value,
164
+ id: G.id,
165
+ entity: G.entity
166
+ }, ee({
167
+ "before-sheet": w((e) => [x(l.$slots, "before-sheet", v(g(e)), () => [(y(!0), f(c, null, b([Ee(e.record, e)], (t) => (y(), f(c, { key: "before-sheet" }, [t?.source || t?.backwardCompatibility || t?.addToCollectionLabel ? (y(), f("div", le, [
168
+ t.source ? (y(), f("p", ue, [t.source.chipClass ? (y(), f("span", {
169
+ key: 0,
170
+ class: _(["mwnf-chip", t.source.chipClass]),
171
+ "aria-hidden": "true"
172
+ }, null, 2)) : d("", !0), t.source.name ? (y(), f(c, { key: 1 }, [m(S(t.source.label) + ": " + S(t.source.name), 1)], 64)) : d("", !0)])) : d("", !0),
173
+ t.backwardCompatibility ? (y(), f("p", O, [p("code", null, S(t.backwardCompatibility), 1)])) : d("", !0),
174
+ t.addToCollectionLabel ? (y(), f("p", k, [p("a", {
175
+ href: C(D).myCollection,
176
+ target: "_blank",
177
+ rel: "noopener"
178
+ }, "↗ " + S(t.addToCollectionLabel), 9, A)])) : d("", !0)
179
+ ])) : d("", !0), t?.notice ? (y(), f("div", j, [m(S(t.notice) + " ", 1), p("strong", null, [p("em", null, S(e.languages.map((e) => e.label).join(", ")), 1)])])) : d("", !0)], 64))), 128))])]),
180
+ museum: w((t) => [x(l.$slots, "museum", v(g(t)), () => [(y(!0), f(c, null, b([Ae(t)], (n) => (y(), f("div", M, [n.holder ? (y(), f("p", {
181
+ key: 0,
182
+ class: "mwnf-sheet-museum__holder",
183
+ innerHTML: n.holder
184
+ }, null, 8, N)) : d("", !0), n.view ? (y(), u(e, {
185
+ key: 1,
186
+ variant: "summary",
187
+ partner: n.view,
188
+ label: "partner.info.about",
189
+ dir: t.dir
190
+ }, null, 8, ["partner", "dir"])) : d("", !0)]))), 128))])]),
191
+ related: w((e) => [x(l.$slots, "related", v(g(e)), () => [(y(!0), f(c, null, b([je(e.record, e)], (s) => (y(), f(c, { key: e.record?.id ?? "related" }, [s ? (y(), f("div", P, [
192
+ s.title ? (y(), f("p", F, S(s.title), 1)) : d("", !0),
193
+ s.description ? (y(), f("p", I, S(s.description), 1)) : d("", !0),
194
+ h(t, {
195
+ heading: s.heading,
196
+ records: s.inPackage,
197
+ variant: s.variant,
198
+ "action-label": s.actionLabel
199
+ }, {
200
+ default: w(() => [s.references.length ? (y(), f("ul", L, [(y(!0), f(c, null, b(s.references, (e) => (y(), f("li", { key: e.id }, [
201
+ e.chipClass ? (y(), f("span", {
202
+ key: 0,
203
+ class: _(["mwnf-chip", e.chipClass]),
204
+ "aria-hidden": "true"
205
+ }, null, 2)) : d("", !0),
206
+ e.name ? (y(), f(c, { key: 1 }, [m(S(e.name), 1)], 64)) : (y(), f("code", R, S(e.backward_compatibility), 1)),
207
+ s.notInPackageLabel ? (y(), f("span", z, S(s.notInPackageLabel), 1)) : d("", !0)
208
+ ]))), 128))])) : d("", !0)]),
209
+ _: 2
210
+ }, 1032, [
211
+ "heading",
212
+ "records",
213
+ "variant",
214
+ "action-label"
215
+ ]),
216
+ s.artisticIntroduction ? (y(), f("p", B, [p("a", {
217
+ href: s.artisticIntroduction.url,
218
+ target: "_blank",
219
+ rel: "noopener"
220
+ }, "↗ " + S(s.artisticIntroduction.label), 9, V)])) : d("", !0),
221
+ s.timeline ? (y(), u(a, {
222
+ key: e.record.id,
223
+ info: s.timeline
224
+ }, null, 8, ["info"])) : d("", !0),
225
+ s.glossary ? (y(), u(i, {
226
+ key: 4,
227
+ language: e.language,
228
+ dir: e.dir
229
+ }, null, 8, ["language", "dir"])) : d("", !0),
230
+ s.dynasties?.records?.length ? (y(), u(n, {
231
+ key: 5,
232
+ heading: C(q)("record.dynasty.list"),
233
+ dynasties: s.dynasties.records,
234
+ tr: s.dynasties.tr,
235
+ dir: e.dir
236
+ }, null, 8, [
237
+ "heading",
238
+ "dynasties",
239
+ "tr",
240
+ "dir"
241
+ ])) : d("", !0),
242
+ h(r, {
243
+ media: s.media,
244
+ descriptions: !1,
245
+ dir: e.dir
246
+ }, null, 8, ["media", "dir"]),
247
+ s.onDisplayIn?.groups.length ? (y(), f("div", H, [p("p", U, S(C(q)("record.related.onDisplayIn")), 1), h(o, {
248
+ heading: "",
249
+ groups: s.onDisplayIn.groups,
250
+ "pending-label": s.onDisplayIn.pendingLabel,
251
+ dir: e.dir
252
+ }, null, 8, [
253
+ "groups",
254
+ "pending-label",
255
+ "dir"
256
+ ])])) : d("", !0),
257
+ s.database ? (y(), f("div", de, [p("p", fe, S(s.database.label), 1), p("p", pe, [p("a", {
258
+ href: s.database.url,
259
+ target: "_blank",
260
+ rel: "noopener"
261
+ }, "↗ " + S(s.database.name), 9, me)])])) : d("", !0),
262
+ s.overallDatabase ? (y(), f("div", W, [p("p", he, S(s.overallDatabase.label), 1), p("p", ge, [p("a", {
263
+ href: C(D).overallDatabase,
264
+ target: "_blank",
265
+ rel: "noopener"
266
+ }, "↗ " + S(s.overallDatabase.linkLabel), 9, _e)])])) : d("", !0),
267
+ s.download ? (y(), f("div", ve, [p("p", ye, S(s.download.label), 1), p("p", {
268
+ class: "mwnf-sheet-related__line mwnf-sheet-related__line--action",
269
+ onClick: Me
270
+ }, "➤ " + S(s.download.pdfLabel), 1)])) : d("", !0)
271
+ ])) : d("", !0)], 64))), 128))])]),
272
+ _: 2
273
+ }, [b(Se.value, (e) => ({
274
+ name: e,
275
+ fn: w((t) => [x(l.$slots, e, v(g(t)))])
276
+ }))]), 1032, [
277
+ "spec",
278
+ "id",
279
+ "entity"
280
+ ])) : (y(), u(C(ne), { key: 1 }));
281
+ }
282
+ };
283
+ //#endregion
284
+ export { G as t };