ogm-viewer 0.3.0 → 0.4.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.
Files changed (197) hide show
  1. package/dist/cjs/{index-CVTUnaNg.js → index-mu9UK_Fb.js} +66 -3
  2. package/dist/cjs/{lerc-TQEssgMv.js → lerc-CT61sKYw.js} +7 -7
  3. package/dist/cjs/loader.cjs.js +2 -2
  4. package/dist/cjs/{ogm-image.ogm-map.ogm-menubar.ogm-metadata.ogm-settings.ogm-sidebar.ogm-viewer-ifPxRNEo.js → ogm-attributes.ogm-image.ogm-map.ogm-menubar.ogm-metadata.ogm-previews.ogm-settings.ogm-sidebar.ogm-viewer-DKxSOrMP.js} +13640 -7139
  5. package/dist/cjs/ogm-attributes_9.cjs.entry.js +16 -0
  6. package/dist/cjs/ogm-viewer.cjs.js +2 -2
  7. package/dist/collection/assets/webawesome/styles/color/palettes/base.css +44 -0
  8. package/dist/collection/assets/webawesome/styles/color/palettes/bright.css +145 -0
  9. package/dist/collection/assets/webawesome/styles/color/palettes/default.css +146 -0
  10. package/dist/collection/assets/webawesome/styles/color/palettes/shoelace.css +146 -0
  11. package/dist/collection/assets/webawesome/styles/color/variants/brand.css +162 -0
  12. package/dist/collection/assets/webawesome/styles/color/variants/danger.css +162 -0
  13. package/dist/collection/assets/webawesome/styles/color/variants/neutral.css +162 -0
  14. package/dist/collection/assets/webawesome/styles/color/variants/success.css +162 -0
  15. package/dist/collection/assets/webawesome/styles/color/variants/warning.css +162 -0
  16. package/dist/collection/assets/webawesome/styles/color/variants.css +5 -0
  17. package/dist/collection/assets/webawesome/styles/component/form-control.styles.d.ts +2 -0
  18. package/dist/collection/assets/webawesome/styles/component/form-control.styles.ts +43 -0
  19. package/dist/collection/assets/webawesome/styles/component/host.styles.d.ts +2 -0
  20. package/dist/collection/assets/webawesome/styles/component/host.styles.ts +18 -0
  21. package/dist/collection/assets/webawesome/styles/component/segmented-field.styles.d.ts +19 -0
  22. package/dist/collection/assets/webawesome/styles/component/segmented-field.styles.ts +92 -0
  23. package/dist/collection/assets/webawesome/styles/component/size.styles.d.ts +2 -0
  24. package/dist/collection/assets/webawesome/styles/component/size.styles.ts +26 -0
  25. package/dist/collection/assets/webawesome/styles/component/variants.styles.d.ts +2 -0
  26. package/dist/collection/assets/webawesome/styles/component/variants.styles.ts +69 -0
  27. package/dist/collection/assets/webawesome/styles/component/visually-hidden.styles.d.ts +2 -0
  28. package/dist/collection/assets/webawesome/styles/component/visually-hidden.styles.ts +19 -0
  29. package/dist/collection/assets/webawesome/styles/layers.css +21 -0
  30. package/dist/collection/assets/webawesome/styles/native.css +1439 -0
  31. package/dist/collection/assets/webawesome/styles/themes/awesome.css +614 -0
  32. package/dist/collection/assets/webawesome/styles/themes/default.css +373 -0
  33. package/dist/collection/assets/webawesome/styles/themes/shoelace.css +648 -0
  34. package/dist/collection/assets/webawesome/styles/utilities/align-items.css +44 -0
  35. package/dist/collection/assets/webawesome/styles/utilities/border-radius.css +20 -0
  36. package/dist/collection/assets/webawesome/styles/utilities/flex-wrap.css +16 -0
  37. package/dist/collection/assets/webawesome/styles/utilities/fouce.css +17 -0
  38. package/dist/collection/assets/webawesome/styles/utilities/gap.css +56 -0
  39. package/dist/collection/assets/webawesome/styles/utilities/justify-content.css +32 -0
  40. package/dist/collection/assets/webawesome/styles/utilities/layout.css +159 -0
  41. package/dist/collection/assets/webawesome/styles/utilities/placeholder.css +9 -0
  42. package/dist/collection/assets/webawesome/styles/utilities/prose.css +161 -0
  43. package/dist/collection/assets/webawesome/styles/utilities/scroll-lock.css +20 -0
  44. package/dist/collection/assets/webawesome/styles/utilities/size.css +21 -0
  45. package/dist/collection/assets/webawesome/styles/utilities/text.css +262 -0
  46. package/dist/collection/assets/webawesome/styles/utilities/variants.css +62 -0
  47. package/dist/collection/assets/webawesome/styles/utilities/visually-hidden.css +17 -0
  48. package/dist/collection/assets/webawesome/styles/utilities.css +17 -0
  49. package/dist/collection/assets/webawesome/styles/webawesome.css +11 -0
  50. package/dist/collection/collection-manifest.json +2 -0
  51. package/dist/collection/components/ogm-attributes/ogm-attributes.css +68 -0
  52. package/dist/collection/components/ogm-attributes/ogm-attributes.js +112 -0
  53. package/dist/collection/components/ogm-image/ogm-image.css +1 -1
  54. package/dist/collection/components/ogm-image/ogm-image.js +16 -18
  55. package/dist/collection/components/ogm-map/ogm-map.css +10 -5
  56. package/dist/collection/components/ogm-map/ogm-map.js +170 -86
  57. package/dist/collection/components/ogm-menubar/ogm-menubar.css +16 -14
  58. package/dist/collection/components/ogm-menubar/ogm-menubar.js +3 -5
  59. package/dist/collection/components/ogm-menubar/ogm-menubar.test.js +7 -3
  60. package/dist/collection/components/ogm-metadata/ogm-metadata.css +6 -492
  61. package/dist/collection/components/ogm-metadata/ogm-metadata.js +3 -5
  62. package/dist/collection/components/ogm-metadata/ogm-metadata.test.js +1 -1
  63. package/dist/collection/components/ogm-previews/ogm-previews.css +44 -0
  64. package/dist/collection/components/ogm-previews/ogm-previews.js +172 -0
  65. package/dist/collection/components/ogm-settings/ogm-settings.js +7 -9
  66. package/dist/collection/components/ogm-sidebar/ogm-sidebar.css +50 -503
  67. package/dist/collection/components/ogm-sidebar/ogm-sidebar.js +8 -12
  68. package/dist/collection/components/ogm-viewer/ogm-viewer.css +4 -974
  69. package/dist/collection/components/ogm-viewer/ogm-viewer.js +41 -25
  70. package/dist/collection/lib/previewers/cog-deck.js +14 -9
  71. package/dist/collection/lib/previewers/cog.js +3 -3
  72. package/dist/collection/lib/previewers/geojson.js +3 -2
  73. package/dist/collection/lib/previewers/maplibre.js +9 -15
  74. package/dist/collection/lib/previewers/pmtiles-raster.js +1 -1
  75. package/dist/collection/lib/previewers/pmtiles-vector.js +7 -1
  76. package/dist/collection/lib/previewers/raster.js +1 -1
  77. package/dist/collection/lib/previewers/vector.js +65 -30
  78. package/dist/collection/lib/previewers/wms.js +1 -1
  79. package/dist/collection/lib/record.js +1 -1
  80. package/dist/collection/lib/record.test.js +1 -1
  81. package/dist/collection/lib/references.js +0 -65
  82. package/dist/collection/lib/references.test.js +0 -150
  83. package/dist/collection/lib/sources/cog.js +5 -2
  84. package/dist/collection/lib/sources/geojson.js +7 -1
  85. package/dist/collection/lib/sources/iiif-manifest.js +55 -0
  86. package/dist/collection/lib/sources/iiif-manifest.test.js +110 -0
  87. package/dist/collection/lib/sources/iiif.js +11 -0
  88. package/dist/collection/lib/sources/maplibre.js +8 -0
  89. package/dist/collection/lib/sources/openindexmap.js +3 -0
  90. package/dist/collection/lib/sources/pmtiles.js +10 -4
  91. package/dist/collection/lib/sources/raster.js +3 -3
  92. package/dist/collection/lib/sources/source.js +26 -7
  93. package/dist/collection/lib/sources/tilejson.js +7 -1
  94. package/dist/collection/lib/sources/tms.js +3 -0
  95. package/dist/collection/lib/sources/vector.js +3 -3
  96. package/dist/collection/lib/sources/wms.js +6 -11
  97. package/dist/collection/lib/sources/xyz.js +3 -0
  98. package/dist/collection/lib/themes/maplibre.js +46 -0
  99. package/dist/collection/lib/themes/theme.js +23 -0
  100. package/dist/esm/{index-BdFT_kJa.js → index-DTLAlWKQ.js} +66 -3
  101. package/dist/esm/{lerc-DFDScDrO.js → lerc-Bz409klg.js} +2 -2
  102. package/dist/esm/loader.js +3 -3
  103. package/dist/esm/{ogm-image.ogm-map.ogm-menubar.ogm-metadata.ogm-settings.ogm-sidebar.ogm-viewer-r5w_49n3.js → ogm-attributes.ogm-image.ogm-map.ogm-menubar.ogm-metadata.ogm-previews.ogm-settings.ogm-sidebar.ogm-viewer-C9zV3wMe.js} +13639 -7140
  104. package/dist/esm/ogm-attributes_9.entry.js +2 -0
  105. package/dist/esm/ogm-viewer.js +3 -3
  106. package/dist/ogm-viewer/assets/webawesome/styles/color/palettes/base.css +44 -0
  107. package/dist/ogm-viewer/assets/webawesome/styles/color/palettes/bright.css +145 -0
  108. package/dist/ogm-viewer/assets/webawesome/styles/color/palettes/default.css +146 -0
  109. package/dist/ogm-viewer/assets/webawesome/styles/color/palettes/shoelace.css +146 -0
  110. package/dist/ogm-viewer/assets/webawesome/styles/color/variants/brand.css +162 -0
  111. package/dist/ogm-viewer/assets/webawesome/styles/color/variants/danger.css +162 -0
  112. package/dist/ogm-viewer/assets/webawesome/styles/color/variants/neutral.css +162 -0
  113. package/dist/ogm-viewer/assets/webawesome/styles/color/variants/success.css +162 -0
  114. package/dist/ogm-viewer/assets/webawesome/styles/color/variants/warning.css +162 -0
  115. package/dist/ogm-viewer/assets/webawesome/styles/color/variants.css +5 -0
  116. package/dist/ogm-viewer/assets/webawesome/styles/component/form-control.styles.d.ts +2 -0
  117. package/dist/ogm-viewer/assets/webawesome/styles/component/form-control.styles.ts +43 -0
  118. package/dist/ogm-viewer/assets/webawesome/styles/component/host.styles.d.ts +2 -0
  119. package/dist/ogm-viewer/assets/webawesome/styles/component/host.styles.ts +18 -0
  120. package/dist/ogm-viewer/assets/webawesome/styles/component/segmented-field.styles.d.ts +19 -0
  121. package/dist/ogm-viewer/assets/webawesome/styles/component/segmented-field.styles.ts +92 -0
  122. package/dist/ogm-viewer/assets/webawesome/styles/component/size.styles.d.ts +2 -0
  123. package/dist/ogm-viewer/assets/webawesome/styles/component/size.styles.ts +26 -0
  124. package/dist/ogm-viewer/assets/webawesome/styles/component/variants.styles.d.ts +2 -0
  125. package/dist/ogm-viewer/assets/webawesome/styles/component/variants.styles.ts +69 -0
  126. package/dist/ogm-viewer/assets/webawesome/styles/component/visually-hidden.styles.d.ts +2 -0
  127. package/dist/ogm-viewer/assets/webawesome/styles/component/visually-hidden.styles.ts +19 -0
  128. package/dist/ogm-viewer/assets/webawesome/styles/layers.css +21 -0
  129. package/dist/ogm-viewer/assets/webawesome/styles/native.css +1439 -0
  130. package/dist/ogm-viewer/assets/webawesome/styles/themes/awesome.css +614 -0
  131. package/dist/ogm-viewer/assets/webawesome/styles/themes/default.css +373 -0
  132. package/dist/ogm-viewer/assets/webawesome/styles/themes/shoelace.css +648 -0
  133. package/dist/ogm-viewer/assets/webawesome/styles/utilities/align-items.css +44 -0
  134. package/dist/ogm-viewer/assets/webawesome/styles/utilities/border-radius.css +20 -0
  135. package/dist/ogm-viewer/assets/webawesome/styles/utilities/flex-wrap.css +16 -0
  136. package/dist/ogm-viewer/assets/webawesome/styles/utilities/fouce.css +17 -0
  137. package/dist/ogm-viewer/assets/webawesome/styles/utilities/gap.css +56 -0
  138. package/dist/ogm-viewer/assets/webawesome/styles/utilities/justify-content.css +32 -0
  139. package/dist/ogm-viewer/assets/webawesome/styles/utilities/layout.css +159 -0
  140. package/dist/ogm-viewer/assets/webawesome/styles/utilities/placeholder.css +9 -0
  141. package/dist/ogm-viewer/assets/webawesome/styles/utilities/prose.css +161 -0
  142. package/dist/ogm-viewer/assets/webawesome/styles/utilities/scroll-lock.css +20 -0
  143. package/dist/ogm-viewer/assets/webawesome/styles/utilities/size.css +21 -0
  144. package/dist/ogm-viewer/assets/webawesome/styles/utilities/text.css +262 -0
  145. package/dist/ogm-viewer/assets/webawesome/styles/utilities/variants.css +62 -0
  146. package/dist/ogm-viewer/assets/webawesome/styles/utilities/visually-hidden.css +17 -0
  147. package/dist/ogm-viewer/assets/webawesome/styles/utilities.css +17 -0
  148. package/dist/ogm-viewer/assets/webawesome/styles/webawesome.css +11 -0
  149. package/dist/ogm-viewer/ogm-viewer.esm.js +1 -1
  150. package/dist/ogm-viewer/p-86b8fafc.entry.js +1 -0
  151. package/dist/ogm-viewer/{p-BeA3REDA.js → p-BrovdLv6.js} +1 -1
  152. package/dist/ogm-viewer/p-CpCT3zlD.js +2750 -0
  153. package/dist/ogm-viewer/p-DTLAlWKQ.js +2 -0
  154. package/dist/types/components/ogm-attributes/ogm-attributes.d.ts +10 -0
  155. package/dist/types/components/ogm-image/ogm-image.d.ts +3 -3
  156. package/dist/types/components/ogm-map/ogm-map.d.ts +32 -14
  157. package/dist/types/components/ogm-menubar/ogm-menubar.d.ts +1 -1
  158. package/dist/types/components/ogm-metadata/ogm-metadata.d.ts +1 -1
  159. package/dist/types/components/ogm-previews/ogm-previews.d.ts +16 -0
  160. package/dist/types/components/ogm-settings/ogm-settings.d.ts +2 -2
  161. package/dist/types/components/ogm-sidebar/ogm-sidebar.d.ts +1 -1
  162. package/dist/types/components/ogm-viewer/ogm-viewer.d.ts +10 -12
  163. package/dist/types/components.d.ts +70 -18
  164. package/dist/types/lib/previewers/cog-deck.d.ts +7 -3
  165. package/dist/types/lib/previewers/cog.d.ts +2 -2
  166. package/dist/types/lib/previewers/maplibre.d.ts +7 -9
  167. package/dist/types/lib/previewers/pmtiles-vector.d.ts +1 -0
  168. package/dist/types/lib/previewers/vector.d.ts +3 -15
  169. package/dist/types/lib/record.d.ts +1 -1
  170. package/dist/types/lib/references.d.ts +0 -8
  171. package/dist/types/lib/sources/cog.d.ts +3 -2
  172. package/dist/types/lib/sources/geojson.d.ts +3 -2
  173. package/dist/types/lib/sources/iiif-manifest.d.ts +12 -0
  174. package/dist/types/lib/sources/iiif-manifest.test.d.ts +1 -0
  175. package/dist/types/lib/sources/iiif.d.ts +5 -0
  176. package/dist/types/lib/sources/maplibre.d.ts +7 -0
  177. package/dist/types/lib/sources/openindexmap.d.ts +1 -0
  178. package/dist/types/lib/sources/pmtiles.d.ts +6 -4
  179. package/dist/types/lib/sources/raster.d.ts +3 -3
  180. package/dist/types/lib/sources/source.d.ts +6 -6
  181. package/dist/types/lib/sources/tilejson.d.ts +3 -2
  182. package/dist/types/lib/sources/tms.d.ts +1 -0
  183. package/dist/types/lib/sources/vector.d.ts +4 -5
  184. package/dist/types/lib/sources/wms.d.ts +4 -3
  185. package/dist/types/lib/sources/xyz.d.ts +1 -0
  186. package/dist/types/lib/themes/maplibre.d.ts +27 -0
  187. package/dist/types/lib/themes/theme.d.ts +7 -0
  188. package/package.json +3 -2
  189. package/dist/cjs/ogm-image_7.cjs.entry.js +0 -14
  190. package/dist/collection/lib/previewers/index.js +0 -36
  191. package/dist/collection/lib/sources/index.js +0 -29
  192. package/dist/esm/ogm-image_7.entry.js +0 -2
  193. package/dist/ogm-viewer/p-0b81e024.entry.js +0 -1
  194. package/dist/ogm-viewer/p-33R3d9fV.js +0 -1950
  195. package/dist/ogm-viewer/p-BdFT_kJa.js +0 -2
  196. package/dist/types/lib/previewers/index.d.ts +0 -3
  197. package/dist/types/lib/sources/index.d.ts +0 -3
