@panoramax/web-viewer 3.2.3-develop-eb68a0ea → 3.2.3-develop-7a7f14f4

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 (48) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/build/index.css +1 -1
  3. package/build/index.css.map +1 -1
  4. package/build/index.html +1 -1
  5. package/build/index.js +29 -25
  6. package/build/index.js.map +1 -1
  7. package/build/photo.html +1 -0
  8. package/config/paths.js +1 -0
  9. package/config/webpack.config.js +26 -0
  10. package/docs/03_URL_settings.md +4 -1
  11. package/docs/09_Develop.md +1 -1
  12. package/docs/images/class_diagram.drawio +37 -22
  13. package/docs/images/class_diagram.jpg +0 -0
  14. package/docs/index.md +15 -1
  15. package/docs/reference/components/core/Basic.md +1 -1
  16. package/docs/reference/components/core/CoverageMap.md +8 -4
  17. package/docs/reference/components/core/Editor.md +1 -1
  18. package/docs/reference/components/core/PhotoViewer.md +256 -0
  19. package/docs/reference/components/core/Viewer.md +48 -50
  20. package/docs/reference/components/menus/MapLegend.md +1 -1
  21. package/docs/reference/components/ui/Photo.md +8 -0
  22. package/docs/reference/components/ui/widgets/Legend.md +7 -1
  23. package/docs/reference.md +3 -2
  24. package/docs/tutorials/custom_widgets.md +6 -11
  25. package/docs/tutorials/migrate_v4.md +2 -0
  26. package/mkdocs.yml +1 -0
  27. package/package.json +1 -1
  28. package/public/index.html +14 -9
  29. package/public/photo.html +55 -0
  30. package/src/components/core/Basic.js +1 -1
  31. package/src/components/core/CoverageMap.js +24 -3
  32. package/src/components/core/Editor.js +1 -1
  33. package/src/components/core/PhotoViewer.css +65 -0
  34. package/src/components/core/PhotoViewer.js +441 -0
  35. package/src/components/core/Viewer.js +72 -307
  36. package/src/components/core/index.js +1 -0
  37. package/src/components/menus/MapLegend.js +1 -21
  38. package/src/components/ui/Map.js +1 -17
  39. package/src/components/ui/Photo.js +13 -3
  40. package/src/components/ui/widgets/Legend.js +32 -1
  41. package/src/index.js +1 -0
  42. package/src/utils/API.js +2 -2
  43. package/src/utils/InitParameters.js +54 -18
  44. package/src/utils/URLHandler.js +11 -5
  45. package/src/utils/map.js +2 -2
  46. package/tests/components/ui/Photo.test.js +6 -6
  47. package/tests/utils/InitParameters.test.js +1 -0
  48. package/tests/utils/URLHandler.test.js +16 -6
@@ -1,14 +1,22 @@
1
1
  import {LitElement, html, css} from "lit";
2
2
  import { panel } from "../../styles";
3
+ import { fa } from "../../../utils/widgets";
4
+ import { faInfoCircle } from "@fortawesome/free-solid-svg-icons/faInfoCircle";
5
+ import PanoramaxImg from "../../../img/panoramax.svg";
3
6
 
4
7
  /**
5
8
  * Legend widget, handling switch between map and photo components.
9
+ * Also displays a default "About Panoramax" message.
6
10
  * @class Panoramax.components.ui.widgets.Legend
7
11
  * @element pnx-widget-legend
8
12
  * @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
9
13
  * @example
10
14
  * ```html
11
- * <pnx-widget-legend _parent=${viewer} />
15
+ * <pnx-widget-legend
16
+ * _parent=${viewer}
17
+ * focus="map"
18
+ * picture="PICTURE-ID-IF-ANY"
19
+ * />
12
20
  * ```
13
21
  */
