ogm-viewer 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/cjs/{lerc-CT61sKYw.js → lerc-IU-tWkVO.js} +6 -6
  2. package/dist/cjs/loader.cjs.js +1 -1
  3. package/dist/cjs/{ogm-attributes.ogm-image.ogm-map.ogm-menubar.ogm-metadata.ogm-previews.ogm-settings.ogm-sidebar.ogm-viewer-DKxSOrMP.js → ogm-alerts.ogm-attributes.ogm-image.ogm-map.ogm-menubar.ogm-metadata.ogm-preview.ogm-previews.ogm-settings.ogm-sidebar.ogm-viewer-BAGJpqMB.js} +465 -98
  4. package/dist/cjs/ogm-alerts_11.cjs.entry.js +18 -0
  5. package/dist/cjs/ogm-viewer.cjs.js +1 -1
  6. package/dist/collection/assets/icons/exclamation-triangle-fill.svg +3 -0
  7. package/dist/collection/collection-manifest.json +2 -0
  8. package/dist/collection/components/ogm-alerts/ogm-alerts.css +51 -0
  9. package/dist/collection/components/ogm-alerts/ogm-alerts.js +71 -0
  10. package/dist/collection/components/ogm-alerts/ogm-alerts.test.js +26 -0
  11. package/dist/collection/components/ogm-image/ogm-image.js +57 -5
  12. package/dist/collection/components/ogm-map/ogm-map.css +34 -40
  13. package/dist/collection/components/ogm-map/ogm-map.js +86 -22
  14. package/dist/collection/components/ogm-menubar/ogm-menubar.js +23 -2
  15. package/dist/collection/components/ogm-menubar/ogm-menubar.test.js +55 -0
  16. package/dist/collection/components/ogm-preview/ogm-preview.css +16 -0
  17. package/dist/collection/components/ogm-preview/ogm-preview.js +144 -0
  18. package/dist/collection/components/ogm-preview/ogm-preview.test.js +37 -0
  19. package/dist/collection/components/ogm-previews/ogm-previews.css +3 -2
  20. package/dist/collection/components/ogm-previews/ogm-previews.js +7 -18
  21. package/dist/collection/components/ogm-previews/ogm-previews.test.js +83 -0
  22. package/dist/collection/components/ogm-settings/ogm-settings.js +1 -1
  23. package/dist/collection/components/ogm-sidebar/ogm-sidebar.js +2 -2
  24. package/dist/collection/components/ogm-viewer/ogm-viewer.css +6 -2
  25. package/dist/collection/components/ogm-viewer/ogm-viewer.js +61 -29
  26. package/dist/collection/lib/errors.js +137 -0
  27. package/dist/collection/lib/errors.test.js +96 -0
  28. package/dist/collection/lib/sources/geojson.js +2 -1
  29. package/dist/collection/lib/sources/iiif-manifest.js +2 -3
  30. package/dist/collection/lib/sources/iiif-manifest.test.js +4 -4
  31. package/dist/esm/{lerc-Bz409klg.js → lerc-yC3LNu-h.js} +1 -1
  32. package/dist/esm/loader.js +1 -1
  33. package/dist/esm/{ogm-attributes.ogm-image.ogm-map.ogm-menubar.ogm-metadata.ogm-previews.ogm-settings.ogm-sidebar.ogm-viewer-C9zV3wMe.js → ogm-alerts.ogm-attributes.ogm-image.ogm-map.ogm-menubar.ogm-metadata.ogm-preview.ogm-previews.ogm-settings.ogm-sidebar.ogm-viewer-BpVupgSB.js} +465 -100
  34. package/dist/esm/ogm-alerts_11.entry.js +2 -0
  35. package/dist/esm/ogm-viewer.js +1 -1
  36. package/dist/ogm-viewer/assets/icons/exclamation-triangle-fill.svg +3 -0
  37. package/dist/ogm-viewer/ogm-viewer.esm.js +1 -1
  38. package/dist/ogm-viewer/p-11cf948e.entry.js +1 -0
  39. package/dist/ogm-viewer/p-DPOGJxir.js +2822 -0
  40. package/dist/ogm-viewer/{p-BrovdLv6.js → p-_eTjrSO2.js} +1 -1
  41. package/dist/types/components/ogm-alerts/ogm-alerts.d.ts +6 -0
  42. package/dist/types/components/ogm-alerts/ogm-alerts.test.d.ts +1 -0
  43. package/dist/types/components/ogm-image/ogm-image.d.ts +5 -0
  44. package/dist/types/components/ogm-map/ogm-map.d.ts +7 -0
  45. package/dist/types/components/ogm-menubar/ogm-menubar.d.ts +2 -1
  46. package/dist/types/components/ogm-preview/ogm-preview.d.ts +13 -0
  47. package/dist/types/components/ogm-preview/ogm-preview.test.d.ts +1 -0
  48. package/dist/types/components/ogm-previews/ogm-previews.d.ts +1 -5
  49. package/dist/types/components/ogm-previews/ogm-previews.test.d.ts +1 -0
  50. package/dist/types/components/ogm-viewer/ogm-viewer.d.ts +8 -2
  51. package/dist/types/components.d.ts +70 -0
  52. package/dist/types/lib/errors.d.ts +13 -0
  53. package/dist/types/lib/errors.test.d.ts +1 -0
  54. package/dist/types/lib/sources/source.d.ts +1 -1
  55. package/package.json +1 -1
  56. package/readme.md +1 -0
  57. package/dist/cjs/ogm-attributes_9.cjs.entry.js +0 -16
  58. package/dist/esm/ogm-attributes_9.entry.js +0 -2
  59. package/dist/ogm-viewer/p-86b8fafc.entry.js +0 -1
  60. package/dist/ogm-viewer/p-CpCT3zlD.js +0 -2750
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ var ogmAlerts_ogmAttributes_ogmImage_ogmMap_ogmMenubar_ogmMetadata_ogmPreview_ogmPreviews_ogmSettings_ogmSidebar_ogmViewer_entry = require('./ogm-alerts.ogm-attributes.ogm-image.ogm-map.ogm-menubar.ogm-metadata.ogm-preview.ogm-previews.ogm-settings.ogm-sidebar.ogm-viewer-BAGJpqMB.js');
4
+ require('./index-mu9UK_Fb.js');
5
+
6
+
7
+
8
+ exports.ogm_alerts = ogmAlerts_ogmAttributes_ogmImage_ogmMap_ogmMenubar_ogmMetadata_ogmPreview_ogmPreviews_ogmSettings_ogmSidebar_ogmViewer_entry.OgmAlerts;
9
+ exports.ogm_attributes = ogmAlerts_ogmAttributes_ogmImage_ogmMap_ogmMenubar_ogmMetadata_ogmPreview_ogmPreviews_ogmSettings_ogmSidebar_ogmViewer_entry.OgmAttributes;
10
+ exports.ogm_image = ogmAlerts_ogmAttributes_ogmImage_ogmMap_ogmMenubar_ogmMetadata_ogmPreview_ogmPreviews_ogmSettings_ogmSidebar_ogmViewer_entry.OgmImage;
11
+ exports.ogm_map = ogmAlerts_ogmAttributes_ogmImage_ogmMap_ogmMenubar_ogmMetadata_ogmPreview_ogmPreviews_ogmSettings_ogmSidebar_ogmViewer_entry.OgmMap;
12
+ exports.ogm_menubar = ogmAlerts_ogmAttributes_ogmImage_ogmMap_ogmMenubar_ogmMetadata_ogmPreview_ogmPreviews_ogmSettings_ogmSidebar_ogmViewer_entry.OgmMenubar;
13
+ exports.ogm_metadata = ogmAlerts_ogmAttributes_ogmImage_ogmMap_ogmMenubar_ogmMetadata_ogmPreview_ogmPreviews_ogmSettings_ogmSidebar_ogmViewer_entry.OgmMetadata;
14
+ exports.ogm_preview = ogmAlerts_ogmAttributes_ogmImage_ogmMap_ogmMenubar_ogmMetadata_ogmPreview_ogmPreviews_ogmSettings_ogmSidebar_ogmViewer_entry.OgmPreview;
15
+ exports.ogm_previews = ogmAlerts_ogmAttributes_ogmImage_ogmMap_ogmMenubar_ogmMetadata_ogmPreview_ogmPreviews_ogmSettings_ogmSidebar_ogmViewer_entry.OgmPreviews;
16
+ exports.ogm_settings = ogmAlerts_ogmAttributes_ogmImage_ogmMap_ogmMenubar_ogmMetadata_ogmPreview_ogmPreviews_ogmSettings_ogmSidebar_ogmViewer_entry.OgmSettings;
17
+ exports.ogm_sidebar = ogmAlerts_ogmAttributes_ogmImage_ogmMap_ogmMenubar_ogmMetadata_ogmPreview_ogmPreviews_ogmSettings_ogmSidebar_ogmViewer_entry.OgmSidebar;
18
+ exports.ogm_viewer = ogmAlerts_ogmAttributes_ogmImage_ogmMap_ogmMenubar_ogmMetadata_ogmPreview_ogmPreviews_ogmSettings_ogmSidebar_ogmViewer_entry.OgmViewer;
@@ -19,7 +19,7 @@ var patchBrowser = () => {
19
19
 
20
20
  patchBrowser().then(async (options) => {
21
21
  await appGlobals.globalScripts();
22
- return index.bootstrapLazy([["ogm-attributes_9.cjs",[[513,"ogm-viewer",{"recordUrl":[1,"record-url"],"theme":[1],"record":[32],"previewOpacity":[32],"sidebarOpen":[32],"loadRecord":[64]},[[0,"sidebarToggled","toggleSidebar"],[0,"opacityChange","adjustPreviewOpacity"],[0,"mapLoading","setLoadingStarted"],[0,"imageLoading","setLoadingStarted"],[0,"mapIdle","setLoadingFinished"],[0,"imageLoaded","setLoadingFinished"]],{"recordUrl":[{"updateRecord":0}]}],[513,"ogm-previews",{"theme":[1],"record":[16],"previewOpacity":[2,"preview-opacity"],"sidebarPadding":[2,"sidebar-padding"],"sources":[32]},null,{"record":[{"getSources":0}]}],[513,"ogm-sidebar",{"record":[16],"theme":[1],"open":[4]}],[513,"ogm-menubar",{"record":[16],"theme":[1],"loading":[4]}],[513,"ogm-map",{"source":[16],"theme":[1],"padding":[2],"easeMapTo":[64]},[[16,"featureSelected","handleFeatureSelected"]],{"source":[{"previewSource":0}],"padding":[{"onPaddingChange":0}]}],[513,"ogm-image",{"source":[16],"theme":[1],"padding":[2]},null,{"source":[{"onSourceChange":0}],"padding":[{"onPaddingChange":0}]}],[513,"ogm-metadata",{"record":[16],"theme":[1],"fieldNames":[16],"filteredRecord":[32]}],[513,"ogm-settings",{"record":[16]},[[0,"input","handleOpacityChange"]]],[513,"ogm-attributes",{"features":[16],"currentIndex":[32]},null,{"features":[{"onFeaturesChange":0}],"currentIndex":[{"onCurrentIndexChange":0}]}]]]], options);
22
+ return index.bootstrapLazy([["ogm-alerts_11.cjs",[[513,"ogm-viewer",{"recordUrl":[1,"record-url"],"theme":[1],"hideTitle":[4,"hide-title"],"record":[32],"error":[32],"previewOpacity":[32],"sidebarOpen":[32],"loading":[32],"loadRecord":[64]},[[0,"sidebarToggled","toggleSidebar"],[0,"opacityChange","adjustPreviewOpacity"],[0,"mapLoading","setLoadingStarted"],[0,"imageLoading","setLoadingStarted"],[0,"mapIdle","setLoadingFinished"],[0,"imageLoaded","setLoadingFinished"]],{"recordUrl":[{"updateRecord":0}],"record":[{"resetLoading":0}]}],[513,"ogm-previews",{"theme":[1],"record":[16],"previewOpacity":[2,"preview-opacity"],"sidebarPadding":[2,"sidebar-padding"],"sources":[32]},null,{"record":[{"getSources":0}]}],[513,"ogm-sidebar",{"record":[16],"theme":[1],"open":[4]}],[513,"ogm-menubar",{"record":[16],"theme":[1],"loading":[4],"hideTitle":[4,"hide-title"]}],[513,"ogm-preview",{"theme":[1],"source":[16],"previewOpacity":[2,"preview-opacity"],"sidebarPadding":[2,"sidebar-padding"],"error":[32]},[[0,"previewError","handlePreviewError"]],{"source":[{"resetError":0}]}],[513,"ogm-metadata",{"record":[16],"theme":[1],"fieldNames":[16],"filteredRecord":[32]}],[513,"ogm-settings",{"record":[16]},[[0,"input","handleOpacityChange"]]],[513,"ogm-map",{"source":[16],"theme":[1],"padding":[2],"easeMapTo":[64]},[[16,"featureSelected","handleFeatureSelected"]],{"source":[{"previewSource":0}],"theme":[{"onThemeChange":0}],"padding":[{"onPaddingChange":0}]}],[513,"ogm-alerts",{"theme":[1],"error":[16]}],[513,"ogm-image",{"source":[16],"theme":[1],"padding":[2]},null,{"source":[{"onSourceChange":0}],"padding":[{"onPaddingChange":0}]}],[513,"ogm-attributes",{"features":[16],"currentIndex":[32]},null,{"features":[{"onFeaturesChange":0}],"currentIndex":[{"onCurrentIndexChange":0}]}]]]], options);
23
23
  });
24
24
 
25
25
  exports.setNonce = index.setNonce;
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-exclamation-triangle-fill" viewBox="0 0 16 16">
2
+ <path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5m.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2"/>
3
+ </svg>
@@ -1,10 +1,12 @@
1
1
  {
2
2
  "entries": [
3
+ "components/ogm-alerts/ogm-alerts.js",
3
4
  "components/ogm-attributes/ogm-attributes.js",
4
5
  "components/ogm-image/ogm-image.js",
5
6
  "components/ogm-map/ogm-map.js",
6
7
  "components/ogm-menubar/ogm-menubar.js",
7
8
  "components/ogm-metadata/ogm-metadata.js",
9
+ "components/ogm-preview/ogm-preview.js",
8
10
  "components/ogm-previews/ogm-previews.js",
9
11
  "components/ogm-settings/ogm-settings.js",
10
12
  "components/ogm-sidebar/ogm-sidebar.js",
@@ -0,0 +1,51 @@
1
+ /* Fill the space left by the preview it replaces and center the error within it */
2
+ :host {
3
+ display: flex;
4
+ width: 100%;
5
+ height: 100%;
6
+ place-items: center;
7
+ place-content: center;
8
+ background: var(--wa-color-surface-default);
9
+ }
10
+
11
+ .alerts {
12
+ padding: var(--wa-space-m);
13
+ max-width: 40rem;
14
+ }
15
+
16
+ .alert {
17
+ box-shadow: var(--wa-shadow-m);
18
+ align-items: start;
19
+ }
20
+
21
+ .alert::part(message) {
22
+ display: flex;
23
+ align-items: start;
24
+ gap: var(--wa-space-s);
25
+ width: 100%;
26
+ }
27
+
28
+ .content {
29
+ flex: auto;
30
+ min-width: 0;
31
+ }
32
+
33
+ .title {
34
+ display: block;
35
+ font-weight: var(--wa-font-weight-bold);
36
+ }
37
+
38
+ .message {
39
+ margin-top: var(--wa-space-3xs);
40
+ font-size: var(--wa-font-size-s);
41
+ }
42
+
43
+ .url {
44
+ margin-top: var(--wa-space-2xs);
45
+ font-family: var(--wa-font-family-code);
46
+ font-size: var(--wa-font-size-xs);
47
+ color: var(--wa-color-text-quiet);
48
+ overflow: hidden;
49
+ text-overflow: ellipsis;
50
+ white-space: nowrap;
51
+ }
@@ -0,0 +1,71 @@
1
+ import { Host, h } from "@stencil/core";
2
+ // Renders a single preview error, centered and filling its container. Used in place of a failed
3
+ // preview (inside <ogm-preview>) or in place of the previews entirely (when the record fails to load).
4
+ export class OgmAlerts {
5
+ theme;
6
+ error;
7
+ render() {
8
+ if (!this.error)
9
+ return null;
10
+ return (h(Host, { class: this.theme && `wa-${this.theme}` }, h("div", { class: "alerts", role: "alert", "aria-live": "assertive" }, h("wa-callout", { variant: "danger", appearance: "filled-outlined", class: "alert" }, h("wa-icon", { slot: "icon", name: "exclamation-triangle-fill", canvas: "auto" }), h("div", { class: "content" }, h("strong", { class: "title" }, this.error.title), h("div", { class: "message" }, this.error.message), this.error.url && (h("div", { class: "url", title: this.error.url }, this.error.url)))))));
11
+ }
12
+ static get is() { return "ogm-alerts"; }
13
+ static get encapsulation() { return "shadow"; }
14
+ static get originalStyleUrls() {
15
+ return {
16
+ "$": ["ogm-alerts.css"]
17
+ };
18
+ }
19
+ static get styleUrls() {
20
+ return {
21
+ "$": ["ogm-alerts.css"]
22
+ };
23
+ }
24
+ static get properties() {
25
+ return {
26
+ "theme": {
27
+ "type": "string",
28
+ "mutable": false,
29
+ "complexType": {
30
+ "original": "'light' | 'dark'",
31
+ "resolved": "\"dark\" | \"light\"",
32
+ "references": {}
33
+ },
34
+ "required": false,
35
+ "optional": false,
36
+ "docs": {
37
+ "tags": [],
38
+ "text": ""
39
+ },
40
+ "getter": false,
41
+ "setter": false,
42
+ "reflect": false,
43
+ "attribute": "theme"
44
+ },
45
+ "error": {
46
+ "type": "unknown",
47
+ "mutable": false,
48
+ "complexType": {
49
+ "original": "PreviewError",
50
+ "resolved": "PreviewError",
51
+ "references": {
52
+ "PreviewError": {
53
+ "location": "import",
54
+ "path": "../../lib/errors",
55
+ "id": "src/lib/errors.ts::PreviewError",
56
+ "referenceLocation": "PreviewError"
57
+ }
58
+ }
59
+ },
60
+ "required": false,
61
+ "optional": true,
62
+ "docs": {
63
+ "tags": [],
64
+ "text": ""
65
+ },
66
+ "getter": false,
67
+ "setter": false
68
+ }
69
+ };
70
+ }
71
+ }
@@ -0,0 +1,26 @@
1
+ import { render, describe, it, expect, h } from "@stencil/vitest";
2
+ import { referenceError } from "../../lib/errors";
3
+ // Mock a network failure on load
4
+ const makeError = (url) => referenceError(new TypeError('Failed to fetch'), 'GeoJSON', url);
5
+ describe('ogm-alerts', () => {
6
+ it('renders nothing when there is no error', async () => {
7
+ const { root } = await render(h("ogm-alerts", null));
8
+ const shadowRoot = root.shadowRoot;
9
+ expect(shadowRoot.querySelector('.alerts')).toBeNull();
10
+ });
11
+ it('renders a danger callout with the error title, message, and url', async () => {
12
+ const { root } = await render(h("ogm-alerts", { error: makeError('http://example.com/data.json') }));
13
+ const shadowRoot = root.shadowRoot;
14
+ const callout = shadowRoot.querySelector('wa-callout');
15
+ expect(callout).not.toBeNull();
16
+ expect(callout.getAttribute('variant')).toBe('danger');
17
+ expect(callout.textContent).toContain('GeoJSON');
18
+ expect(callout.textContent).toContain('CORS');
19
+ expect(callout.textContent).toContain('http://example.com/data.json');
20
+ });
21
+ it('omits the url line when the error has no url', async () => {
22
+ const { root } = await render(h("ogm-alerts", { error: makeError() }));
23
+ const shadowRoot = root.shadowRoot;
24
+ expect(shadowRoot.querySelector('.url')).toBeNull();
25
+ });
26
+ });
@@ -1,6 +1,7 @@
1
1
  import { h, Host } from "@stencil/core";
2
2
  import { Viewer } from "openseadragon";
3
3
  import { getElement, findElement } from "../../lib/elements";
4
+ import { referenceError } from "../../lib/errors";
4
5
  export class OgmImage {
5
6
  el;
6
7
  source;
@@ -8,8 +9,11 @@ export class OgmImage {
8
9
  padding = 0;
9
10
  imageLoaded;
10
11
  imageLoading;
12
+ previewError;
11
13
  // OpenSeadragon viewer instance
12
14
  viewer;
15
+ // Guards against reporting more than one error per load attempt
16
+ errorReported = false;
13
17
  // Set up OpenSeadragon viewer on load
14
18
  async componentDidLoad() {
15
19
  this.viewer = new Viewer({
@@ -26,10 +30,21 @@ export class OgmImage {
26
30
  nextButton: getElement(this.el, '.next'),
27
31
  previousButton: getElement(this.el, '.prev'),
28
32
  });
33
+ // Clear loading state whether we succeeded or failed
29
34
  this.viewer.addHandler('open', () => this.imageLoaded.emit());
35
+ // Surface OpenSeaDragon decode errors here
36
+ this.viewer.addHandler('open-failed', event => {
37
+ this.imageLoaded.emit();
38
+ this.reportError(new Error(event.message));
39
+ });
40
+ // If we do have a source, load the images
30
41
  if (this.source)
31
42
  await this.loadImages();
32
43
  }
44
+ // Destroy the viewer when we are removed from the DOM
45
+ disconnectedCallback() {
46
+ this.viewer?.destroy();
47
+ }
33
48
  // Update preview when source changes
34
49
  async onSourceChange() {
35
50
  if (this.source)
@@ -46,14 +61,29 @@ export class OgmImage {
46
61
  // Get all of the IIIF image URLs and send them to OpenSeadragon
47
62
  // This makes a request to fetch and cache the manifest
48
63
  async loadImages() {
64
+ this.errorReported = false;
49
65
  this.imageLoading.emit();
50
- const images = await this.source.getIIIFImageUrls();
51
- if (!images)
52
- throw new Error('No IIIF images found for source');
53
- this.viewer.open(images);
66
+ try {
67
+ const images = await this.source.getIIIFImageUrls();
68
+ if (!images)
69
+ throw new Error('No IIIF images found for source');
70
+ this.viewer.open(images);
71
+ }
72
+ catch (error) {
73
+ console.error(`Error loading IIIF images for ${this.source.url}:`, error);
74
+ this.imageLoaded.emit();
75
+ this.reportError(error);
76
+ }
77
+ }
78
+ // Emit a single preview error per load attempt
79
+ reportError(error) {
80
+ if (this.errorReported || !this.source)
81
+ return;
82
+ this.errorReported = true;
83
+ this.previewError.emit(referenceError(error, this.source.label(), this.source.url));
54
84
  }
55
85
  render() {
56
- return (h(Host, { key: '155b1ad9e24bc0148c90fa6c0d456a09437e08b6' }, h("div", { key: 'e0cf7750187e9a322f72c59a23f40e5897f31756', id: "openseadragon" }, h("div", { key: '59fadbbbe2e10fa53a0e1a3b311eef2c483d0800', class: "controls" }, h("wa-button", { key: '8c2323085fef8326b663c26aaa42dee935b09f0b', class: "zoom-in", size: "s", appearance: "filled-outlined", pill: true }, h("wa-icon", { key: '129a1e11cf602e922a977a35c9d1001cfb64dc4c', name: "zoom-in", label: "Zoom In", canvas: "auto" })), h("wa-button", { key: '944656ac675915dc589ed72b7cc76a9273a6dc5f', class: "zoom-out", size: "s", appearance: "filled-outlined", pill: true }, h("wa-icon", { key: 'b76f8cda3420937d45431e9d41a3eeac116b9fad', name: "zoom-out", label: "Zoom Out", canvas: "auto" })), h("wa-button", { key: 'c1d11fae3106ef08de6e7de68fdfdcdbe28a06a0', class: "home", size: "s", appearance: "filled-outlined", pill: true }, h("wa-icon", { key: '1a6cc9c1f3b49ef2284faa0c506afe558bfc5e73', name: "house", label: "Reset View", canvas: "auto" })), h("wa-button", { key: '55924e70820e7118745115b08b8b0fce17ffd6c0', class: "full-page", size: "s", appearance: "filled-outlined", pill: true }, h("wa-icon", { key: '0e719e259f142cfec5a8fd0276f6beee2b522e51', name: "arrows-fullscreen", label: "Full Screen", canvas: "auto" })), h("wa-button", { key: '76335703d0b4e9944bde2d077e26db7fa3aba223', class: "next", size: "s", appearance: "filled-outlined", pill: true }, h("wa-icon", { key: 'a543fa9e9822eafb31c35700a1f19df8d23225b1', name: "arrow-right", label: "Next", canvas: "auto" })), h("wa-button", { key: 'a2680c1b66ecea112a01327af560fb1b49607d09', class: "prev", size: "s", appearance: "filled-outlined", pill: true }, h("wa-icon", { key: '00d8226e18ecb42471601b005f90198b8baa14d2', name: "arrow-left", label: "Previous", canvas: "auto" }))))));
86
+ return (h(Host, { key: 'ebbebaddac72024a3b311e0e0e501267a83d1e95' }, h("div", { key: '4f171256535cfd11430e2269fda389ffae3cedae', id: "openseadragon" }, h("div", { key: 'c308186a13fd6b8e0b1fadbf1fd1e6924631f9d6', class: "controls" }, h("wa-button", { key: '1f9e0c4c6d881bf09d7ceb160f470992d71ebe07', class: "zoom-in", size: "s", appearance: "filled-outlined", pill: true }, h("wa-icon", { key: 'cbacb0a388de2c7f349e25172027874a0224a678', name: "zoom-in", label: "Zoom In", canvas: "auto" })), h("wa-button", { key: 'dd8d7e9c5584b124ae105b2187100964f5e3a02a', class: "zoom-out", size: "s", appearance: "filled-outlined", pill: true }, h("wa-icon", { key: '2b5acf2e07c99df9556692bf534bb3847506e4d4', name: "zoom-out", label: "Zoom Out", canvas: "auto" })), h("wa-button", { key: '5534a5c77ea20a1f77544e5acd99d4247dd3d287', class: "home", size: "s", appearance: "filled-outlined", pill: true }, h("wa-icon", { key: 'faadb8ea57bf1b831b292eaf66bcea39b7e215d1', name: "house", label: "Reset View", canvas: "auto" })), h("wa-button", { key: '8979cf58a94da89e0e2d132a92fe45323f07de01', class: "full-page", size: "s", appearance: "filled-outlined", pill: true }, h("wa-icon", { key: '12cb3224e7f14258376c1c0bf6ffc5abda5c2252', name: "arrows-fullscreen", label: "Full Screen", canvas: "auto" })), h("wa-button", { key: 'e79c37f27ff14d5c4991ff30ff88aada467214ec', class: "next", size: "s", appearance: "filled-outlined", pill: true }, h("wa-icon", { key: 'ee10870ace583257393c53ba12b73ab3b015371b', name: "arrow-right", label: "Next", canvas: "auto" })), h("wa-button", { key: '9d8999e199b7b6338261eb16a655d2199520cf22', class: "prev", size: "s", appearance: "filled-outlined", pill: true }, h("wa-icon", { key: 'fde1a9022b9e5f90e269c75ce87943808031e021', name: "arrow-left", label: "Previous", canvas: "auto" }))))));
57
87
  }
58
88
  static get is() { return "ogm-image"; }
59
89
  static get encapsulation() { return "shadow"; }
@@ -163,6 +193,28 @@ export class OgmImage {
163
193
  "resolved": "void",
164
194
  "references": {}
165
195
  }
196
+ }, {
197
+ "method": "previewError",
198
+ "name": "previewError",
199
+ "bubbles": true,
200
+ "cancelable": true,
201
+ "composed": true,
202
+ "docs": {
203
+ "tags": [],
204
+ "text": ""
205
+ },
206
+ "complexType": {
207
+ "original": "PreviewError",
208
+ "resolved": "PreviewError",
209
+ "references": {
210
+ "PreviewError": {
211
+ "location": "import",
212
+ "path": "../../lib/errors",
213
+ "id": "src/lib/errors.ts::PreviewError",
214
+ "referenceLocation": "PreviewError"
215
+ }
216
+ }
217
+ }
166
218
  }];
167
219
  }
168
220
  static get elementRef() { return "el"; }
@@ -6,6 +6,11 @@
6
6
  position: relative;
7
7
  }
8
8
 
9
+ /* Color of the map background ("space" around the globe) */
10
+ .maplibregl-canvas {
11
+ background-color: var(--wa-color-surface-default);
12
+ }
13
+
9
14
  /* Inspection popups. Vertical scrolling (when content is too tall) is handled by the
10
15
  <wa-scroller> inside ogm-attributes, not here - that's what gives it the fading shadow cue. */
11
16
  .maplibregl-popup-content {
@@ -14,6 +19,34 @@
14
19
  padding: 0;
15
20
  box-shadow: var(--wa-shadow-l);
16
21
  border: var(--wa-panel-border-width) solid var(--wa-color-surface-border);
22
+ color: var(--wa-color-text-normal);
23
+ background-color: var(--wa-color-surface-default);
24
+
25
+ .maplibregl-popup-close-button {
26
+ background-color: var(--wa-color-surface-default);
27
+ border-radius: 10px;
28
+ }
29
+ }
30
+
31
+ /* Popup tip (the little triangle) */
32
+ .maplibregl-popup-anchor-right .maplibregl-popup-tip {
33
+ border-left-color: var(--wa-color-surface-border);
34
+ }
35
+
36
+ .maplibregl-popup-anchor-left .maplibregl-popup-tip {
37
+ border-right-color: var(--wa-color-surface-border);
38
+ }
39
+
40
+ .maplibregl-popup-anchor-bottom .maplibregl-popup-tip,
41
+ .maplibregl-popup-anchor-bottom-right .maplibregl-popup-tip,
42
+ .maplibregl-popup-anchor-bottom-left .maplibregl-popup-tip {
43
+ border-top-color: var(--wa-color-surface-border);
44
+ }
45
+
46
+ .maplibregl-popup-anchor-top .maplibregl-popup-tip,
47
+ .maplibregl-popup-anchor-top-right .maplibregl-popup-tip,
48
+ .maplibregl-popup-anchor-top-left .maplibregl-popup-tip {
49
+ border-bottom-color: var(--wa-color-surface-border);
17
50
  }
18
51
 
19
52
  /* Dark mode map adjustments */
@@ -22,10 +55,6 @@
22
55
  stroke: white;
23
56
  }
24
57
 
25
- .maplibregl-canvas {
26
- background-color: black;
27
- }
28
-
29
58
  .maplibregl-ctrl.maplibregl-ctrl-group {
30
59
  filter: invert(1);
31
60
  }
@@ -34,43 +63,8 @@
34
63
  .maplibregl-ctrl-group button:focus:focus-visible {
35
64
  box-shadow: 0 0 2px 2px #ff6900;
36
65
  }
66
+
37
67
  .maplibregl-ctrl button.maplibregl-ctrl-globe-enabled .maplibregl-ctrl-icon {
38
68
  filter: invert(1);
39
69
  }
40
-
41
- details.maplibregl-ctrl.maplibregl-ctrl-attrib {
42
- background-color: black;
43
- color: #999;
44
- }
45
- details.maplibregl-ctrl.maplibregl-ctrl-attrib a {
46
- color: #999;
47
- }
48
- details.maplibregl-ctrl.maplibregl-ctrl-attrib.maplibregl-compact-show .maplibregl-ctrl-attrib-button {
49
- background-color: #999;
50
- }
51
-
52
- div.maplibregl-popup-content {
53
- background-color: black;
54
- color: #ccc;
55
- }
56
-
57
- div.maplibregl-popup-anchor-right div.maplibregl-popup-tip {
58
- border-left-color: black;
59
- }
60
-
61
- div.maplibregl-popup-anchor-left div.maplibregl-popup-tip {
62
- border-right-color: black;
63
- }
64
-
65
- div.maplibregl-popup-anchor-bottom div.maplibregl-popup-tip,
66
- div.maplibregl-popup-anchor-bottom-right div.maplibregl-popup-tip,
67
- div.maplibregl-popup-anchor-bottom-left div.maplibregl-popup-tip {
68
- border-top-color: black;
69
- }
70
-
71
- div.maplibregl-popup-anchor-top div.maplibregl-popup-tip,
72
- div.maplibregl-popup-anchor-top-right div.maplibregl-popup-tip,
73
- div.maplibregl-popup-anchor-top-left div.maplibregl-popup-tip {
74
- border-bottom-color: black;
75
- }
76
70
  }
@@ -8,6 +8,7 @@ import PMTilesSource from "../../lib/sources/pmtiles";
8
8
  import RasterSource from "../../lib/sources/raster";
9
9
  import WmsSource from "../../lib/sources/wms";
10
10
  import { getElement } from "../../lib/elements";
11
+ import { referenceError } from "../../lib/errors";
11
12
  import CogPreviewer from "../../lib/previewers/cog";
12
13
  import GeoJSONPreviewer from "../../lib/previewers/geojson";
13
14
  import OpenIndexMapPreviewer from "../../lib/previewers/openindexmap";
@@ -26,6 +27,9 @@ export class OgmMap {
26
27
  padding = 0;
27
28
  mapIdle;
28
29
  mapLoading;
30
+ previewError;
31
+ // Guards against reporting more than one error per load attempt
32
+ errorReported = false;
29
33
  // MapLibre map instance and popup instance for feature info display
30
34
  map;
31
35
  mapTheme;
@@ -50,10 +54,10 @@ export class OgmMap {
50
54
  });
51
55
  this.getContainer();
52
56
  this.addControls();
53
- this.map.on('idle', () => this.mapIdle.emit());
54
57
  this.map.on('load', () => this.previewSource(this.source));
55
58
  this.map.on('mousemove', this.handleHover.bind(this));
56
59
  this.map.on('click', this.handleClick.bind(this));
60
+ this.map.on('error', this.handleMapError.bind(this));
57
61
  // View as a globe with atmosphere effects
58
62
  this.map.on('style.load', () => {
59
63
  this.map.setProjection({ type: 'globe' });
@@ -118,30 +122,64 @@ export class OgmMap {
118
122
  // Do nothing if we didn't get passed a source
119
123
  if (!source)
120
124
  return;
125
+ // Fresh load attempt: allow one error to be reported again for this source
126
+ this.errorReported = false;
121
127
  // Indicate loading state so we can show the spinner
122
128
  this.mapLoading.emit();
123
- // Close popup if one is open
124
- this.clearFeatureSelection();
125
- this.destroyPopup();
126
- // Clear existing preview
127
- if (this.previewer) {
128
- await this.previewer.clearPreview();
129
- this.previewer = undefined;
129
+ try {
130
+ // Close popup if one is open
131
+ this.clearFeatureSelection();
132
+ this.destroyPopup();
133
+ // Clear existing preview
134
+ if (this.previewer) {
135
+ await this.previewer.clearPreview();
136
+ this.previewer = undefined;
137
+ }
138
+ // Get the appropriate previewer for our source and preview it
139
+ this.previewer = await this.getMapPreviewer(this.map);
140
+ if (!this.previewer)
141
+ throw new Error(`No previewer found for source: ${source.constructor.name}`);
142
+ await this.previewer.preview();
143
+ // Fit to bounds from the record; the spinner stays up until the map finishes moving
144
+ const bounds = await this.source.getBounds();
145
+ if (bounds)
146
+ await this.fitMapBounds(bounds);
147
+ }
148
+ catch (error) {
149
+ console.error(`Error previewing source ${source.url}:`, error);
150
+ if (!this.errorReported) {
151
+ this.errorReported = true;
152
+ this.previewError.emit(referenceError(error, source.label(), source.url));
153
+ }
154
+ }
155
+ finally {
156
+ this.mapIdle.emit();
130
157
  }
131
- // Get the appropriate previewer for our source and preview it
132
- this.previewer = await this.getMapPreviewer(this.map);
133
- if (!this.previewer)
134
- throw new Error(`No previewer found for source: ${source.constructor.name}`);
135
- await this.previewer.preview();
136
- // Fit to bounds from the record
137
- const bounds = await this.source.getBounds();
138
- if (bounds)
139
- await this.fitMapBounds(bounds);
140
- // Emit map idle event to signal that the map is ready
141
- this.mapIdle.emit();
142
158
  }
143
- // Fit the map to the given bounds; return when moving is finished
159
+ // When the theme changes, swap the basemap to match.
160
+ onThemeChange() {
161
+ if (!this.map)
162
+ return;
163
+ this.map.setStyle(this.mapTheme.getBaseMapStyle());
164
+ this.map.once('style.load', async () => await this.previewSource(this.source));
165
+ }
166
+ // Surface MapLibre errors tied to the current preview source, skipping the
167
+ // noise from basemap/glyph/sprite loads, and deduped to a single alert per load attempt.
168
+ handleMapError(event) {
169
+ if (this.errorReported || !this.source || !this.previewer)
170
+ return;
171
+ if (event.sourceId !== this.previewer.sourceId)
172
+ return;
173
+ this.errorReported = true;
174
+ this.previewError.emit(referenceError(event.error, this.source.label(), this.source.url));
175
+ }
176
+ // Fit the map to the given bounds; resolve once the move finishes. Guard the case where the bounds
177
+ // can't produce a camera - e.g. sidebar padding wider than the viewport, or a hidden, zero-size
178
+ // inactive tab panel - because then fitBounds won't move, 'moveend' never fires, and awaiting this
179
+ // promise (and the loading state that depends on it) would hang forever.
144
180
  async fitMapBounds(bounds) {
181
+ if (!this.map.cameraForBounds(bounds))
182
+ return;
145
183
  return new Promise(resolve => {
146
184
  this.map.once('moveend', () => resolve());
147
185
  this.map.fitBounds(bounds);
@@ -213,7 +251,8 @@ export class OgmMap {
213
251
  }
214
252
  // Create a new popup and set its content and location
215
253
  createPopup(location) {
216
- this.popup = new maplibregl.Popup({ maxWidth: 'none', closeButton: false }).setDOMContent(this.attributesEl).setLngLat(location).addTo(this.map);
254
+ this.popup = new maplibregl.Popup({ maxWidth: 'none' }).setDOMContent(this.attributesEl).setLngLat(location).addTo(this.map);
255
+ this.popup.on('close', this.clearFeatureSelection.bind(this));
217
256
  }
218
257
  // Remove popup from the map and clear the reference
219
258
  destroyPopup() {
@@ -227,7 +266,7 @@ export class OgmMap {
227
266
  return this.previewer?.layerIds || [];
228
267
  }
229
268
  render() {
230
- return h("div", { key: '1c38c0508638813aee913dce29f533845393023b', id: "map", class: `wa-${this.theme}` });
269
+ return h("div", { key: 'c683bc9d81e84caa58d4575d0812c5fcdb096e28', id: "map", class: `wa-${this.theme}` });
231
270
  }
232
271
  static get is() { return "ogm-map"; }
233
272
  static get encapsulation() { return "shadow"; }
@@ -337,6 +376,28 @@ export class OgmMap {
337
376
  "resolved": "void",
338
377
  "references": {}
339
378
  }
379
+ }, {
380
+ "method": "previewError",
381
+ "name": "previewError",
382
+ "bubbles": true,
383
+ "cancelable": true,
384
+ "composed": true,
385
+ "docs": {
386
+ "tags": [],
387
+ "text": ""
388
+ },
389
+ "complexType": {
390
+ "original": "PreviewError",
391
+ "resolved": "PreviewError",
392
+ "references": {
393
+ "PreviewError": {
394
+ "location": "import",
395
+ "path": "../../lib/errors",
396
+ "id": "src/lib/errors.ts::PreviewError",
397
+ "referenceLocation": "PreviewError"
398
+ }
399
+ }
400
+ }
340
401
  }];
341
402
  }
342
403
  static get methods() {
@@ -373,6 +434,9 @@ export class OgmMap {
373
434
  return [{
374
435
  "propName": "source",
375
436
  "methodName": "previewSource"
437
+ }, {
438
+ "propName": "theme",
439
+ "methodName": "onThemeChange"
376
440
  }, {
377
441
  "propName": "padding",
378
442
  "methodName": "onPaddingChange"
@@ -2,10 +2,11 @@ import { h } from "@stencil/core";
2
2
  export class OgmMenubar {
3
3
  record;
4
4
  theme;
5
- sidebarToggled;
6
5
  loading = false;
6
+ hideTitle = false;
7
+ sidebarToggled;
7
8
  render() {
8
- return (h("div", { key: '437b8f1b6f44844f1db4460c6597c0a68479a8f7', class: `menubar ${this.theme && `wa-${this.theme}`}` }, h("wa-button", { key: 'baf0a0baa96fcf3f114fdf47b4446b90df862c6b', appearance: "plain", class: "menu-button", onclick: this.sidebarToggled.emit }, h("wa-icon", { key: 'b4e1c07bd7284dbd191997a4b270d12c1e1d5b63', name: "list", label: "Open sidebar", canvas: "auto" })), this.record?.restricted && (h(h.Fragment, null, h("wa-tooltip", { key: '4e8d318982e7bd8af99b320e4810b23d0c00b264', for: "restricted-icon" }, "Restricted access"), h("wa-icon", { key: '6da389ec7b55697d203dd2935af192ae64c0622b', id: "restricted-icon", name: "lock-fill", label: "Restricted access", class: "restricted-icon", canvas: "auto" }))), h("div", { key: 'd118cbb176a44ae761d82c7adff2b0fa31cbe87e', class: "title" }, this.record?.title), this.loading && h("wa-spinner", { key: '5bc6c254a1fa5b4b4bb82ec8fc1cc04ff1457a31', class: "loading-spinner" })));
9
+ return (h("div", { key: '470fcb6d5d31cc9a53142372e82c6b15563863d3', class: `menubar ${this.theme && `wa-${this.theme}`}` }, h("wa-button", { key: '22dec0e10b5b12a1481355d97cc61b6c85b7e612', appearance: "plain", class: "menu-button", onclick: this.sidebarToggled.emit }, h("wa-icon", { key: '48f29ad88af2d982392d946cf11fdc7e605b7aa6', name: "list", label: "Open sidebar", canvas: "auto" })), this.record?.restricted && (h(h.Fragment, null, h("wa-tooltip", { key: 'fa26b6d78dc07d0a009a941e6c2587342a8d6fe4', for: "restricted-icon" }, "Restricted access"), h("wa-icon", { key: '0aaece23cb54401ed62634c079cbfdca63926d69', id: "restricted-icon", name: "lock-fill", label: "Restricted access", class: "restricted-icon", canvas: "auto" }))), !this.hideTitle && h("div", { key: '89d30f43c7d84478dae1777a5be57136342d72f0', class: "title" }, this.record?.title), this.loading && h("wa-spinner", { key: '69a12e09d0b807e85000d63ee3fba8fb98c4ec33', class: "loading-spinner" })));
9
10
  }
10
11
  static get is() { return "ogm-menubar"; }
11
12
  static get encapsulation() { return "shadow"; }
@@ -81,6 +82,26 @@ export class OgmMenubar {
81
82
  "reflect": false,
82
83
  "attribute": "loading",
83
84
  "defaultValue": "false"
85
+ },
86
+ "hideTitle": {
87
+ "type": "boolean",
88
+ "mutable": false,
89
+ "complexType": {
90
+ "original": "boolean",
91
+ "resolved": "boolean",
92
+ "references": {}
93
+ },
94
+ "required": false,
95
+ "optional": false,
96
+ "docs": {
97
+ "tags": [],
98
+ "text": ""
99
+ },
100
+ "getter": false,
101
+ "setter": false,
102
+ "reflect": false,
103
+ "attribute": "hide-title",
104
+ "defaultValue": "false"
84
105
  }
85
106
  };
86
107
  }