@@ -74,156 +74,6 @@ describe('References', () => {
74
74
  expect(references.iiifManifestUrl).toBe('http://example.com/manifest.json');
75
75
  });
76
76
  });
77
- describe('Fetching IIIF image URLs', () => {
78
- it('should return IIIF image URL directly if present', async () => {
79
- const contents = { 'http://iiif.io/api/image': 'http://example.com/image1/info.json' };
80
- const references = new References(JSON.stringify(contents));
81
- const urls = await references.iiifImageUrls();
82
- expect(urls).toEqual(['http://example.com/image1/info.json']);
83
- });
84
- it('should fetch and extract image URLs from IIIF v2 manifest', async () => {
85
- const manifest = {
86
- '@context': 'http://iiif.io/api/presentation/2/context.json',
87
- '@id': 'http://example.com/manifest.json',
88
- '@type': 'sc:Manifest',
89
- 'sequences': [
90
- {
91
- canvases: [
92
- {
93
- images: [
94
- {
95
- resource: {
96
- '@id': 'http://example.com/image1/full/full/0/default.jpg',
97
- '@type': 'dctypes:Image',
98
- 'service': {
99
- '@id': 'http://example.com/image1',
100
- },
101
- },
102
- },
103
- ],
104
- },
105
- ],
106
- },
107
- ],
108
- };
109
- const references = new References(JSON.stringify({ 'http://iiif.io/api/presentation#manifest': 'http://example.com/manifest.json' }));
110
- vi.spyOn(global, 'fetch').mockResolvedValueOnce(new Response(JSON.stringify(manifest)));
111
- const urls = await references.iiifImageUrls();
112
- expect(urls).toEqual(['http://example.com/image1/info.json']);
113
- });
114
- it('should fetch and extract image URLs from IIIF v3 manifest', async () => {
115
- const manifest = {
116
- '@context': 'http://iiif.io/api/presentation/3/context.json',
117
- 'id': 'http://example.com/manifest.json',
118
- 'type': 'Manifest',
119
- 'items': [
120
- {
121
- id: 'http://example.com/canvas1',
122
- type: 'Canvas',
123
- items: [
124
- {
125
- id: 'http://example.com/annotationpage1',
126
- type: 'AnnotationPage',
127
- items: [
128
- {
129
- id: 'http://example.com/annotation1',
130
- type: 'Annotation',
131
- body: {
132
- id: 'http://example.com/image1/full/full/0/default.jpg',
133
- type: 'Image',
134
- service: {
135
- id: 'http://example.com/image1',
136
- type: 'ImageService2',
137
- },
138
- },
139
- },
140
- ],
141
- },
142
- ],
143
- },
144
- ],
145
- };
146
- const references = new References(JSON.stringify({ 'http://iiif.io/api/presentation#manifest': 'http://example.com/manifest.json' }));
147
- vi.spyOn(global, 'fetch').mockResolvedValueOnce(new Response(JSON.stringify(manifest)));
148
- const urls = await references.iiifImageUrls();
149
- expect(urls).toEqual(['http://example.com/image1/info.json']);
150
- });
151
- it('should not fetch manifest again if already cached', async () => {
152
- const manifest = {
153
- '@context': 'http://iiif.io/api/presentation/2/context.json',
154
- '@id': 'http://example.com/manifest.json',
155
- '@type': 'sc:Manifest',
156
- 'sequences': [
157
- {
158
- canvases: [
159
- {
160
- images: [
161
- {
162
- resource: {
163
- '@id': 'http://example.com/image1/full/full/0/default.jpg',
164
- '@type': 'dctypes:Image',
165
- 'service': {
166
- '@id': 'http://example.com/image1',
167
- },
168
- },
169
- },
170
- ],
171
- },
172
- ],
173
- },
174
- ],
175
- };
176
- const references = new References(JSON.stringify({ 'http://iiif.io/api/presentation#manifest': 'http://example.com/manifest.json' }));
177
- const fetchSpy = vi.spyOn(global, 'fetch').mockResolvedValueOnce(new Response(JSON.stringify(manifest)));
178
- const urls1 = await references.iiifImageUrls();
179
- const urls2 = await references.iiifImageUrls();
180
- expect(fetchSpy).toHaveBeenCalledTimes(1);
181
- expect(urls1).toEqual(['http://example.com/image1/info.json']);
182
- expect(urls2).toEqual(['http://example.com/image1/info.json']);
183
- });
184
- it('should return empty array if manifest URL is not present', async () => {
185
- const references = new References('{}');
186
- const urls = await references.iiifImageUrls();
187
- expect(urls).toEqual([]);
188
- });
189
- it('should return empty array if manifest has no items', async () => {
190
- const manifest = {
191
- '@context': 'http://iiif.io/api/presentation/3/context.json',
192
- 'id': 'http://example.com/manifest.json',
193
- 'type': 'Manifest',
194
- 'items': [],
195
- };
196
- const references = new References(JSON.stringify({ 'http://iiif.io/api/presentation#manifest': 'http://example.com/manifest.json' }));
197
- vi.spyOn(global, 'fetch').mockResolvedValueOnce(new Response(JSON.stringify(manifest)));
198
- const urls = await references.iiifImageUrls();
199
- expect(urls).toEqual([]);
200
- });
201
- it('should return empty array and error if manifest fetch fails', async () => {
202
- const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
203
- const references = new References(JSON.stringify({ 'http://iiif.io/api/presentation#manifest': 'http://example.com/manifest.json' }));
204
- vi.spyOn(global, 'fetch').mockRejectedValueOnce(new Error('Network error'));
205
- const urls = await references.iiifImageUrls();
206
- expect(urls).toEqual([]);
207
- expect(consoleErrorSpy).toHaveBeenCalled();
208
- });
209
- it('should return empty array and error if manifest does not match spec', async () => {
210
- const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
211
- const manifest = { unexpected: 'structure' };
212
- const references = new References(JSON.stringify({ 'http://iiif.io/api/presentation#manifest': 'http://example.com/manifest.json' }));
213
- vi.spyOn(global, 'fetch').mockResolvedValueOnce(new Response(JSON.stringify(manifest)));
214
- const urls = await references.iiifImageUrls();
215
- expect(urls).toEqual([]);
216
- expect(consoleErrorSpy).toHaveBeenCalled();
217
- });
218
- it('should return empty array if manifest is not valid JSON', async () => {
219
- const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
220
- const references = new References(JSON.stringify({ 'http://iiif.io/api/presentation#manifest': 'http://example.com/manifest.json' }));
221
- vi.spyOn(global, 'fetch').mockResolvedValueOnce(new Response('invalid json'));
222
- const urls = await references.iiifImageUrls();
223
- expect(urls).toEqual([]);
224
- expect(consoleErrorSpy).toHaveBeenCalled();
225
- });
226
- });
227
77
  describe('downloads', () => {
228
78
  it('should return empty links list if no download links are present', () => {
229
79
  const references = new References('{}');
@@ -1,7 +1,10 @@
1
1
  import RasterSource from "./raster";
2
2
  export default class CogSource extends RasterSource {
3
+ label() {
4
+ return 'Cloud Optimized GeoTIFF';
5
+ }
3
6
  // Appends the cog:// protocol; must be registered first to work
4
- getSourceUrl() {
7
+ getMapLibreSourceUrl() {
5
8
  return `cog://${this.url}`;
6
9
  }
7
10
  // COGs have no specific scheme identifier for MapLibre
@@ -10,6 +13,6 @@ export default class CogSource extends RasterSource {
10
13
  }
11
14
  // TODO: is it possible to read COG metadata to get bounds?
12
15
  async getBounds() {
13
- return undefined;
16
+ return super.getBounds();
14
17
  }
15
18
  }
@@ -11,6 +11,9 @@ export default class GeoJSONSource extends VectorSource {
11
11
  }
12
12
  return this.data;
13
13
  }
14
+ label() {
15
+ return 'GeoJSON';
16
+ }
14
17
  // GeoJSON is always a vector source
15
18
  async isVector() {
16
19
  return true;
@@ -20,14 +23,17 @@ export default class GeoJSONSource extends VectorSource {
20
23
  return undefined;
21
24
  }
22
25
  // GeoJSON has a special source type
23
- async getType() {
26
+ async getMapLibreSourceType() {
24
27
  return 'geojson';
25
28
  }
26
29
  // Only one layer in a GeoJSON document
27
30
  async getVectorLayers() {
28
31
  return ['geojson'];
29
32
  }
33
+ // Used to zoom the map to the data once loaded
30
34
  async getBounds() {
35
+ if (this.bounds)
36
+ return this.bounds;
31
37
  const data = await this.getData();
32
38
  const bounds = geojsonExtent(data);
33
39
  return [
@@ -0,0 +1,55 @@
1
+ import IIIFSource from "./iiif";
2
+ // A manifest containing multiple IIIF image URLs for preview
3
+ export default class IIIFManifestSource extends IIIFSource {
4
+ // The parsed manifest contents
5
+ manifest;
6
+ label() {
7
+ return 'IIIF Manifest';
8
+ }
9
+ // List of IIIF image URLs extracted from the manifest
10
+ async getIIIFImageUrls() {
11
+ // Fetch and cache the manifest if we haven't already
12
+ const manifest = await this.fetchManifest();
13
+ if (!manifest)
14
+ return [];
15
+ // Try to extract image URLs from the manifest
16
+ if (this.getIIIFVersion(manifest) == 3)
17
+ return this.extractIiif3ImageUrls(manifest);
18
+ return this.extractIiif2ImageUrls(manifest);
19
+ }
20
+ // Attempt to fetch and parse the IIIF manifest, if any
21
+ async fetchManifest() {
22
+ if (this.manifest)
23
+ return this.manifest;
24
+ const response = await fetch(this.url);
25
+ if (!response.ok)
26
+ throw new Error(`Unexpected response fetching IIIF manifest: ${response.statusText}`);
27
+ const manifest = await response.json();
28
+ this.manifest = manifest;
29
+ return manifest;
30
+ }
31
+ // Get the IIIF presentation spec version of the manifest
32
+ getIIIFVersion(manifest) {
33
+ return manifest['@context']?.includes('http://iiif.io/api/presentation/3/context.json') ? 3 : 2;
34
+ }
35
+ // Given a v2 manifest, extract all of the IIIF images and format as info.json URLs
36
+ extractIiif2ImageUrls(manifest) {
37
+ return (manifest.sequences
38
+ .flatMap(seq => seq.canvases)
39
+ .flatMap(can => can.images)
40
+ .flatMap(img => img.resource)
41
+ //@ts-ignore
42
+ .flatMap(res => (res['@type'] === 'dctypes:Image' ? res.service['@id'] + '/info.json' : [])));
43
+ }
44
+ // Given a v3 manifest, extract all of the IIIF images and format as info.json URLs
45
+ extractIiif3ImageUrls(manifest) {
46
+ // Recursively search the '.items' key until we end up with nodes that have type 'ImageService2'
47
+ return (manifest.items
48
+ .flatMap(canvas => canvas.items)
49
+ .flatMap(annotationPage => annotationPage?.items || [])
50
+ .flatMap(annotation => (Array.isArray(annotation.body) ? annotation.body : [annotation.body]))
51
+ //@ts-ignore
52
+ .flatMap(annotationBody => annotationBody.service)
53
+ .flatMap(service => service.id + '/info.json'));
54
+ }
55
+ }
@@ -0,0 +1,110 @@
1
+ import { describe, it, expect, vi, afterEach } from "@stencil/vitest";
2
+ import IIIFManifestSource from "./iiif-manifest";
3
+ const MANIFEST_URL = 'http://example.com/manifest.json';
4
+ // A source always points at a manifest URL; the manifest itself is fetched lazily
5
+ const createSource = () => new IIIFManifestSource('test-id', MANIFEST_URL);
6
+ // A minimal IIIF v2 manifest with a single image
7
+ const v2Manifest = {
8
+ '@context': 'http://iiif.io/api/presentation/2/context.json',
9
+ '@id': MANIFEST_URL,
10
+ '@type': 'sc:Manifest',
11
+ 'sequences': [
12
+ {
13
+ canvases: [
14
+ {
15
+ images: [
16
+ {
17
+ resource: {
18
+ '@id': 'http://example.com/image1/full/full/0/default.jpg',
19
+ '@type': 'dctypes:Image',
20
+ 'service': {
21
+ '@id': 'http://example.com/image1',
22
+ },
23
+ },
24
+ },
25
+ ],
26
+ },
27
+ ],
28
+ },
29
+ ],
30
+ };
31
+ // A minimal IIIF v3 manifest with a single image
32
+ const v3Manifest = {
33
+ '@context': 'http://iiif.io/api/presentation/3/context.json',
34
+ 'id': MANIFEST_URL,
35
+ 'type': 'Manifest',
36
+ 'items': [
37
+ {
38
+ id: 'http://example.com/canvas1',
39
+ type: 'Canvas',
40
+ items: [
41
+ {
42
+ id: 'http://example.com/annotationpage1',
43
+ type: 'AnnotationPage',
44
+ items: [
45
+ {
46
+ id: 'http://example.com/annotation1',
47
+ type: 'Annotation',
48
+ body: {
49
+ id: 'http://example.com/image1/full/full/0/default.jpg',
50
+ type: 'Image',
51
+ service: {
52
+ id: 'http://example.com/image1',
53
+ type: 'ImageService2',
54
+ },
55
+ },
56
+ },
57
+ ],
58
+ },
59
+ ],
60
+ },
61
+ ],
62
+ };
63
+ describe('IIIFManifestSource', () => {
64
+ afterEach(() => vi.restoreAllMocks());
65
+ describe('getIIIFImageUrls', () => {
66
+ it('should fetch and extract image URLs from a IIIF v2 manifest', async () => {
67
+ const fetchSpy = vi.spyOn(global, 'fetch').mockResolvedValueOnce(new Response(JSON.stringify(v2Manifest)));
68
+ const urls = await createSource().getIIIFImageUrls();
69
+ expect(fetchSpy).toHaveBeenCalledWith(MANIFEST_URL);
70
+ expect(urls).toEqual(['http://example.com/image1/info.json']);
71
+ });
72
+ it('should fetch and extract image URLs from a IIIF v3 manifest', async () => {
73
+ const fetchSpy = vi.spyOn(global, 'fetch').mockResolvedValueOnce(new Response(JSON.stringify(v3Manifest)));
74
+ const urls = await createSource().getIIIFImageUrls();
75
+ expect(fetchSpy).toHaveBeenCalledWith(MANIFEST_URL);
76
+ expect(urls).toEqual(['http://example.com/image1/info.json']);
77
+ });
78
+ it('should not fetch the manifest again if already cached', async () => {
79
+ const fetchSpy = vi.spyOn(global, 'fetch').mockResolvedValueOnce(new Response(JSON.stringify(v2Manifest)));
80
+ const source = createSource();
81
+ const urls1 = await source.getIIIFImageUrls();
82
+ const urls2 = await source.getIIIFImageUrls();
83
+ expect(fetchSpy).toHaveBeenCalledTimes(1);
84
+ expect(urls1).toEqual(['http://example.com/image1/info.json']);
85
+ expect(urls2).toEqual(['http://example.com/image1/info.json']);
86
+ });
87
+ it('should return an empty array if the manifest has no items', async () => {
88
+ const manifest = { ...v3Manifest, items: [] };
89
+ vi.spyOn(global, 'fetch').mockResolvedValueOnce(new Response(JSON.stringify(manifest)));
90
+ const urls = await createSource().getIIIFImageUrls();
91
+ expect(urls).toEqual([]);
92
+ });
93
+ it('should throw if the manifest fetch fails', async () => {
94
+ vi.spyOn(global, 'fetch').mockRejectedValueOnce(new Error('Network error'));
95
+ await expect(createSource().getIIIFImageUrls()).rejects.toThrow('Network error');
96
+ });
97
+ it('should throw if the manifest response is not ok', async () => {
98
+ vi.spyOn(global, 'fetch').mockResolvedValueOnce(new Response('Not found', { status: 404, statusText: 'Not Found' }));
99
+ await expect(createSource().getIIIFImageUrls()).rejects.toThrow('Unexpected response fetching IIIF manifest');
100
+ });
101
+ it('should throw if the manifest is not valid JSON', async () => {
102
+ vi.spyOn(global, 'fetch').mockResolvedValueOnce(new Response('invalid json'));
103
+ await expect(createSource().getIIIFImageUrls()).rejects.toThrow();
104
+ });
105
+ it('should throw if the manifest does not match the IIIF spec', async () => {
106
+ vi.spyOn(global, 'fetch').mockResolvedValueOnce(new Response(JSON.stringify({ unexpected: 'structure' })));
107
+ await expect(createSource().getIIIFImageUrls()).rejects.toThrow();
108
+ });
109
+ });
110
+ });
@@ -0,0 +1,11 @@
1
+ import Source from "./source";
2
+ // A source of IIIF image URL(s) to be previewed
3
+ export default class IIIFSource extends Source {
4
+ label() {
5
+ return 'IIIF Image';
6
+ }
7
+ // By default, assume we have a IIIF image URL and just return it
8
+ async getIIIFImageUrls() {
9
+ return [this.url];
10
+ }
11
+ }
@@ -0,0 +1,8 @@
1
+ import Source from "./source";
2
+ // A source of mapped vector or raster data, accessed remotely
3
+ export default class MapLibreSource extends Source {
4
+ // URL used to generate a MapLibre source when adding to map
5
+ getMapLibreSourceUrl() {
6
+ return this.url;
7
+ }
8
+ }
@@ -4,4 +4,7 @@ export default class OpenIndexMapSource extends GeoJSONSource {
4
4
  async getVectorLayers() {
5
5
  return ['indexmap'];
6
6
  }
7
+ label() {
8
+ return 'Index Map';
9
+ }
7
10
  }
@@ -9,10 +9,13 @@ export default class PMTilesSource extends Source {
9
9
  metadata;
10
10
  header;
11
11
  // Store a reference so we can open the archive for metadata inspection
12
- constructor(id, url) {
13
- super(id, url);
12
+ constructor(id, url, bounds) {
13
+ super(id, url, bounds);
14
14
  this.archive = new PMTiles(url);
15
15
  }
16
+ label() {
17
+ return 'PMTiles';
18
+ }
16
19
  // Fetch and memoize PMTiles metadata
17
20
  async getMetadata() {
18
21
  if (!this.metadata) {
@@ -32,7 +35,10 @@ export default class PMTilesSource extends Source {
32
35
  const header = await this.getHeader();
33
36
  return header.tileType === TileType.Mvt || header.tileType === TileType.Mlt;
34
37
  }
38
+ // Used to zoom the map to the data once loaded
35
39
  async getBounds() {
40
+ if (this.bounds)
41
+ return this.bounds;
36
42
  const header = await this.getHeader();
37
43
  return [
38
44
  [header.minLon, header.minLat],
@@ -57,7 +63,7 @@ export default class PMTilesSource extends Source {
57
63
  return undefined;
58
64
  }
59
65
  // Appends the pmtiles:// protocol; must be registered first to work
60
- getSourceUrl() {
66
+ getMapLibreSourceUrl() {
61
67
  return `pmtiles://${this.url}`;
62
68
  }
63
69
  // Raster PMTiles have no specific scheme identifier for MapLibre
@@ -68,7 +74,7 @@ export default class PMTilesSource extends Source {
68
74
  getTileSize() {
69
75
  return undefined;
70
76
  }
71
- async getType() {
77
+ async getMapLibreSourceType() {
72
78
  if (await this.isVector())
73
79
  return 'vector';
74
80
  return 'raster';
@@ -1,13 +1,13 @@
1
- import Source from "./source";
1
+ import MapLibreSource from "./maplibre";
2
2
  // Tiled raster data, accessed remotely
3
- export default class RasterSource extends Source {
3
+ export default class RasterSource extends MapLibreSource {
4
4
  async isVector() {
5
5
  return false;
6
6
  }
7
7
  getTileSize() {
8
8
  return 256;
9
9
  }
10
- async getType() {
10
+ async getMapLibreSourceType() {
11
11
  return 'raster';
12
12
  }
13
13
  }
@@ -1,20 +1,39 @@
1
- // A source of vector or raster data, accessed remotely
1
+ // A source of previewable data at a URL
2
2
  export default class Source {
3
3
  // URL to the remote data source
4
4
  url;
5
- // Unique ID for this source, used in MapLibre style generation
5
+ // Explicitly provided bounds for the source, if any
6
+ bounds;
7
+ // Unique ID for this source
6
8
  id;
7
9
  // Store the source URL
8
- constructor(id, url) {
10
+ constructor(id, url, bounds) {
9
11
  this.id = id;
10
12
  this.url = url;
13
+ this.bounds = bounds;
14
+ }
15
+ // Used to label the tabs for switching between sources, e.g.
16
+ label() {
17
+ return this.constructor.name;
11
18
  }
12
19
  // Check that the URL is valid and accessible
13
20
  async test() {
14
- await fetch(this.url, { method: 'HEAD' });
21
+ try {
22
+ const response = await fetch(this.url, {
23
+ method: 'HEAD',
24
+ signal: AbortSignal.timeout(8000),
25
+ });
26
+ return response.ok;
27
+ }
28
+ catch (error) {
29
+ console.error(`Error checking source URL ${this.url}:`, error);
30
+ return false;
31
+ }
15
32
  }
16
- // URL used to generate a MapLibre source when adding to map
17
- getSourceUrl() {
18
- return this.url;
33
+ // Async because subclasses may do operations to calculate it
34
+ async getBounds() {
35
+ if (this.bounds)
36
+ return this.bounds;
37
+ return undefined;
19
38
  }
20
39
  }
@@ -3,6 +3,9 @@ import Source from "./source";
3
3
  export default class TileJSONSource extends Source {
4
4
  // Metadata parsed from TileJSON document
5
5
  metadata;
6
+ label() {
7
+ return 'TileJSON';
8
+ }
6
9
  // Fetch and memoize metadata
7
10
  async getMetadata() {
8
11
  if (!this.metadata) {
@@ -18,7 +21,10 @@ export default class TileJSONSource extends Source {
18
21
  const pathname = new URL(template).pathname;
19
22
  return pathname.endsWith('.pbf') || pathname.endsWith('.mvt') || pathname.endsWith('.mlt');
20
23
  }
24
+ // Used to zoom the map to the data once loaded
21
25
  async getBounds() {
26
+ if (this.bounds)
27
+ return this.bounds;
22
28
  const metadata = await this.getMetadata();
23
29
  return [
24
30
  [metadata.bounds[0], metadata.bounds[1]],
@@ -37,7 +43,7 @@ export default class TileJSONSource extends Source {
37
43
  return 'mlt';
38
44
  return 'mvt';
39
45
  }
40
- async getType() {
46
+ async getMapLibreSourceType() {
41
47
  if (await this.isVector())
42
48
  return 'vector';
43
49
  return 'raster';
@@ -1,5 +1,8 @@
1
1
  import RasterSource from "./raster";
2
2
  export default class TmsSource extends RasterSource {
3
+ label() {
4
+ return 'Tiled Map Service (TMS)';
5
+ }
3
6
  getScheme() {
4
7
  return 'tms';
5
8
  }
@@ -1,10 +1,10 @@
1
- import Source from "./source";
1
+ import MapLibreSource from "./maplibre";
2
2
  // A source of vector data, accessed remotely
3
- export default class VectorSource extends Source {
3
+ export default class VectorSource extends MapLibreSource {
4
4
  async isVector() {
5
5
  return true;
6
6
  }
7
- async getType() {
7
+ async getMapLibreSourceType() {
8
8
  return 'vector';
9
9
  }
10
10
  }
@@ -13,14 +13,17 @@ export default class WmsSource extends RasterSource {
13
13
  options;
14
14
  // Memoized metadata via GetCapabilities request
15
15
  metadata;
16
- constructor(id, url, options) {
17
- super(id, url);
16
+ constructor(id, url, options, bounds) {
17
+ super(id, url, bounds);
18
18
  this.options = { ...defaultOptions, ...options };
19
19
  // Assume we're using one layer with the given ID if no layer IDs are provided
20
20
  if (!this.options.layerIds || this.options.layerIds.length === 0) {
21
21
  this.options.layerIds = [id];
22
22
  }
23
23
  }
24
+ label() {
25
+ return 'Web Map Service (WMS)';
26
+ }
24
27
  // Fetch and memoize WMS GetCapabilities XML document
25
28
  async getMetadata() {
26
29
  if (!this.metadata) {
@@ -30,15 +33,7 @@ export default class WmsSource extends RasterSource {
30
33
  }
31
34
  return this.metadata;
32
35
  }
33
- // WMS doesn't provide bounds in the URL, so we can't zoom the map
34
- // TODO: use getCapabilities to get the bounding box of the layer(s) and return that
35
- async getBounds() {
36
- return [
37
- [-180, -90],
38
- [180, 90],
39
- ];
40
- }
41
- getSourceUrl() {
36
+ getMapLibreSourceUrl() {
42
37
  return this.tilesUrl;
43
38
  }
44
39
  // WMS has no specific scheme identifier for MapLibre
@@ -1,5 +1,8 @@
1
1
  import RasterSource from "./raster";
2
2
  export default class XyzSource extends RasterSource {
3
+ label() {
4
+ return 'XYZ Tile Service';
5
+ }
3
6
  getScheme() {
4
7
  return 'xyz';
5
8
  }