@panoramax/web-viewer 3.2.3-develop-e277ccb9 → 3.2.3-develop-dbce84df

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 (42) 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/PhotoViewer.md +256 -0
  16. package/docs/reference/components/core/Viewer.md +47 -49
  17. package/docs/reference/components/menus/MapLegend.md +1 -1
  18. package/docs/reference/components/ui/Photo.md +8 -0
  19. package/docs/reference/components/ui/widgets/Legend.md +7 -1
  20. package/docs/reference.md +3 -2
  21. package/docs/tutorials/custom_widgets.md +6 -11
  22. package/docs/tutorials/migrate_v4.md +2 -0
  23. package/mkdocs.yml +1 -0
  24. package/package.json +1 -1
  25. package/public/index.html +14 -9
  26. package/public/photo.html +55 -0
  27. package/src/components/core/PhotoViewer.css +65 -0
  28. package/src/components/core/PhotoViewer.js +441 -0
  29. package/src/components/core/Viewer.js +71 -306
  30. package/src/components/core/index.js +1 -0
  31. package/src/components/menus/MapLegend.js +1 -21
  32. package/src/components/ui/Photo.js +13 -3
  33. package/src/components/ui/widgets/Legend.js +32 -1
  34. package/src/index.js +1 -0
  35. package/src/translations/nl.json +105 -5
  36. package/src/utils/API.js +2 -2
  37. package/src/utils/InitParameters.js +29 -16
  38. package/src/utils/URLHandler.js +11 -5
  39. package/src/utils/map.js +2 -2
  40. package/tests/components/ui/Photo.test.js +6 -6
  41. package/tests/utils/InitParameters.test.js +1 -0
  42. package/tests/utils/URLHandler.test.js +16 -6
@@ -1,11 +1,9 @@
1
1
  /* eslint-disable no-unused-vars */
2
2
 
3
3
  import "./Viewer.css";
4
- import { SYSTEM as PSSystem, DEFAULTS as PSDefaults } from "@photo-sphere-viewer/core";
5
- import URLHandler from "../../utils/URLHandler";
6
4
  import { linkMapAndPhoto, saveMapParamsToLocalStorage, getMapParamsFromLocalStorage } from "../../utils/map";
5
+ import PhotoViewer from "./PhotoViewer";
7
6
  import Basic from "./Basic";
8
- import Photo, { PSV_DEFAULT_ZOOM, PSV_ANIM_DURATION } from "../ui/Photo";
9
7
  import MapMore from "../ui/MapMore";
10
8
  import { initMapKeyboardHandler } from "../../utils/map";
11
9
  import { isNullId } from "../../utils/utils";
@@ -14,7 +12,7 @@ import { fa } from "../../utils/widgets";
14
12
  import { faPanorama } from "@fortawesome/free-solid-svg-icons/faPanorama";
15
13
  import { faMap } from "@fortawesome/free-solid-svg-icons/faMap";
16
14
  import { querySelectorDeep } from "query-selector-shadow-dom";
17
- import { default as InitParameters, alterPSVState, alterMapState, alterViewerState } from "../../utils/InitParameters";
15
+ import { default as InitParameters, alterMapState, alterViewerState } from "../../utils/InitParameters";
18
16
 
19
17
 
20
18
  export const PSV_ZOOM_DELTA = 20;
@@ -26,15 +24,17 @@ const MAP_MOVE_DELTA = 100;
26
24
  * Viewer is the main component of Panoramax JS library, showing pictures and map.
27
25
  *
