@canopy-iiif/app 1.4.0 → 1.4.2
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/package.json +1 -1
- package/ui/dist/index.mjs +193 -92
- package/ui/dist/index.mjs.map +4 -4
- package/ui/dist/server.mjs +207 -146
- package/ui/dist/server.mjs.map +4 -4
- package/ui/styles/base/_markdown.scss +1 -1
- package/ui/styles/components/_map.scss +63 -0
- package/ui/styles/components/search/_results.scss +2 -2
- package/ui/styles/index.css +54 -3
package/package.json
CHANGED
package/ui/dist/index.mjs
CHANGED
|
@@ -2075,7 +2075,7 @@ function CanopyDiagram() {
|
|
|
2075
2075
|
}
|
|
2076
2076
|
|
|
2077
2077
|
// ui/src/content/timeline/Timeline.jsx
|
|
2078
|
-
import
|
|
2078
|
+
import React34 from "react";
|
|
2079
2079
|
|
|
2080
2080
|
// ui/src/content/timeline/date-utils.js
|
|
2081
2081
|
var FALLBACK_LOCALE = (() => {
|
|
@@ -2227,6 +2227,56 @@ function clampProgress(value) {
|
|
|
2227
2227
|
return value;
|
|
2228
2228
|
}
|
|
2229
2229
|
|
|
2230
|
+
// ui/src/layout/ReferencedManifestCard.jsx
|
|
2231
|
+
import React33 from "react";
|
|
2232
|
+
function normalizeMetadata(metadata, summary) {
|
|
2233
|
+
if (Array.isArray(metadata) && metadata.length) {
|
|
2234
|
+
return metadata.filter(Boolean);
|
|
2235
|
+
}
|
|
2236
|
+
if (summary) return [summary];
|
|
2237
|
+
return [];
|
|
2238
|
+
}
|
|
2239
|
+
function ReferencedManifestCard({
|
|
2240
|
+
manifest = null,
|
|
2241
|
+
href,
|
|
2242
|
+
title,
|
|
2243
|
+
summary,
|
|
2244
|
+
metadata,
|
|
2245
|
+
thumbnail,
|
|
2246
|
+
type,
|
|
2247
|
+
className = "",
|
|
2248
|
+
...rest
|
|
2249
|
+
}) {
|
|
2250
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2251
|
+
const record = manifest || {};
|
|
2252
|
+
const resolvedHref = (_a = href != null ? href : record.href) != null ? _a : "";
|
|
2253
|
+
const resolvedTitle = (_c = (_b = title != null ? title : record.title) != null ? _b : record.href) != null ? _c : "";
|
|
2254
|
+
const resolvedSummary = (_d = summary != null ? summary : record.summary) != null ? _d : "";
|
|
2255
|
+
const resolvedMetadata = normalizeMetadata(
|
|
2256
|
+
metadata != null ? metadata : record.metadata,
|
|
2257
|
+
resolvedSummary
|
|
2258
|
+
);
|
|
2259
|
+
const resolvedThumbnail = (_e = thumbnail != null ? thumbnail : record.thumbnail) != null ? _e : null;
|
|
2260
|
+
const resolvedType = (_f = type != null ? type : record.type) != null ? _f : "work";
|
|
2261
|
+
const classes = [
|
|
2262
|
+
"canopy-referenced-manifest-card",
|
|
2263
|
+
className
|
|
2264
|
+
].filter(Boolean).join(" ");
|
|
2265
|
+
return /* @__PURE__ */ React33.createElement(
|
|
2266
|
+
TeaserCard,
|
|
2267
|
+
{
|
|
2268
|
+
href: resolvedHref || void 0,
|
|
2269
|
+
title: resolvedTitle || resolvedHref || "",
|
|
2270
|
+
summary: resolvedSummary,
|
|
2271
|
+
metadata: resolvedMetadata,
|
|
2272
|
+
thumbnail: resolvedThumbnail,
|
|
2273
|
+
type: resolvedType,
|
|
2274
|
+
className: classes,
|
|
2275
|
+
...rest
|
|
2276
|
+
}
|
|
2277
|
+
);
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2230
2280
|
// ui/src/content/timeline/Timeline.jsx
|
|
2231
2281
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
2232
2282
|
var DEFAULT_TRACK_HEIGHT = 640;
|
|
@@ -2394,24 +2444,14 @@ function TimelineConnector({ side, isActive, highlight }) {
|
|
|
2394
2444
|
"canopy-timeline__connector-dot",
|
|
2395
2445
|
highlight || isActive ? "is-active" : ""
|
|
2396
2446
|
].filter(Boolean).join(" ");
|
|
2397
|
-
return /* @__PURE__ */
|
|
2447
|
+
return /* @__PURE__ */ React34.createElement("span", { className: connectorClasses, "aria-hidden": "true" }, side === "left" ? /* @__PURE__ */ React34.createElement(React34.Fragment, null, /* @__PURE__ */ React34.createElement("span", { className: "canopy-timeline__connector-line" }), /* @__PURE__ */ React34.createElement("span", { className: dotClasses })) : /* @__PURE__ */ React34.createElement(React34.Fragment, null, /* @__PURE__ */ React34.createElement("span", { className: dotClasses }), /* @__PURE__ */ React34.createElement("span", { className: "canopy-timeline__connector-line" })));
|
|
2398
2448
|
}
|
|
2399
2449
|
function renderResourceSection(point) {
|
|
2400
2450
|
if (!point) return null;
|
|
2401
2451
|
const manifestCards = Array.isArray(point.manifests) ? point.manifests.filter(Boolean) : [];
|
|
2402
2452
|
const legacyResources = Array.isArray(point.resources) ? point.resources.filter(Boolean) : [];
|
|
2403
2453
|
if (!manifestCards.length && !legacyResources.length) return null;
|
|
2404
|
-
return /* @__PURE__ */
|
|
2405
|
-
TeaserCard,
|
|
2406
|
-
{
|
|
2407
|
-
href: manifest.href,
|
|
2408
|
-
title: manifest.title || manifest.href,
|
|
2409
|
-
summary: manifest.summary,
|
|
2410
|
-
metadata: Array.isArray(manifest.metadata) && manifest.metadata.length ? manifest.metadata : manifest.summary ? [manifest.summary] : [],
|
|
2411
|
-
thumbnail: manifest.thumbnail,
|
|
2412
|
-
type: manifest.type || "work"
|
|
2413
|
-
}
|
|
2414
|
-
))), legacyResources.map((resource, idx) => /* @__PURE__ */ React33.createElement("div", { key: resource.id || resource.href || `legacy-${idx}` }, /* @__PURE__ */ React33.createElement(
|
|
2454
|
+
return /* @__PURE__ */ React34.createElement("div", { className: "canopy-timeline__resources" }, /* @__PURE__ */ React34.createElement("div", { className: "canopy-timeline__resources-list" }, manifestCards.map((manifest) => /* @__PURE__ */ React34.createElement("div", { key: manifest.id || manifest.href }, /* @__PURE__ */ React34.createElement(ReferencedManifestCard, { manifest }))), legacyResources.map((resource, idx) => /* @__PURE__ */ React34.createElement("div", { key: resource.id || resource.href || `legacy-${idx}` }, /* @__PURE__ */ React34.createElement(
|
|
2415
2455
|
TeaserCard,
|
|
2416
2456
|
{
|
|
2417
2457
|
href: resource.href,
|
|
@@ -2436,26 +2476,26 @@ function Timeline({
|
|
|
2436
2476
|
...rest
|
|
2437
2477
|
}) {
|
|
2438
2478
|
const payloadPoints = payload && Array.isArray(payload.points) ? payload.points : null;
|
|
2439
|
-
const rawPoints =
|
|
2479
|
+
const rawPoints = React34.useMemo(() => {
|
|
2440
2480
|
if (Array.isArray(pointsProp) && pointsProp.length) return pointsProp;
|
|
2441
2481
|
if (payloadPoints && payloadPoints.length) return payloadPoints;
|
|
2442
2482
|
return [];
|
|
2443
2483
|
}, [pointsProp, payloadPoints]);
|
|
2444
|
-
const sanitizedPoints =
|
|
2484
|
+
const sanitizedPoints = React34.useMemo(
|
|
2445
2485
|
() => sanitizePoints(rawPoints),
|
|
2446
2486
|
[rawPoints]
|
|
2447
2487
|
);
|
|
2448
2488
|
const localeValue = payload && payload.locale ? payload.locale : localeProp;
|
|
2449
|
-
const baseLocale =
|
|
2489
|
+
const baseLocale = React34.useMemo(
|
|
2450
2490
|
() => createLocale(localeValue),
|
|
2451
2491
|
[localeValue]
|
|
2452
2492
|
);
|
|
2453
2493
|
const rangeInput = payload && payload.range ? payload.range : rangeProp || {};
|
|
2454
|
-
const rangeOverrides =
|
|
2494
|
+
const rangeOverrides = React34.useMemo(
|
|
2455
2495
|
() => deriveRangeOverrides(sanitizedPoints, rangeInput),
|
|
2456
2496
|
[sanitizedPoints, rangeInput]
|
|
2457
2497
|
);
|
|
2458
|
-
const effectiveRange =
|
|
2498
|
+
const effectiveRange = React34.useMemo(
|
|
2459
2499
|
() => normalizeRange({
|
|
2460
2500
|
...rangeOverrides,
|
|
2461
2501
|
locale: baseLocale
|
|
@@ -2464,7 +2504,7 @@ function Timeline({
|
|
|
2464
2504
|
);
|
|
2465
2505
|
const spanStart = effectiveRange.startDate.getTime();
|
|
2466
2506
|
const span = effectiveRange.span;
|
|
2467
|
-
const pointsWithPosition =
|
|
2507
|
+
const pointsWithPosition = React34.useMemo(() => {
|
|
2468
2508
|
if (!sanitizedPoints.length) return [];
|
|
2469
2509
|
return sanitizedPoints.map((point, index) => {
|
|
2470
2510
|
const timestamp = point.meta.timestamp;
|
|
@@ -2478,29 +2518,29 @@ function Timeline({
|
|
|
2478
2518
|
};
|
|
2479
2519
|
});
|
|
2480
2520
|
}, [sanitizedPoints, spanStart, span]);
|
|
2481
|
-
const [activeId, setActiveId] =
|
|
2521
|
+
const [activeId, setActiveId] = React34.useState(
|
|
2482
2522
|
() => getActivePointId(pointsWithPosition)
|
|
2483
2523
|
);
|
|
2484
|
-
|
|
2524
|
+
React34.useEffect(() => {
|
|
2485
2525
|
setActiveId(getActivePointId(pointsWithPosition));
|
|
2486
2526
|
}, [pointsWithPosition]);
|
|
2487
2527
|
const thresholdValue = typeof thresholdProp === "number" ? thresholdProp : payload && payload.threshold != null ? payload.threshold : null;
|
|
2488
2528
|
const stepsValue = typeof steps === "number" ? Number(steps) : payload && typeof payload.steps === "number" ? Number(payload.steps) : null;
|
|
2489
|
-
const thresholdMs =
|
|
2529
|
+
const thresholdMs = React34.useMemo(
|
|
2490
2530
|
() => getThresholdMs(thresholdValue, effectiveRange.granularity),
|
|
2491
2531
|
[thresholdValue, effectiveRange.granularity]
|
|
2492
2532
|
);
|
|
2493
|
-
const groupedEntries =
|
|
2533
|
+
const groupedEntries = React34.useMemo(
|
|
2494
2534
|
() => buildGroupedEntries(pointsWithPosition, thresholdMs, {
|
|
2495
2535
|
granularity: effectiveRange.granularity,
|
|
2496
2536
|
locale: baseLocale
|
|
2497
2537
|
}),
|
|
2498
2538
|
[pointsWithPosition, thresholdMs, effectiveRange.granularity, baseLocale]
|
|
2499
2539
|
);
|
|
2500
|
-
const [expandedGroupIds, setExpandedGroupIds] =
|
|
2540
|
+
const [expandedGroupIds, setExpandedGroupIds] = React34.useState(
|
|
2501
2541
|
() => /* @__PURE__ */ new Set()
|
|
2502
2542
|
);
|
|
2503
|
-
|
|
2543
|
+
React34.useEffect(() => {
|
|
2504
2544
|
setExpandedGroupIds((prev) => {
|
|
2505
2545
|
if (!prev || prev.size === 0) return prev;
|
|
2506
2546
|
const validIds = new Set(
|
|
@@ -2515,7 +2555,7 @@ function Timeline({
|
|
|
2515
2555
|
return changed ? next : prev;
|
|
2516
2556
|
});
|
|
2517
2557
|
}, [groupedEntries]);
|
|
2518
|
-
const toggleGroup =
|
|
2558
|
+
const toggleGroup = React34.useCallback((groupId) => {
|
|
2519
2559
|
setExpandedGroupIds((prev) => {
|
|
2520
2560
|
const next = new Set(prev || []);
|
|
2521
2561
|
if (next.has(groupId)) next.delete(groupId);
|
|
@@ -2538,7 +2578,7 @@ function Timeline({
|
|
|
2538
2578
|
point.id === activeId ? "is-active" : "",
|
|
2539
2579
|
point.highlight ? "is-highlighted" : ""
|
|
2540
2580
|
].filter(Boolean).join(" ");
|
|
2541
|
-
const connector = /* @__PURE__ */
|
|
2581
|
+
const connector = /* @__PURE__ */ React34.createElement(
|
|
2542
2582
|
TimelineConnector,
|
|
2543
2583
|
{
|
|
2544
2584
|
side: point.side,
|
|
@@ -2546,9 +2586,9 @@ function Timeline({
|
|
|
2546
2586
|
highlight: point.highlight
|
|
2547
2587
|
}
|
|
2548
2588
|
);
|
|
2549
|
-
const body = /* @__PURE__ */
|
|
2589
|
+
const body = /* @__PURE__ */ React34.createElement("div", { className: "canopy-timeline__point-body" }, /* @__PURE__ */ React34.createElement("span", { className: "canopy-timeline__point-date" }, point.meta.label), /* @__PURE__ */ React34.createElement("span", { className: "canopy-timeline__point-title" }, point.title), point.summary ? /* @__PURE__ */ React34.createElement("span", { className: "canopy-timeline__point-summary" }, point.summary) : null);
|
|
2550
2590
|
const resourceSection = renderResourceSection(point);
|
|
2551
|
-
return /* @__PURE__ */
|
|
2591
|
+
return /* @__PURE__ */ React34.createElement(
|
|
2552
2592
|
"div",
|
|
2553
2593
|
{
|
|
2554
2594
|
key: point.id,
|
|
@@ -2556,7 +2596,7 @@ function Timeline({
|
|
|
2556
2596
|
style: wrapperStyle,
|
|
2557
2597
|
role: "listitem"
|
|
2558
2598
|
},
|
|
2559
|
-
point.side === "left" ? /* @__PURE__ */
|
|
2599
|
+
point.side === "left" ? /* @__PURE__ */ React34.createElement(React34.Fragment, null, /* @__PURE__ */ React34.createElement("div", { className: cardClasses }, body, resourceSection), connector) : /* @__PURE__ */ React34.createElement(React34.Fragment, null, connector, /* @__PURE__ */ React34.createElement("div", { className: cardClasses }, body, resourceSection))
|
|
2560
2600
|
);
|
|
2561
2601
|
}
|
|
2562
2602
|
function renderGroupEntry(entry) {
|
|
@@ -2567,7 +2607,7 @@ function Timeline({
|
|
|
2567
2607
|
const wrapperStyle = { top: `${entry.progress * 100}%` };
|
|
2568
2608
|
const isExpanded = expandedGroupIds.has(entry.id);
|
|
2569
2609
|
const hasActivePoint = entry.points.some((point) => point.id === activeId);
|
|
2570
|
-
const connector = /* @__PURE__ */
|
|
2610
|
+
const connector = /* @__PURE__ */ React34.createElement(
|
|
2571
2611
|
TimelineConnector,
|
|
2572
2612
|
{
|
|
2573
2613
|
side: entry.side,
|
|
@@ -2581,7 +2621,7 @@ function Timeline({
|
|
|
2581
2621
|
hasActivePoint ? "is-active" : ""
|
|
2582
2622
|
].filter(Boolean).join(" ");
|
|
2583
2623
|
const countLabel = `${entry.count} event${entry.count > 1 ? "s" : ""}`;
|
|
2584
|
-
const header = /* @__PURE__ */
|
|
2624
|
+
const header = /* @__PURE__ */ React34.createElement("div", { className: "canopy-timeline__group-header" }, /* @__PURE__ */ React34.createElement("div", { className: "canopy-timeline__group-summary" }, /* @__PURE__ */ React34.createElement("span", { className: "canopy-timeline__point-date" }, entry.label), /* @__PURE__ */ React34.createElement("span", { className: "canopy-timeline__group-count" }, countLabel)), /* @__PURE__ */ React34.createElement(
|
|
2585
2625
|
"button",
|
|
2586
2626
|
{
|
|
2587
2627
|
type: "button",
|
|
@@ -2591,7 +2631,7 @@ function Timeline({
|
|
|
2591
2631
|
},
|
|
2592
2632
|
isExpanded ? "Hide details" : "Show details"
|
|
2593
2633
|
));
|
|
2594
|
-
const groupPoints = isExpanded ? /* @__PURE__ */
|
|
2634
|
+
const groupPoints = isExpanded ? /* @__PURE__ */ React34.createElement("div", { className: "canopy-timeline__group-points" }, entry.points.map((point) => /* @__PURE__ */ React34.createElement(
|
|
2595
2635
|
"button",
|
|
2596
2636
|
{
|
|
2597
2637
|
key: point.id,
|
|
@@ -2602,11 +2642,11 @@ function Timeline({
|
|
|
2602
2642
|
].filter(Boolean).join(" "),
|
|
2603
2643
|
onClick: () => setActiveId(point.id)
|
|
2604
2644
|
},
|
|
2605
|
-
/* @__PURE__ */
|
|
2606
|
-
/* @__PURE__ */
|
|
2645
|
+
/* @__PURE__ */ React34.createElement("span", { className: "canopy-timeline__point-date" }, point.meta.label),
|
|
2646
|
+
/* @__PURE__ */ React34.createElement("span", { className: "canopy-timeline__group-point-title" }, point.title)
|
|
2607
2647
|
))) : null;
|
|
2608
|
-
const groupCard = /* @__PURE__ */
|
|
2609
|
-
return /* @__PURE__ */
|
|
2648
|
+
const groupCard = /* @__PURE__ */ React34.createElement("div", { className: groupClasses }, header, groupPoints);
|
|
2649
|
+
return /* @__PURE__ */ React34.createElement(
|
|
2610
2650
|
"div",
|
|
2611
2651
|
{
|
|
2612
2652
|
key: entry.id,
|
|
@@ -2614,17 +2654,17 @@ function Timeline({
|
|
|
2614
2654
|
style: wrapperStyle,
|
|
2615
2655
|
role: "listitem"
|
|
2616
2656
|
},
|
|
2617
|
-
entry.side === "left" ? /* @__PURE__ */
|
|
2657
|
+
entry.side === "left" ? /* @__PURE__ */ React34.createElement(React34.Fragment, null, groupCard, connector) : /* @__PURE__ */ React34.createElement(React34.Fragment, null, connector, groupCard)
|
|
2618
2658
|
);
|
|
2619
2659
|
}
|
|
2620
|
-
return /* @__PURE__ */
|
|
2660
|
+
return /* @__PURE__ */ React34.createElement("section", { className: containerClasses, ...rest }, title ? /* @__PURE__ */ React34.createElement("h2", { className: "canopy-timeline__title" }, title) : null, description ? /* @__PURE__ */ React34.createElement("p", { className: "canopy-timeline__description" }, description) : null, rangeLabel ? /* @__PURE__ */ React34.createElement("p", { className: "canopy-timeline__range", "aria-live": "polite" }, rangeLabel) : null, /* @__PURE__ */ React34.createElement("div", { className: "canopy-timeline__body" }, /* @__PURE__ */ React34.createElement(
|
|
2621
2661
|
"div",
|
|
2622
2662
|
{
|
|
2623
2663
|
className: "canopy-timeline__list",
|
|
2624
2664
|
role: "list",
|
|
2625
2665
|
style: { minHeight: trackHeight }
|
|
2626
2666
|
},
|
|
2627
|
-
/* @__PURE__ */
|
|
2667
|
+
/* @__PURE__ */ React34.createElement("div", { className: "canopy-timeline__spine", "aria-hidden": "true" }),
|
|
2628
2668
|
renderSteps(stepsValue, effectiveRange),
|
|
2629
2669
|
groupedEntries.map((entry) => {
|
|
2630
2670
|
if (entry.type === "group") return renderGroupEntry(entry);
|
|
@@ -2639,7 +2679,7 @@ function renderSteps(stepSize, range) {
|
|
|
2639
2679
|
const markers = [];
|
|
2640
2680
|
if (startYear < endYear) {
|
|
2641
2681
|
markers.push(
|
|
2642
|
-
/* @__PURE__ */
|
|
2682
|
+
/* @__PURE__ */ React34.createElement(
|
|
2643
2683
|
"span",
|
|
2644
2684
|
{
|
|
2645
2685
|
key: "timeline-step-start",
|
|
@@ -2647,12 +2687,12 @@ function renderSteps(stepSize, range) {
|
|
|
2647
2687
|
style: { top: "0%" },
|
|
2648
2688
|
"aria-hidden": "true"
|
|
2649
2689
|
},
|
|
2650
|
-
/* @__PURE__ */
|
|
2651
|
-
/* @__PURE__ */
|
|
2690
|
+
/* @__PURE__ */ React34.createElement("span", { className: "canopy-timeline__step-line" }),
|
|
2691
|
+
/* @__PURE__ */ React34.createElement("span", { className: "canopy-timeline__step-label" }, startYear)
|
|
2652
2692
|
)
|
|
2653
2693
|
);
|
|
2654
2694
|
markers.push(
|
|
2655
|
-
/* @__PURE__ */
|
|
2695
|
+
/* @__PURE__ */ React34.createElement(
|
|
2656
2696
|
"span",
|
|
2657
2697
|
{
|
|
2658
2698
|
key: "timeline-step-end",
|
|
@@ -2660,8 +2700,8 @@ function renderSteps(stepSize, range) {
|
|
|
2660
2700
|
style: { top: "100%" },
|
|
2661
2701
|
"aria-hidden": "true"
|
|
2662
2702
|
},
|
|
2663
|
-
/* @__PURE__ */
|
|
2664
|
-
/* @__PURE__ */
|
|
2703
|
+
/* @__PURE__ */ React34.createElement("span", { className: "canopy-timeline__step-line" }),
|
|
2704
|
+
/* @__PURE__ */ React34.createElement("span", { className: "canopy-timeline__step-label" }, endYear)
|
|
2665
2705
|
)
|
|
2666
2706
|
);
|
|
2667
2707
|
}
|
|
@@ -2671,7 +2711,7 @@ function renderSteps(stepSize, range) {
|
|
|
2671
2711
|
const progress = (timestamp - range.startDate.getTime()) / range.span;
|
|
2672
2712
|
if (progress <= 0 || progress >= 1) continue;
|
|
2673
2713
|
markers.push(
|
|
2674
|
-
/* @__PURE__ */
|
|
2714
|
+
/* @__PURE__ */ React34.createElement(
|
|
2675
2715
|
"span",
|
|
2676
2716
|
{
|
|
2677
2717
|
key: `timeline-step-${year}`,
|
|
@@ -2679,8 +2719,8 @@ function renderSteps(stepSize, range) {
|
|
|
2679
2719
|
style: { top: `calc(${progress * 100}% - 0.5px)` },
|
|
2680
2720
|
"aria-hidden": "true"
|
|
2681
2721
|
},
|
|
2682
|
-
/* @__PURE__ */
|
|
2683
|
-
/* @__PURE__ */
|
|
2722
|
+
/* @__PURE__ */ React34.createElement("span", { className: "canopy-timeline__step-line" }),
|
|
2723
|
+
/* @__PURE__ */ React34.createElement("span", { className: "canopy-timeline__step-label" }, year)
|
|
2684
2724
|
)
|
|
2685
2725
|
);
|
|
2686
2726
|
}
|
|
@@ -2694,7 +2734,8 @@ function TimelinePoint() {
|
|
|
2694
2734
|
TimelinePoint.displayName = "TimelinePoint";
|
|
2695
2735
|
|
|
2696
2736
|
// ui/src/content/map/Map.jsx
|
|
2697
|
-
import
|
|
2737
|
+
import React35 from "react";
|
|
2738
|
+
import { createRoot } from "react-dom/client";
|
|
2698
2739
|
var DEFAULT_TILE_LAYERS = [
|
|
2699
2740
|
{
|
|
2700
2741
|
name: "OpenStreetMap",
|
|
@@ -2947,35 +2988,72 @@ function escapeHtml(value) {
|
|
|
2947
2988
|
if (value == null) return "";
|
|
2948
2989
|
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2949
2990
|
}
|
|
2950
|
-
function
|
|
2951
|
-
if (!marker) return
|
|
2991
|
+
function MapPopupContent({ marker }) {
|
|
2992
|
+
if (!marker) return null;
|
|
2952
2993
|
const title = marker.title || marker.manifestTitle || "";
|
|
2953
2994
|
const summary = marker.summary || marker.manifestSummary || "";
|
|
2954
2995
|
const href = marker.href ? withBasePath(marker.href) : "";
|
|
2955
2996
|
const thumbnail = marker.thumbnail || "";
|
|
2956
2997
|
const thumbWidth = marker.thumbnailWidth;
|
|
2957
2998
|
const thumbHeight = marker.thumbnailHeight;
|
|
2958
|
-
const
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2999
|
+
const manifestLinks = Array.isArray(marker.manifests) ? marker.manifests.filter((entry) => entry && (entry.href || entry.title)) : [];
|
|
3000
|
+
const normalizedManifests = manifestLinks.map((manifest) => ({
|
|
3001
|
+
...manifest,
|
|
3002
|
+
href: manifest.href ? withBasePath(manifest.href) : manifest.href || ""
|
|
3003
|
+
}));
|
|
3004
|
+
return /* @__PURE__ */ React35.createElement("div", { className: "canopy-map__popup" }, thumbnail ? /* @__PURE__ */ React35.createElement("div", { className: "canopy-map__popup-media" }, /* @__PURE__ */ React35.createElement(
|
|
3005
|
+
"img",
|
|
3006
|
+
{
|
|
3007
|
+
src: thumbnail,
|
|
3008
|
+
alt: "",
|
|
3009
|
+
loading: "lazy",
|
|
3010
|
+
width: typeof thumbWidth === "number" && thumbWidth > 0 ? thumbWidth : void 0,
|
|
3011
|
+
height: typeof thumbHeight === "number" && thumbHeight > 0 ? thumbHeight : void 0
|
|
3012
|
+
}
|
|
3013
|
+
)) : null, /* @__PURE__ */ React35.createElement("div", { className: "canopy-map__popup-body" }, title ? href ? /* @__PURE__ */ React35.createElement("a", { href, className: "canopy-map__popup-title" }, title) : /* @__PURE__ */ React35.createElement("span", { className: "canopy-map__popup-title" }, title) : null, summary ? /* @__PURE__ */ React35.createElement("p", { className: "canopy-map__popup-summary" }, summary) : null, marker.detailsHtml ? /* @__PURE__ */ React35.createElement(
|
|
3014
|
+
"div",
|
|
3015
|
+
{
|
|
3016
|
+
className: "canopy-map__popup-details",
|
|
3017
|
+
dangerouslySetInnerHTML: { __html: marker.detailsHtml }
|
|
3018
|
+
}
|
|
3019
|
+
) : null, !summary && !marker.detailsHtml && href && !title ? /* @__PURE__ */ React35.createElement("a", { href, className: "canopy-map__popup-link" }, "View item") : null, normalizedManifests.length ? /* @__PURE__ */ React35.createElement("div", { className: "canopy-map__popup-manifests" }, /* @__PURE__ */ React35.createElement("div", { className: "canopy-map__popup-manifests-list" }, normalizedManifests.map((manifest, index) => /* @__PURE__ */ React35.createElement(
|
|
3020
|
+
"div",
|
|
3021
|
+
{
|
|
3022
|
+
key: manifest.id || manifest.href || `manifest-${index}`,
|
|
3023
|
+
className: "canopy-map__popup-manifests-item"
|
|
3024
|
+
},
|
|
3025
|
+
/* @__PURE__ */ React35.createElement(ReferencedManifestCard, { manifest })
|
|
3026
|
+
)))) : null));
|
|
3027
|
+
}
|
|
3028
|
+
function renderPopup(marker) {
|
|
3029
|
+
if (!marker || typeof document === "undefined") return null;
|
|
3030
|
+
const container = document.createElement("div");
|
|
3031
|
+
let root = null;
|
|
3032
|
+
try {
|
|
3033
|
+
root = createRoot(container);
|
|
3034
|
+
root.render(/* @__PURE__ */ React35.createElement(MapPopupContent, { marker }));
|
|
3035
|
+
} catch (error) {
|
|
3036
|
+
if (root) {
|
|
3037
|
+
try {
|
|
3038
|
+
root.unmount();
|
|
3039
|
+
} catch (_) {
|
|
3040
|
+
}
|
|
3041
|
+
root = null;
|
|
3042
|
+
}
|
|
3043
|
+
const fallbackTitle = marker.title || marker.summary || marker.href || "Location";
|
|
3044
|
+
container.innerHTML = `<div class="canopy-map__popup"><div class="canopy-map__popup-body"><span class="canopy-map__popup-title">${escapeHtml(fallbackTitle)}</span></div></div>`;
|
|
2976
3045
|
}
|
|
2977
|
-
|
|
2978
|
-
|
|
3046
|
+
return {
|
|
3047
|
+
element: container,
|
|
3048
|
+
destroy() {
|
|
3049
|
+
if (!root) return;
|
|
3050
|
+
try {
|
|
3051
|
+
root.unmount();
|
|
3052
|
+
} catch (_) {
|
|
3053
|
+
}
|
|
3054
|
+
root = null;
|
|
3055
|
+
}
|
|
3056
|
+
};
|
|
2979
3057
|
}
|
|
2980
3058
|
function normalizeCustomMarkers(points) {
|
|
2981
3059
|
if (!Array.isArray(points)) return [];
|
|
@@ -2995,6 +3073,7 @@ function normalizeCustomMarkers(points) {
|
|
|
2995
3073
|
thumbnail: point.thumbnail || "",
|
|
2996
3074
|
thumbnailWidth: point.thumbnailWidth,
|
|
2997
3075
|
thumbnailHeight: point.thumbnailHeight,
|
|
3076
|
+
manifests: Array.isArray(point.manifests) ? point.manifests : [],
|
|
2998
3077
|
type: "custom"
|
|
2999
3078
|
};
|
|
3000
3079
|
}).filter(Boolean);
|
|
@@ -3072,26 +3151,26 @@ function Map2({
|
|
|
3072
3151
|
defaultCenter = null,
|
|
3073
3152
|
defaultZoom = null
|
|
3074
3153
|
} = {}) {
|
|
3075
|
-
const containerRef =
|
|
3076
|
-
const mapRef =
|
|
3077
|
-
const layerRef =
|
|
3078
|
-
const [leafletLib, setLeafletLib] =
|
|
3079
|
-
const [leafletError, setLeafletError] =
|
|
3154
|
+
const containerRef = React35.useRef(null);
|
|
3155
|
+
const mapRef = React35.useRef(null);
|
|
3156
|
+
const layerRef = React35.useRef(null);
|
|
3157
|
+
const [leafletLib, setLeafletLib] = React35.useState(() => resolveGlobalLeaflet());
|
|
3158
|
+
const [leafletError, setLeafletError] = React35.useState(null);
|
|
3080
3159
|
const datasetInfo = navDataset && typeof navDataset === "object" ? navDataset : null;
|
|
3081
3160
|
const datasetHref = datasetInfo && datasetInfo.href || "/api/navplace.json";
|
|
3082
3161
|
const datasetVersion = datasetInfo && datasetInfo.version;
|
|
3083
3162
|
const datasetHasFeatures = !!(datasetInfo && datasetInfo.hasFeatures);
|
|
3084
|
-
const [navState, setNavState] =
|
|
3163
|
+
const [navState, setNavState] = React35.useState(() => ({
|
|
3085
3164
|
loading: false,
|
|
3086
3165
|
error: null,
|
|
3087
3166
|
markers: []
|
|
3088
3167
|
}));
|
|
3089
|
-
const [iiifTargets, setIiifTargets] =
|
|
3168
|
+
const [iiifTargets, setIiifTargets] = React35.useState(() => ({
|
|
3090
3169
|
loading: false,
|
|
3091
3170
|
error: null,
|
|
3092
3171
|
keys: []
|
|
3093
3172
|
}));
|
|
3094
|
-
|
|
3173
|
+
React35.useEffect(() => {
|
|
3095
3174
|
if (!iiifContent) {
|
|
3096
3175
|
setIiifTargets({ loading: false, error: null, keys: [] });
|
|
3097
3176
|
return;
|
|
@@ -3139,7 +3218,7 @@ function Map2({
|
|
|
3139
3218
|
const navTargets = iiifTargets.keys || [];
|
|
3140
3219
|
const navTargetsKey = navTargets.join("|");
|
|
3141
3220
|
const shouldFetchNav = datasetHasFeatures && navTargets.length > 0;
|
|
3142
|
-
|
|
3221
|
+
React35.useEffect(() => {
|
|
3143
3222
|
if (!shouldFetchNav) {
|
|
3144
3223
|
setNavState({ loading: false, error: null, markers: [] });
|
|
3145
3224
|
return void 0;
|
|
@@ -3171,7 +3250,7 @@ function Map2({
|
|
|
3171
3250
|
cancelled = true;
|
|
3172
3251
|
};
|
|
3173
3252
|
}, [datasetHref, datasetVersion, navTargetsKey, shouldFetchNav]);
|
|
3174
|
-
|
|
3253
|
+
React35.useEffect(() => {
|
|
3175
3254
|
if (leafletLib) return;
|
|
3176
3255
|
let cancelled = false;
|
|
3177
3256
|
waitForLeaflet().then((lib) => {
|
|
@@ -3183,7 +3262,7 @@ function Map2({
|
|
|
3183
3262
|
cancelled = true;
|
|
3184
3263
|
};
|
|
3185
3264
|
}, [leafletLib]);
|
|
3186
|
-
const navMatchMap =
|
|
3265
|
+
const navMatchMap = React35.useMemo(() => {
|
|
3187
3266
|
const matchMap = createMarkerMap();
|
|
3188
3267
|
(navState.markers || []).forEach((marker) => {
|
|
3189
3268
|
if (!marker || !Array.isArray(marker.matchKeys)) return;
|
|
@@ -3194,7 +3273,7 @@ function Map2({
|
|
|
3194
3273
|
});
|
|
3195
3274
|
return matchMap;
|
|
3196
3275
|
}, [navState.markers]);
|
|
3197
|
-
const normalizedCustom =
|
|
3276
|
+
const normalizedCustom = React35.useMemo(() => {
|
|
3198
3277
|
return normalizeCustomMarkers(customPoints).map((point) => {
|
|
3199
3278
|
if (!point || point.thumbnail || !point.href) return point;
|
|
3200
3279
|
const match = navMatchMap.get(normalizeKey(point.href));
|
|
@@ -3209,11 +3288,11 @@ function Map2({
|
|
|
3209
3288
|
};
|
|
3210
3289
|
});
|
|
3211
3290
|
}, [customPoints, navMatchMap]);
|
|
3212
|
-
const allMarkers =
|
|
3291
|
+
const allMarkers = React35.useMemo(() => {
|
|
3213
3292
|
return [...navState.markers || [], ...normalizedCustom];
|
|
3214
3293
|
}, [navState.markers, normalizedCustom]);
|
|
3215
|
-
const clusterOptions =
|
|
3216
|
-
|
|
3294
|
+
const clusterOptions = React35.useMemo(() => buildClusterOptions(leafletLib), [leafletLib]);
|
|
3295
|
+
React35.useEffect(() => {
|
|
3217
3296
|
if (!containerRef.current || mapRef.current || !leafletLib) return void 0;
|
|
3218
3297
|
const map = leafletLib.map(containerRef.current, {
|
|
3219
3298
|
zoomControl: true,
|
|
@@ -3251,7 +3330,7 @@ function Map2({
|
|
|
3251
3330
|
layerRef.current = null;
|
|
3252
3331
|
};
|
|
3253
3332
|
}, [tileLayers, scrollWheelZoom, cluster, clusterOptions, leafletLib]);
|
|
3254
|
-
|
|
3333
|
+
React35.useEffect(() => {
|
|
3255
3334
|
const map = mapRef.current;
|
|
3256
3335
|
const layer = layerRef.current;
|
|
3257
3336
|
if (!map || !layer || !leafletLib) return;
|
|
@@ -3267,11 +3346,33 @@ function Map2({
|
|
|
3267
3346
|
const icon = buildMarkerIcon(marker, leafletLib);
|
|
3268
3347
|
const leafletMarker = leafletLib.marker(latlng, icon ? { icon } : void 0);
|
|
3269
3348
|
const popup = renderPopup(marker);
|
|
3270
|
-
if (popup) {
|
|
3349
|
+
if (popup && popup.element) {
|
|
3271
3350
|
try {
|
|
3272
|
-
leafletMarker.bindPopup(popup);
|
|
3351
|
+
leafletMarker.bindPopup(popup.element);
|
|
3352
|
+
if (typeof popup.destroy === "function") {
|
|
3353
|
+
let disposed = false;
|
|
3354
|
+
const cleanup = () => {
|
|
3355
|
+
if (disposed) return;
|
|
3356
|
+
disposed = true;
|
|
3357
|
+
try {
|
|
3358
|
+
leafletMarker.off("popupclose", cleanup);
|
|
3359
|
+
leafletMarker.off("popupremove", cleanup);
|
|
3360
|
+
leafletMarker.off("remove", cleanup);
|
|
3361
|
+
} catch (_) {
|
|
3362
|
+
}
|
|
3363
|
+
popup.destroy();
|
|
3364
|
+
};
|
|
3365
|
+
leafletMarker.on("popupclose", cleanup);
|
|
3366
|
+
leafletMarker.on("popupremove", cleanup);
|
|
3367
|
+
leafletMarker.on("remove", cleanup);
|
|
3368
|
+
}
|
|
3273
3369
|
} catch (_) {
|
|
3370
|
+
if (typeof popup.destroy === "function") {
|
|
3371
|
+
popup.destroy();
|
|
3372
|
+
}
|
|
3274
3373
|
}
|
|
3374
|
+
} else if (popup && typeof popup.destroy === "function") {
|
|
3375
|
+
popup.destroy();
|
|
3275
3376
|
}
|
|
3276
3377
|
try {
|
|
3277
3378
|
layer.addLayer(leafletMarker);
|
|
@@ -3327,14 +3428,14 @@ function Map2({
|
|
|
3327
3428
|
].filter(Boolean).join(" ");
|
|
3328
3429
|
const statusLabel = leafletError ? leafletError.message || "Failed to load map library" : !leafletLib ? "Loading map\u2026" : iiifTargets.error ? iiifTargets.error : datasetUnavailable ? "Map data is unavailable for this site." : navState.error ? navState.error : isLoadingMarkers ? "Loading map data\u2026" : !iiifContent && !hasCustomPoints ? "Add iiifContent or MapPoint markers to populate this map." : !hasMarkers ? "No map locations available." : "";
|
|
3329
3430
|
const showStatus = Boolean(statusLabel);
|
|
3330
|
-
return /* @__PURE__ */
|
|
3431
|
+
return /* @__PURE__ */ React35.createElement("div", { className: rootClass, id: id || void 0, style: style || void 0 }, /* @__PURE__ */ React35.createElement(
|
|
3331
3432
|
"div",
|
|
3332
3433
|
{
|
|
3333
3434
|
ref: containerRef,
|
|
3334
3435
|
className: "canopy-map__canvas",
|
|
3335
3436
|
style: { height: height || "600px" }
|
|
3336
3437
|
}
|
|
3337
|
-
), showStatus ? /* @__PURE__ */
|
|
3438
|
+
), showStatus ? /* @__PURE__ */ React35.createElement("div", { className: "canopy-map__status", "aria-live": "polite" }, statusLabel) : null);
|
|
3338
3439
|
}
|
|
3339
3440
|
|
|
3340
3441
|
// ui/src/content/map/MapPoint.jsx
|