14
22
  export default class Legend extends LitElement {
@@ -22,6 +30,15 @@ export default class Legend extends LitElement {
22
30
  max-width: 80vh;
23
31
  z-index: 120;
24
32
  }
33
+ .presentation {
34
+ font-size: 0.9em;
35
+ display: flex;
36
+ gap: 5px;
37
+ align-items: center;
38
+ }
39
+ .logo {
40
+ width: 45px;
41
+ }
25
42
  `];
26
43
 
27
44
  /**
@@ -29,13 +46,27 @@ export default class Legend extends LitElement {
29
46
  * @memberof Panoramax.components.ui.widgets.Legend#
30
47
  * @type {Object}
31
48
  * @property {string} [focus] The focused main component (map, pic)
49
+ * @property {string} [picture] The picture ID
32
50
  */
33
51
  static properties = {
34
52
  focus: {type: String},
53
+ picture: {type: String},
35
54
  };
36
55
 
37
56
  render() {
38
57
  return html`<div class="pnx-panel pnx-padded" part="panel">
58
+ <div
59
+ class="presentation"
60
+ style=${this.focus != "map" && this.picture ? "display: none": ""}
61
+ >
62
+ <img class="logo" src=${PanoramaxImg} alt="" />
63
+ <div>
64
+ Panoramax est le géocommun des photos de rues.
65
+ <pnx-link-button title=${this._parent?._t.map.more_panoramax} kind="outline" href="https://panoramax.fr" target="_blank">
66
+ ${fa(faInfoCircle, { styles: {height: "12px", "margin-inline": "3px"}})}
67
+ </pnx-link-button>
68
+ </div>
69
+ </div>
39
70
  <pnx-picture-legend
40
71
  ._parent=${this._parent}
41
72
  style=${this.focus == "map" ? "display: none": ""}
package/src/index.js CHANGED
@@ -5,3 +5,4 @@ export * as utils from "./utils";
5
5
  export {default as Viewer} from "./components/core/Viewer";
6
6
  export {default as CoverageMap} from "./components/core/CoverageMap";
7
7
  export {default as Editor} from "./components/core/Editor";
8
+ export {default as PhotoViewer} from "./components/core/PhotoViewer";
package/src/utils/API.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { TILES_PICTURES_ZOOM } from "./map";
2
- import { BASE_PANORAMA_ID } from "./utils";
2
+ import { isNullId } from "./utils";
3
3
 
4
4
  /**
5
5
  * API contains various utility functions to communicate with Panoramax/STAC API
@@ -772,7 +772,7 @@ export default class API {
772
772
  * @throws {Error} If not valid
773
773
  */
774
774
  static isIdValid(id) {
775
- if(!id || typeof id !== "string" || id.length === 0 || id === BASE_PANORAMA_ID) {
775
+ if(isNullId(id)) {
776
776
  throw new Error("id should be a valid picture unique identifier");
777
777
  }
778
778
  return true;
@@ -10,6 +10,24 @@ export const MAP_FILTERS_JS2URL = {
10
10
  const MAP_FILTERS_URL2JS = Object.fromEntries(Object.entries(MAP_FILTERS_JS2URL).map(v => [v[1], v[0]]));
11
11
  const MAP_NONE = ["none", "null", "false", false];
12
12
 
13
+ const MAPLIBRE_OPTIONS = [
14
+ "antialias", "bearing", "bearingSnap", "bounds",
15
+ "boxZoom", "clickTolerance", "collectResourceTiming",
16
+ "cooperativeGestures", "crossSourceCollisions", "doubleClickZoom", "dragPan",
17
+ "dragRotate", "fadeDuration", "failIfMajorPerformanceCaveat", "fitBoundsOptions",
18
+ "hash", "interactive", "keyboard", "localIdeographFontFamily", "locale", "logoPosition",
19
+ "maplibreLogo", "maxBounds", "maxCanvasSize", "maxPitch", "maxTileCacheSize",
20
+ "maxTileCacheZoomLevels", "maxZoom", "minPitch", "minZoom", "pitch", "pitchWithRotate",
21
+ "pixelRatio", "preserveDrawingBuffer", "refreshExpiredTiles", "renderWorldCopies",
22
+ "scrollZoom", "touchPitch", "touchZoomRotate", "trackResize",
23
+ "transformCameraUpdate", "transformRequest", "validateStyle"
24
+ ];
25
+
26
+ function filterMapLibreOptions(opts) {
27
+ return Object.fromEntries(Object.entries(opts).filter(([key]) => MAPLIBRE_OPTIONS.includes(key)));
28
+ }
29
+
30
+
13
31
  /**
14
32
  * Merges all URL parameters and component attributes into a single set of coherent settings.
15
33
  *
@@ -61,6 +79,7 @@ export default class InitParameters { // eslint-disable-line import/no-unused-mo
61
79
  let endpoint = componentAttrs.endpoint;
62
80
  let map_raster = componentMap.raster;
63
81
  let map_attribution = componentMap.attributionControl;
82
+ let map_others = filterMapLibreOptions(componentMap);
64
83
 
65
84
  // - URL only
66
85
  let psv_xyz = urlParams.xyz;
@@ -102,7 +121,11 @@ export default class InitParameters { // eslint-disable-line import/no-unused-mo
102
121
  };
103
122
  this._psvPostInit = { xyz: psv_xyz };
104
123
 
105
- this._mapInit = { raster: map_raster, attributionControl: map_attribution };
124
+ this._mapInit = {
125
+ raster: map_raster,
126
+ attributionControl: map_attribution,
127
+ ...map_others
128
+ };
106
129
  this._mapAny = {
107
130
  theme: map_theme,
108
131
  background: map_background,
@@ -283,13 +306,15 @@ export function alterPSVState(psv, params) {
283
306
  }
284
307
 
285
308
  // Change transitionDuration
286
- if(params.transitionDuration !== undefined) {
287
- psv.setTransitionDuration(params.transitionDuration);
309
+ let td = params.transitionDuration || params.speed;
310
+ if(td !== undefined) {
311
+ psv.setTransitionDuration(td);
288
312
  }
289
313
 
290
314
  // Change pictures navigation mode
291
- if(["none", "pic", "any", "seq"].includes(params.picturesNavigation)) {
292
- psv.setPicturesNavigation(params.picturesNavigation);
315
+ let nav = params.picturesNavigation || params.nav;
316
+ if(["none", "pic", "any", "seq"].includes(nav)) {
317
+ psv.setPicturesNavigation(nav);
293
318
  }
294
319
  }
295
320
 
@@ -311,7 +336,7 @@ export function alterMapState(map, params) {
311
336
  map.setVisibleUsers(vu);
312
337
 
313
338
  // Change map filters
314
- map.setFilters(paramsToMapFilters(params));
339
+ map.setFilters?.(paramsToMapFilters(params));
315
340
 
316
341
  // Change map background
317
342
  if(["aerial", "streets"].includes(params.background)) {
@@ -320,39 +345,50 @@ export function alterMapState(map, params) {
320
345
  }
321
346
 
322
347
  /**
323
- * Change Viewer current state based on standardized parameters.
324
- * @param {Viewer} viewer The Viewer component to change.
348
+ * Change PhotoViewer current state based on standardized parameters.
349
+ * @param {PhotoViewer} viewer The PhotoViewer component to change.
325
350
  * @param {object} params The parameters to apply.
326
351
  */
327
- export function alterViewerState(viewer, params) {
352
+ export function alterPhotoViewerState(viewer, params) {
328
353
  // Restore selected picture
329
- if(params.picture) {
330
- const picIds = params.picture.split(";"); // Handle multiple IDs coming from OSM
354
+ let pic = params.picture || params.pic;
355
+ if(pic) {
356
+ const picIds = pic.split(";"); // Handle multiple IDs coming from OSM
331
357
  if(picIds.length > 1) {
332
358
  console.warn("Multiple picture IDs passed in URL, only first one kept");
333
359
  }
334
360
 
335
- // Post-load changes
336
- if(params.focus && params.focus == "meta") {
337
- viewer.psv.addEventListener("picture-loaded", () => viewer._showPictureMetadata(), { once: true });
338
- }
339
-
340
361
  viewer.select(null, picIds[0]);
341
362
  }
342
363
  else {
343
364
  viewer.select();
344
365
  }
366
+ }
367
+
368
+ /**
369
+ * Change Viewer current state based on standardized parameters.
370
+ * @param {Viewer} viewer The Viewer component to change.
371
+ * @param {object} params The parameters to apply.
372
+ */
373
+ export function alterViewerState(viewer, params) {
374
+ alterPhotoViewerState(viewer, params);
375
+
376
+ // Restore selected picture
377
+ let pic = params.picture || params.pic;
378
+ if(pic && params.focus && params.focus == "meta") {
379
+ viewer.psv.addEventListener("picture-loaded", () => viewer._showPictureMetadata(), { once: true });
380
+ }
345
381
 
346
382
  // Change focus
347
383
  if(params.focus === "map" && viewer?.map) {
348
384
  viewer.setPopup(false);
349
385
  viewer._setFocus("map", null, params.forceFocus);
350
386
  }
351
- else if(params.focus === "pic") {
387
+ else if(params.focus === "pic" && viewer?.mini) {
352
388
  viewer.setPopup(false);
353
389
  viewer._setFocus("pic", null, params.forceFocus);
354
390
  }
355
- else if(params.focus && params.focus === "meta") {
391
+ else if(params.focus && params.focus === "meta" && viewer?.mini) {
356
392
  viewer._setFocus("pic", null, params.forceFocus);
357
393
  }
358
394
  }
@@ -1,6 +1,7 @@
1
1
  import {
2
- alterPSVState, MAP_FILTERS_JS2URL, alterMapState, alterViewerState
2
+ alterPSVState, MAP_FILTERS_JS2URL, alterMapState, alterViewerState, alterPhotoViewerState
3
3
  } from "./InitParameters";
4
+ import Viewer from "../components/core/Viewer";
4
5
 
5
6
  // List of supported parameters
6
7
  const MANAGED_PARAMETERS = [
@@ -61,9 +62,11 @@ export default class URLHandler extends EventTarget {
61
62
  hashParts.nav = this._parent.psv.getPicturesNavigation();
62
63
  }
63
64
 
65
+ if(this._parent.psv.getPictureId()) {
66
+ hashParts.pic = this._parent.psv.getPictureId();
67
+ }
64
68
  const picMeta = this._parent.psv.getPictureMetadata();
65
69
  if (picMeta) {
66
- hashParts.pic = picMeta.id;
67
70
  hashParts.xyz = this.currentPSVString();
68
71
  }
69
72
 
@@ -93,7 +96,7 @@ export default class URLHandler extends EventTarget {
93
96
  }
94
97
  }
95
98
  }
96
- else {
99
+ else if(this._parent instanceof Viewer) {
97
100
  hashParts.map = "none";
98
101
  }
99
102
  return hashParts;
@@ -256,7 +259,10 @@ export default class URLHandler extends EventTarget {
256
259
  */
257
260
  _onURLChange() {
258
261
  let vals = this.currentURLParams();
259
- alterViewerState(this._parent, vals);
262
+
263
+ if(this._parent instanceof Viewer) { alterViewerState(this._parent, vals); }
264
+ else { alterPhotoViewerState(this._parent, vals); }
265
+
260
266
  alterPSVState(this._parent.psv, vals);
261
267
  if(this._parent.map) { alterMapState(this._parent.map, vals); }
262
268
  }
@@ -329,7 +335,7 @@ export default class URLHandler extends EventTarget {
329
335
  if(prevUrl.search == nextUrl.search) { return; }
330
336
 
331
337
  const prevPic = this.currentURLParams().pic || "";
332
- const nextPic = this._parent?.psv?.getPictureMetadata()?.id || "";
338
+ const nextPic = this._parent?.psv?.getPictureId?.() || "";
333
339
 
334
340
  const prevFocus = this.currentURLParams().focus || "";
335
341
  const nextFocus = nextUrl.search.includes("focus=meta") ? "meta" : (nextUrl.search.includes("focus=map") ? "map" : "pic");
package/src/utils/map.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import LoaderImg from "../img/marker.svg";
2
2
  import { COLORS, QUALITYSCORE_RES_FLAT_VALUES, QUALITYSCORE_RES_360_VALUES, QUALITYSCORE_GPS_VALUES, QUALITYSCORE_POND_RES, QUALITYSCORE_POND_GPS } from "./utils";
3
3
  import { autoDetectLocale } from "./i18n";
4
- import { BASE_PANORAMA_ID } from "./utils";
4
+ import { isNullId } from "./utils";
5
5
 
6
6
  export const DEFAULT_TILES = "https://panoramax.openstreetmap.fr/pmtiles/basic.json";
7
7
  export const RASTER_LAYER_ID = "pnx-aerial";
@@ -398,7 +398,7 @@ export function switchCoefValue(expr, newCoefVal) {
398
398
  export function linkMapAndPhoto(parent) {
399
399
  // Switched picture
400
400
  const onPicLoad = e => {
401
- if(!e.detail.picId || e.detail.picId === BASE_PANORAMA_ID) {
401
+ if(isNullId(e.detail.picId)) {
402
402
  parent.map.displayPictureMarker();
403
403
  if(parent?.isMapWide?.()) {
404
404
  parent?.mini?.setAttribute("collapsed", "");
@@ -66,8 +66,8 @@ describe("getPictureMetadata", () => {
66
66
  const p = createParent();
67
67
  const c = document.createElement("div");
68
68
  const ph = new Photo(p, c);
69
- ph._myVTour.state.currentNode = { bla: "bla" };
70
- expect(ph.getPictureMetadata()).toStrictEqual({ bla: "bla" });
69
+ ph._myVTour.state.currentNode = { id: "1234", bla: "bla" };
70
+ expect(ph.getPictureMetadata()).toStrictEqual({ id: "1234", bla: "bla" });
71
71
  });
72
72
 
73
73
  it("nulls when no pic is selected", () => {
@@ -111,7 +111,7 @@ describe("goToNextPicture", () => {
111
111
  const p = createParent();
112
112
  const c = document.createElement("div");
113
113
  const ph = new Photo(p, c);
114
- ph._myVTour = { state: { currentNode: { sequence: { id: "seq", nextPic: "idnext" } } } };
114
+ ph._myVTour = { state: { currentNode: { id: "bla", sequence: { id: "seq", nextPic: "idnext" } } } };
115
115
  ph.goToNextPicture();
116
116
  expect(p.select.mock.calls).toEqual([["seq", "idnext"]]);
117
117
  });
@@ -120,7 +120,7 @@ describe("goToNextPicture", () => {
120
120
  const p = createParent();
121
121
  const c = document.createElement("div");
122
122
  const ph = new Photo(p, c);
123
- ph._myVTour = { state: { currentNode: { sequence: {} } } };
123
+ ph._myVTour = { state: { currentNode: { id: "bla", sequence: {} } } };
124
124
  expect(() => ph.goToNextPicture()).toThrow(new Error("No next picture available"));
125
125
  });
126
126
  });
@@ -138,7 +138,7 @@ describe("goToPrevPicture", () => {
138
138
  const p = createParent();
139
139
  const c = document.createElement("div");
140
140
  const ph = new Photo(p, c);
141
- ph._myVTour = { state: { currentNode: { sequence: { id: "seq", prevPic: "idprev" } } } };
141
+ ph._myVTour = { state: { currentNode: { id: "bla", sequence: { id: "seq", prevPic: "idprev" } } } };
142
142
  ph.goToPrevPicture();
143
143
  expect(p.select.mock.calls).toEqual([["seq", "idprev"]]);
144
144
  });
@@ -147,7 +147,7 @@ describe("goToPrevPicture", () => {
147
147
  const p = createParent();
148
148
  const c = document.createElement("div");
149
149
  const ph = new Photo(p, c);
150
- ph._myVTour = { state: { currentNode: { sequence: {} } } };
150
+ ph._myVTour = { state: { currentNode: { id: "bla", sequence: {} } } };
151
151
  expect(() => ph.goToPrevPicture()).toThrow(new Error("No previous picture available"));
152
152
  });
153
153
  });
@@ -443,6 +443,7 @@ describe("alterViewerState", () => {
443
443
  setPopup: jest.fn(),
444
444
  _setFocus: jest.fn(),
445
445
  _showPictureMetadata: jest.fn(),
446
+ mini: {},
446
447
  };
447
448
  });
448
449
 
@@ -31,10 +31,11 @@ describe("nextURLString", () => {
31
31
  getPicturesNavigation: () => null,
32
32
  getTransitionDuration: () => null,
33
33
  getPictureMetadata: () => null,
34
+ getPictureId: () => null,
34
35
  },
35
36
  };
36
37
  const uh = new URLHandler(v);
37
- expect(uh.nextURLString()).toBe("?map=none");
38
+ expect(uh.nextURLString()).toBe("?");
38
39
  });
39
40
 
40
41
  it("works with picture metadata", () => {
@@ -43,12 +44,13 @@ describe("nextURLString", () => {
43
44
  psv: {
44
45
  getPicturesNavigation: () => null,
45
46
  getTransitionDuration: () => null,
46
- getPictureMetadata: () => ({ "id": "cbfc3add-8173-4464-98c8-de2a43c6a50f" })
47
+ getPictureMetadata: () => ({}),
48
+ getPictureId: () => "cbfc3add-8173-4464-98c8-de2a43c6a50f",
47
49
  },
48
50
  };
49
51
  const uh = new URLHandler(v);
50
52
  uh.currentPSVString = () => "0/1/2";
51
- expect(uh.nextURLString()).toBe("?map=none&pic=cbfc3add-8173-4464-98c8-de2a43c6a50f&xyz=0/1/2");
53
+ expect(uh.nextURLString()).toBe("?pic=cbfc3add-8173-4464-98c8-de2a43c6a50f&xyz=0/1/2");
52
54
  });
53
55
 
54
56
  it("works with map started + wide", () => {
@@ -58,6 +60,7 @@ describe("nextURLString", () => {
58
60
  getPicturesNavigation: () => null,
59
61
  getTransitionDuration: () => null,
60
62
  getPictureMetadata: () => null,
63
+ getPictureId: () => null,
61
64
  },
62
65
  map: {
63
66
  getVisibleUsers: () => ["geovisio"],
@@ -78,7 +81,8 @@ describe("nextURLString", () => {
78
81
  psv: {
79
82
  getPicturesNavigation: () => null,
80
83
  getTransitionDuration: () => null,
81
- getPictureMetadata: () => ({ "id": "cbfc3add-8173-4464-98c8-de2a43c6a50f" }),
84
+ getPictureMetadata: () => ({}),
85
+ getPictureId: () => "cbfc3add-8173-4464-98c8-de2a43c6a50f",
82
86
  },
83
87
  map: {
84
88
  getVisibleUsers: () => ["geovisio"],
@@ -101,6 +105,7 @@ describe("nextURLString", () => {
101
105
  getPicturesNavigation: () => null,
102
106
  getTransitionDuration: () => null,
103
107
  getPictureMetadata: () => null,
108
+ getPictureId: () => null,
104
109
  },
105
110
  map: {
106
111
  getVisibleUsers: () => ["geovisio"],
@@ -129,10 +134,11 @@ describe("nextURLString", () => {
129
134
  getPicturesNavigation: () => null,
130
135
  getTransitionDuration: () => 250,
131
136
  getPictureMetadata: () => null,
137
+ getPictureId: () => null,
132
138
  },
133
139
  };
134
140
  const uh = new URLHandler(v);
135
- expect(uh.nextURLString()).toBe("?map=none&speed=250");
141
+ expect(uh.nextURLString()).toBe("?speed=250");
136
142
  });
137
143
 
138
144
  it("works with popup", () => {
@@ -142,6 +148,7 @@ describe("nextURLString", () => {
142
148
  getPicturesNavigation: () => null,
143
149
  getTransitionDuration: () => null,
144
150
  getPictureMetadata: () => null,
151
+ getPictureId: () => null,
145
152
  },
146
153
  map: {
147
154
  getVisibleUsers: () => ["geovisio"],
@@ -163,10 +170,11 @@ describe("nextURLString", () => {
163
170
  getPicturesNavigation: () => "pic",
164
171
  getTransitionDuration: () => null,
165
172
  getPictureMetadata: () => null,
173
+ getPictureId: () => null,
166
174
  },
167
175
  };
168
176
  const uh = new URLHandler(v);
169
- expect(uh.nextURLString()).toBe("?map=none&nav=pic");
177
+ expect(uh.nextURLString()).toBe("?nav=pic");
170
178
  });
171
179
  });
172
180
 
@@ -312,6 +320,7 @@ describe("currentPSVString", () => {
312
320
  addEventListener: jest.fn(),
313
321
  psv: {
314
322
  getXYZ: () => ({ x: 12.123456, y: 50.789456, z: 75 }),
323
+ getPictureId: () => null,
315
324
  },
316
325
  };
317
326
  const uh = new URLHandler(v);
@@ -323,6 +332,7 @@ describe("currentPSVString", () => {
323
332
  addEventListener: jest.fn(),
324
333
  psv: {
325
334
  getXYZ: () => ({ x: 12, y: 50 }),
335
+ getPictureId: () => null,
326
336
  },
327
337
  };
328
338
  const uh = new URLHandler(v);