28
26
  * This component has a [CorneredGrid](#Panoramax.components.layout.CorneredGrid) layout, you can use directly any slot element to pass custom widgets.
27
+ *
28
+ * If you need a viewer without map, checkout [Photo Viewer component](#Panoramax.components.core.PhotoViewer).
29
29
  * @class Panoramax.components.core.Viewer
30
30
  * @element pnx-viewer
31
- * @extends Panoramax.components.core.Basic
31
+ * @extends Panoramax.components.core.PhotoViewer
32
32
  * @property {Panoramax.components.ui.Loader} loader The loader screen
33
33
  * @property {Panoramax.utils.API} api The API manager
34
- * @property {Panoramax.components.ui.MapMore} [map] The MapLibre GL map itself (if map is enabled)
34
+ * @property {Panoramax.components.ui.MapMore} map The MapLibre GL map itself
35
35
  * @property {Panoramax.components.ui.Photo} psv The Photo Sphere Viewer component itself
36
36
  * @property {Panoramax.components.layout.CorneredGrid} grid The grid layout manager
37
- * @property {Panoramax.components.layout.Mini} [mini] The reduced/collapsed map/photo component (if map is enabled)
37
+ * @property {Panoramax.components.layout.Mini} mini The reduced/collapsed map/photo component
38
38
  * @property {Panoramax.components.ui.Popup} popup The popup container
39
39
  * @property {Panoramax.utils.URLHandler} urlHandler The URL query parameters manager
40
40
  * @fires Panoramax.components.core.Basic#select
@@ -65,7 +65,6 @@ const MAP_MOVE_DELTA = 100;
65
65
  * <pnx-viewer
66
66
  * endpoint="https://panoramax.openstreetmap.fr/"
67
67
  * widgets="false"
68
- * map="false"
69
68
  * >
70
69
  * <p slot="top-right">My custom text</p>
71
70
  * </pnx-viewer>
@@ -77,14 +76,14 @@ const MAP_MOVE_DELTA = 100;
77
76
  * />
78
77
  * ```
79
78
  */
80
- export default class Viewer extends Basic {
79
+ export default class Viewer extends PhotoViewer {
81
80
  /**
82
81
  * Component properties. All of [Basic properties](#Panoramax.components.core.Basic+properties) are available as well.
83
82
  * @memberof Panoramax.components.core.Viewer#
84
- * @mixes Panoramax.components.core.Basic#properties
83
+ * @mixes Panoramax.components.core.PhotoViewer#properties
85
84
  * @type {Object}
86
85
  * @property {string} endpoint URL to API to use (must be a [STAC API](https://github.com/radiantearth/stac-api-spec/blob/main/overview.md))
87
- * @property {boolean|object} [map=true] Should map be used (true/false), or an object with [any map option available in Map or MapMore class](#Panoramax.components.ui.MapMore).<br />Example: `map="{'background': 'aerial', 'theme': 'age'}"`
86
+ * @property {object} [map] An object with [any map option available in Map or MapMore class](#Panoramax.components.ui.MapMore).<br />Example: `map="{'background': 'aerial', 'theme': 'age'}"`
88
87
  * @property {object} [psv] [Any option to pass to Photo component](#Panoramax.components.ui.Photo) as an object.<br />Example: `psv="{'transitionDuration': 500, 'picturesNavigation': 'pic'}"`
89
88
  * @property {string} [url-parameters=true] Should the component add and update URL query parameters to save viewer state ?
90
89
  * @property {string} [focus=pic] The component showing up as main component (pic, map)
@@ -99,12 +98,9 @@ export default class Viewer extends Basic {
99
98
  */
100
99
  static properties = {
101
100
  map: {type: Object},
102
- psv: {type: Object},
103
- "url-parameters": {type: String},
104
101
  focus: {type: String, reflect: true},
105
102
  geocoder: {type: String},
106
- widgets: {type: String},
107
- ...Basic.properties
103
+ ...PhotoViewer.properties
108
104
  };
109
105
 
110
106
  constructor() {
@@ -112,16 +108,9 @@ export default class Viewer extends Basic {
112
108
 
113
109
  // Defaults
114
110
  this.map = true;
115
- this.psv = {};
116
- this["url-parameters"] = this.getAttribute("url-parameters") || true;
117
111
  this.geocoder = this.getAttribute("geocoder") || "nominatim";
118
- this.widgets = this.getAttribute("widgets") || "true";
119
-
120
- // Set variables
121
- this._prevSequence = null;
122
112
 
123
113
  // Init DOM containers
124
- this.grid = createWebComp("pnx-cornered-grid");
125
114
  this.mini = createWebComp("pnx-mini", {
126
115
  slot: "bottom-left",
127
116
  _parent: this,
@@ -130,80 +119,71 @@ export default class Viewer extends Basic {
130
119
  });
131
120
  this.mini.addEventListener("expand", this._toggleFocus.bind(this));
132
121
  this.grid.appendChild(this.mini);
133
- this.psvContainer = document.createElement("div");
134
122
  this.mapContainer = document.createElement("div");
135
- this.popup = createWebComp("pnx-popup", {_parent: this, onclose: this._onPopupClose.bind(this)});
123
+ }
124
+
125
+ /** @private */
126
+ _createInitParamsHandler() {
127
+ this._initParams = new InitParameters(
128
+ InitParameters.GetComponentProperties(Viewer, this),
129
+ Object.assign({}, this.urlHandler?.currentURLParams(), this.urlHandler?.currentURLParams(true)),
130
+ { map: getMapParamsFromLocalStorage() },
131
+ );
132
+ }
136
133
 
137
- if(this["url-parameters"] && this["url-parameters"] !== "false") {
138
- this.urlHandler = new URLHandler(this);
139
- this.onceReady().then(() => {
140
- this.urlHandler.listenToChanges();
141
- this.urlHandler._onParentChange();
134
+ /** @private */
135
+ _initWidgets() {
136
+ if(this._initParams.getParentInit().widgets !== "false") {
137
+ this.grid.appendChild(createWebComp("pnx-widget-zoom", {
138
+ slot: this.isWidthSmall() ? "top-left" : "bottom-right",
139
+ class: this.isWidthSmall() ? "pnx-only-map pnx-print-hidden" : "pnx-print-hidden",
140
+ _parent: this
141
+ }));
142
+ this.grid.appendChild(createWebComp("pnx-widget-share", {slot: "bottom-right", class: "pnx-print-hidden", _parent: this}));
143
+
144
+ this.legend = createWebComp("pnx-widget-legend", {
145
+ slot: this.isWidthSmall() ? "top" : "top-left",
146
+ _parent: this,
147
+ focus: this._initParams.getParentPostInit().focus,
148
+ picture: this._initParams.getParentPostInit().picture,
142
149
  });
150
+ this.grid.appendChild(this.legend);
151
+ this.grid.appendChild(createWebComp("pnx-widget-player", {slot: "top", _parent: this, class: "pnx-only-psv pnx-print-hidden"}));
152
+
153
+ this.grid.appendChild(createWebComp("pnx-widget-geosearch", {
154
+ slot: this.isWidthSmall() ? "top-right" : "top-left",
155
+ _parent: this,
156
+ class: "pnx-only-map pnx-print-hidden",
157
+ geocoder: this._initParams.getParentPostInit().geocoder,
158
+ }));
159
+ this.grid.appendChild(createWebComp("pnx-widget-mapfilters", {
160
+ slot: this.isWidthSmall() ? "top-right" : "top-left",
161
+ _parent: this,
162
+ "user-search": this.api._endpoints.user_search !== null && this.api._endpoints.user_tiles !== null,
163
+ "quality-score": this.map?._hasQualityScore?.() || false,
164
+ class: "pnx-only-map pnx-print-hidden",
165
+ }));
166
+ this.grid.appendChild(createWebComp("pnx-widget-maplayers", { slot: "top-right", _parent: this, class: "pnx-only-map pnx-print-hidden" }));
143
167
  }
144
168
  }
145
169
 
146
170
  /** @private */
147
171
  connectedCallback() {
148
- super.connectedCallback();
172
+ Basic.prototype.connectedCallback.call(this);
149
173
  this._moveChildToGrid();
150
174
 
151
175
  this.onceAPIReady().then(async () => {
152
176
  this.loader.setAttribute("value", 30);
153
- this._initParams = new InitParameters(
154
- InitParameters.GetComponentProperties(Viewer, this),
155
- Object.assign({}, this.urlHandler?.currentURLParams(), this.urlHandler?.currentURLParams(true)),
156
- { map: getMapParamsFromLocalStorage() },
157
- );
177
+ this._createInitParamsHandler();
158
178
 
159
- const myInitParams = this._initParams.getParentInit();
160
179
  const myPostInitParams = this._initParams.getParentPostInit();
161
180
 
162
181
  this._initPSV();
163
- if(myInitParams.map) { await this._initMap(); }
164
- else {
165
- this.grid.removeChild(this.mini);
166
- this.psvContainer.setAttribute("slot", "bg");
167
- this.grid.appendChild(this.psvContainer);
168
- }
169
-
170
- // Init various widgets
171
- if(myInitParams.widgets !== "false") {
172
- this.grid.appendChild(createWebComp("pnx-widget-zoom", {
173
- slot: this.isWidthSmall() ? "top-left" : "bottom-right",
174
- class: this.isWidthSmall() ? "pnx-only-map pnx-print-hidden" : "pnx-print-hidden",
175
- _parent: this
176
- }));
177
- this.grid.appendChild(createWebComp("pnx-widget-share", {slot: "bottom-right", class: "pnx-print-hidden", _parent: this}));
178
-
179
- if(this.map) {
180
- this.grid.appendChild(createWebComp("pnx-widget-geosearch", {
181
- slot: this.isWidthSmall() ? "top-right" : "top-left",
182
- _parent: this,
183
- class: "pnx-only-map pnx-print-hidden",
184
- geocoder: myPostInitParams.geocoder,
185
- }));
186
- this.grid.appendChild(createWebComp("pnx-widget-mapfilters", {
187
- slot: this.isWidthSmall() ? "top-right" : "top-left",
188
- _parent: this,
189
- "user-search": this.api._endpoints.user_search !== null && this.api._endpoints.user_tiles !== null,
190
- "quality-score": this.map?._hasQualityScore?.() || false,
191
- class: "pnx-only-map pnx-print-hidden",
192
- }));
193
- this.grid.appendChild(createWebComp("pnx-widget-maplayers", { slot: "top-right", _parent: this, class: "pnx-only-map pnx-print-hidden" }));
194
- }
195
-
196
- this.legend = createWebComp("pnx-widget-legend", {
197
- slot: this.isWidthSmall() ? "top" : "top-left",
198
- _parent: this,
199
- focus: myPostInitParams.focus
200
- });
201
- this.grid.appendChild(this.legend);
202
- this.grid.appendChild(createWebComp("pnx-widget-player", {slot: "top", _parent: this, class: "pnx-only-psv pnx-print-hidden"}));
203
- }
204
-
182
+ await this._initMap();
183
+ this._initWidgets();
205
184
  alterViewerState(this, myPostInitParams);
206
185
  this._handleKeyboardManagement();
186
+
207
187
  if(myPostInitParams.picture) {
208
188
  this.psv.addEventListener("picture-loaded", () => this.loader.dismiss(), {once: true});
209
189
  }
@@ -234,48 +214,23 @@ export default class Viewer extends Basic {
234
214
  attributeChangedCallback(name, old, value) {
235
215
  super.attributeChangedCallback(name, old, value);
236
216
 
237
- // First pic load : show map in mini component
238
- if(name === "picture" && isNullId(old) && !isNullId(value)) {
239
- this.mini.removeAttribute("collapsed");
240
- }
241
- if(name === "picture" && isNullId(value) && this.map && this.isMapWide()) {
242
- this.mini.classList.add("pnx-hidden");
217
+ if(name === "picture") {
218
+ this.legend?.setAttribute?.("picture", value);
219
+
220
+ // First pic load : show map in mini component
221
+ if(isNullId(old) && !isNullId(value)) {
222
+ this.mini.removeAttribute("collapsed");
223
+ }
224
+ if(isNullId(value) && this.map && this.isMapWide()) {
225
+ this.mini.classList.add("pnx-hidden");
226
+ }
243
227
  }
228
+
244
229
  if(name === "focus") {
245
230
  this._setFocus(value);
246
231
  }
247
232
  }
248
233
 
249
- /** @private */
250
- render() {
251
- return [this.loader, this.grid, this.popup, this.slot];
252
- }
253
-
254
- /**
255
- * Waiting for Photo Sphere Viewer to be available.
256
- * @returns {Promise} When PSV is ready to use
257
- * @memberof Panoramax.components.core.Viewer#
258
- */
259
- oncePSVReady() {
260
- let waiter;
261
- return new Promise(resolve => {
262
- waiter = setInterval(() => {
263
- if(typeof this.psv === "object") {
264
- if(this.psv.container) {
265
- clearInterval(waiter);
266
- resolve();
267
- }
268
- else if(this.psv.addEventListener) {
269
- this.psv.addEventListener("ready", () => {
270
- clearInterval(waiter);
271
- resolve();
272
- }, {once: true});
273
- }
274
- }
275
- }, 250);
276
- });
277
- }
278
-
279
234
  /**
280
235
  * Waiting for map to be available.
281
236
  * @returns {Promise} When map is ready to use
@@ -303,67 +258,6 @@ export default class Viewer extends Basic {
303
258
  });
304
259
  }
305
260
 
306
- /**
307
- * Waits for first picture to display on PSV.
308
- * @returns {Promise}
309
- * @fulfil {undefined} When picture is shown
310
- * @memberof Panoramax.components.core.Viewer#
311
- */
312
- onceFirstPicLoaded() {
313
- return this.oncePSVReady().then(() => {
314
- if(this.psv.getPictureMetadata()) { return Promise.resolve(); }
315
- else {
316
- return new Promise(resolve => {
317
- this.psv.addEventListener("picture-loaded", resolve, {once: true});
318
- });
319
- }
320
- });
321
- }
322
-
323
- /** @private */
324
- _initPSV() {
325
- try {
326
- this.psv = new Photo(this, this.psvContainer, {
327
- shouldGoFast: this._psvShouldGoFast.bind(this),
328
- keyboard: "always",
329
- keyboardActions: {
330
- ...PSDefaults.keyboardActions,
331
- "8": "ROTATE_UP",
332
- "2": "ROTATE_DOWN",
333
- "4": "ROTATE_LEFT",
334
- "6": "ROTATE_RIGHT",
335
-
336
- "PageUp": () => this.psv.goToNextPicture(),
337
- "9": () => this.psv.goToNextPicture(),
338
-
339
- "PageDown": () => this.psv.goToPrevPicture(),
340
- "3": () => this.psv.goToPrevPicture(),
341
-
342
- "5": () => this.moveCenter(),
343
- "*": () => this.moveCenter(),
344
-
345
- "Home": () => this._toggleFocus(),
346
- "7": () => this._toggleFocus(),
347
-
348
- "End": () => this.mini.toggleAttribute("collapsed"),
349
- "1": () => this.mini.toggleAttribute("collapsed"),
350
-
351
- " ": () => this.psv.toggleSequencePlaying(),
352
- "0": () => this.psv.toggleSequencePlaying(),
353
- },
354
- ...this._initParams.getPSVInit()
355
- });
356
- this.oncePSVReady().then(() => {
357
- this.loader.setAttribute("value", 50);
358
- alterPSVState(this.psv, this._initParams.getPSVPostInit());
359
- });
360
- }
361
- catch(e) {
362
- let err = !PSSystem.isWebGLSupported ? this._t.pnx.error_webgl : this._t.pnx.error_psv;
363
- this.loader.dismiss(e, err);
364
- }
365
- }
366
-
367
261
  /**
368
262
  * Inits MapLibre GL component
369
263
  *
@@ -424,69 +318,6 @@ export default class Viewer extends Basic {
424
318
  }
425
319
  }
426
320
 
427
- /**
428
- * Given context, should tiles be loaded in PSV.
429
- * @private
430
- */
431
- _psvShouldGoFast() {
432
- return (this.psv._sequencePlaying && this.psv.getTransitionDuration() < 1000)
433
- || (this.map && this.isMapWide());
434
- }
435
-
436
- /** @private */
437
- _moveChildToGrid() {
438
- for(let i=0; i < this.childNodes.length; i++) {
439
- let n = this.childNodes[i];
440
- if(n.getAttribute?.("slot")) {
441
- // Add parent + translation for our components
442
- if(n.tagName?.toLowerCase().startsWith("pnx-")) {
443
- n._parent = this;
444
- n._t = this._t;
445
- }
446
- this.grid.appendChild(n);
447
- }
448
- }
449
- }
450
-
451
- /**
452
- * Change full-page popup visibility and content
453
- * @memberof Panoramax.components.core.Viewer#
454
- * @param {boolean} visible True to make it appear
455
- * @param {string|Element[]} [content] The new popup content
456
- */
457
- setPopup(visible, content = null) {
458
- if(visible) { this.popup.setAttribute("visible", ""); }
459
- else { this.popup.removeAttribute("visible"); }
460
-
461
- this.popup.innerHTML = "";
462
- if(typeof content === "string") { this.popup.innerHTML = content; }
463
- else if(Array.isArray(content)) { content.forEach(c => this.popup.appendChild(c)); }
464
- }
465
-
466
- /** @private */
467
- _onPopupClose() {
468
- this.dispatchEvent(new CustomEvent("focus-changed", { detail: { focus: this.map && this.isMapWide() ? "map" : "pic" } }));
469
- }
470
-
471
- /** @private */
472
- _showQualityScoreDoc() {
473
- this.setPopup(true, [createWebComp("pnx-quality-score-doc", {_t: this._t})]);
474
- }
475
-
476
- /** @private */
477
- _showReportForm() {
478
- if(!this.psv.getPictureMetadata()) { throw new Error("No picture currently selected"); }
479
- this.setPopup(true, [createWebComp("pnx-report-form", {_parent: this})]);
480
- this.dispatchEvent(new CustomEvent("focus-changed", { detail: { focus: "meta" } }));
481
- }
482
-
483
- /** @private */
484
- _showPictureMetadata() {
485
- if(!this.psv.getPictureMetadata()) { throw new Error("No picture currently selected"); }
486
- this.setPopup(true, [createWebComp("pnx-picture-metadata", {_parent: this})]);
487
- this.dispatchEvent(new CustomEvent("focus-changed", { detail: { focus: "meta" } }));
488
- }
489
-
490
321
  /**
491
322
  * Move the view of main component to its center.
492
323
  * For map, center view on selected picture.
@@ -501,47 +332,10 @@ export default class Viewer extends Basic {
501
332
  this.map.flyTo({ center: meta.gps, zoom: 20 });
502
333
  }
503
334
  else {
504
- this._psvAnimate({
505
- speed: PSV_ANIM_DURATION,
506
- yaw: 0,
507
- pitch: 0,
508
- zoom: PSV_DEFAULT_ZOOM
509
- });
335
+ super.moveCenter();
510
336
  }
511
337
  }
512
338
 
513
- /**
514
- * Moves the view of main component slightly to the left.
515
- * @memberof Panoramax.components.core.Viewer#
516
- */
517
- moveLeft() {
518
- this._moveToDirection("left");
519
- }
520
-
521
- /**
522
- * Moves the view of main component slightly to the right.
523
- * @memberof Panoramax.components.core.Viewer#
524
- */
525
- moveRight() {
526
- this._moveToDirection("right");
527
- }
528
-
529
- /**
530
- * Moves the view of main component slightly to the top.
531
- * @memberof Panoramax.components.core.Viewer#
532
- */
533
- moveUp() {
534
- this._moveToDirection("up");
535
- }
536
-
537
- /**
538
- * Moves the view of main component slightly to the bottom.
539
- * @memberof Panoramax.components.core.Viewer#
540
- */
541
- moveDown() {
542
- this._moveToDirection("down");
543
- }
544
-
545
339
  /**
546
340
  * Moves map or picture viewer to given direction.
547
341
  * @param {string} dir Direction to move to (up, left, down, right)
@@ -567,43 +361,16 @@ export default class Viewer extends Basic {
567
361
  this.map.panBy(pan);
568
362
  }
569
363
  else {
570
- let pos = this.psv.getPosition();
571
- switch(dir) {
572
- case "up":
573
- pos.pitch += PSV_MOVE_DELTA;
574
- break;
575
- case "left":
576
- pos.yaw -= PSV_MOVE_DELTA;
577
- break;
578
- case "down":
579
- pos.pitch -= PSV_MOVE_DELTA;
580
- break;
581
- case "right":
582
- pos.yaw += PSV_MOVE_DELTA;
583
- break;
584
- }
585
- this._psvAnimate({ speed: PSV_ANIM_DURATION, ...pos });
364
+ super._moveToDirection(dir);
586
365
  }
587
366
  }
588
367
 
589
- /**
590
- * Overrided PSV animate function to ensure a single animation plays at once.
591
- * @param {object} options PSV animate options
592
- * @private
593
- */
594
- _psvAnimate(options) {
595
- if(this._lastPsvAnim) { this._lastPsvAnim.cancel(); }
596
- this._lastPsvAnim = this.psv.animate(options);
597
- }
598
-
599
368
  /**
600
369
  * Is the map shown as main element instead of viewer (wide map mode) ?
601
370
  * @memberof Panoramax.components.core.Viewer#
602
371
  * @returns {boolean} True if map is wider than viewer
603
- * @throws {Error} If map is not enabled
604
372
  */
605
373
  isMapWide() {
606
- if(!this.map) { throw new Error("Map is not enabled"); }
607
374
  return this.mapContainer.parentNode == this.grid;
608
375
  }
609
376
 
@@ -695,11 +462,9 @@ export default class Viewer extends Basic {
695
462
  /**
696
463
  * Toggle the viewer focus (either on picture or map)
697
464
  * @memberof Panoramax.components.core.Viewer#
698
- * @throws {Error} If map is not enabled
699
465
  * @private
700
466
  */
701
467
  _toggleFocus() {
702
- if(!this.map) { throw new Error("Map is not enabled"); }
703
468
  this._setFocus(this.isMapWide() ? "pic" : "map");
704
469
  }
705
470
 
@@ -9,3 +9,4 @@ export {default as Basic} from "./Basic";
9
9
  export {default as CoverageMap} from "./CoverageMap";
10
10
  export {default as Editor} from "./Editor";
11
11
  export {default as Viewer} from "./Viewer";
12
+ export {default as PhotoViewer} from "./PhotoViewer";
@@ -1,10 +1,7 @@
1
1
  import {LitElement, html, nothing, css} from "lit";
2
- import { fa } from "../../utils/widgets";
3
- import { faInfoCircle } from "@fortawesome/free-solid-svg-icons/faInfoCircle";
4
- import PanoramaxImg from "../../img/panoramax.svg";
5
2
 
6
3
  /**
7
- * Map legend displays information about map sources and Panoramax.
4
+ * Map legend displays information about map sources.
8
5
  * @class Panoramax.components.menus.MapLegend
9
6
  * @element pnx-map-legend
10
7
  * @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
@@ -22,14 +19,6 @@ export default class MapLegend extends LitElement {
22
19
  small {
23
20
  font-size: 1em;
24
21
  }
25
- .presentation {
26
- display: flex;
27
- gap: 5px;
28
- align-items: center;
29
- }
30
- .logo {
31
- width: 45px;
32
- }
33
22
  `;
34
23
 
35
24
  /** @private */
@@ -37,15 +26,6 @@ export default class MapLegend extends LitElement {
37
26
  const mapAttrib = this._parent?.map?._attribution?._innerContainer;
38
27
 
39
28
  return html`
40
- <div class="presentation">
41
- <img class="logo" src=${PanoramaxImg} alt="" />
42
- <div>
43
- Panoramax est le géocommun des photos de rues.
44
- <pnx-link-button title=${this._parent?._t.map.more_panoramax} kind="outline" href="https://panoramax.fr" target="_blank">
45
- ${fa(faInfoCircle, { styles: {height: "12px", "margin-inline": "3px"}})}
46
- </pnx-link-button>
47
- </div>
48
- </div>
49
29
  ${mapAttrib && mapAttrib.innerHTML.length > 0 ? html`${this._parent?._t.map.map_data}<br />${mapAttrib}` : nothing}
50
30
  `;
51
31
  }
@@ -3,7 +3,7 @@ import LoaderImgBase from "../../img/loader_base.jpg";
3
3
  import LogoDead from "../../img/logo_dead.svg";
4
4
  import {
5
5
  getDistance, positionToXYZ, xyzToPosition,
6
- getRelativeHeading, BASE_PANORAMA_ID,
6
+ getRelativeHeading, BASE_PANORAMA_ID, isNullId,
7
7
  } from "../../utils/utils";
8
8
  import { apiFeatureToPSVNode } from "../../utils/picture";
9
9
 
@@ -155,7 +155,7 @@ export default class Photo extends PSViewer {
155
155
  * @memberof Panoramax.components.ui.Photo#
156
156
  */
157
157
  async _getNodeFromAPI(picId) {
158
- if(!picId || picId === BASE_PANORAMA_ID) { return BASE_PANORAMA_NODE; }
158
+ if(isNullId(picId)) { return BASE_PANORAMA_NODE; }
159
159
 
160
160
  const picApiResponse = await fetch(
161
161
  this._parent.api.getPictureMetadataUrl(picId, this._picturesSequences[picId]),
@@ -490,10 +490,20 @@ export default class Photo extends PSViewer {
490
490
  * @returns {object} Picture metadata
491
491
  */
492
492
  getPictureMetadata() {
493
- if(this._myVTour?.state?.currentNode?.id === BASE_PANORAMA_ID) { return null; }
493
+ if(isNullId(this._myVTour?.state?.currentNode?.id)) { return null; }
494
494
  return this._myVTour.state.currentNode ? Object.assign({}, this._myVTour.state.currentNode) : null;
495
495
  }
496
496
 
497
+ /**
498
+ * Get current picture ID, or loading picture ID if any.
499
+ * @memberof Panoramax.components.ui.Photo#
500
+ * @returns {string|null} Picture ID (current or loading), or null if none is selected.
501
+ */
502
+ getPictureId() {
503
+ const id = this._myVTour?.state?.loadingNode || this._myVTour?.state?.currentNode?.id;
504
+ return isNullId(id) ? null : id;
505
+ }
506
+
497
507
  /**
498
508
  * Handler for select event.
499
509
  * @private
@@ -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";