@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
@@ -0,0 +1,441 @@
1
+ /* eslint-disable import/no-unused-modules */
2
+ /* eslint-disable no-unused-vars */
3
+
4
+ import "./PhotoViewer.css";
5
+ import { SYSTEM as PSSystem, DEFAULTS as PSDefaults } from "@photo-sphere-viewer/core";
6
+ import URLHandler from "../../utils/URLHandler";
7
+ import Basic from "./Basic";
8
+ import Photo, { PSV_DEFAULT_ZOOM, PSV_ANIM_DURATION } from "../ui/Photo";
9
+ import { createWebComp } from "../../utils/widgets";
10
+ import { default as InitParameters, alterPSVState, alterMapState, alterPhotoViewerState } from "../../utils/InitParameters";
11
+
12
+
13
+ export const PSV_ZOOM_DELTA = 20;
14
+ const PSV_MOVE_DELTA = Math.PI / 6;
15
+
16
+
17
+ /**
18
+ * Photo Viewer is a component showing pictures (without any map).
19
+ *
20
+ * This component has a [CorneredGrid](#Panoramax.components.layout.CorneredGrid) layout, you can use directly any slot element to pass custom widgets.
21
+ *
22
+ * If you need a viewer with map, checkout [Viewer component](#Panoramax.components.core.Viewer).
23
+ * @class Panoramax.components.core.PhotoViewer
24
+ * @element pnx-photo-viewer
25
+ * @extends Panoramax.components.core.Basic
26
+ * @property {Panoramax.components.ui.Loader} loader The loader screen
27
+ * @property {Panoramax.utils.API} api The API manager
28
+ * @property {Panoramax.components.ui.Photo} psv The Photo Sphere Viewer component itself
29
+ * @property {Panoramax.components.layout.CorneredGrid} grid The grid layout manager
30
+ * @property {Panoramax.components.ui.Popup} popup The popup container
31
+ * @property {Panoramax.utils.URLHandler} urlHandler The URL query parameters manager
32
+ * @fires Panoramax.components.core.Basic#select
33
+ * @fires Panoramax.components.core.Basic#ready
34
+ * @fires Panoramax.components.core.Basic#broken
35
+ * @slot `top-left` The top-left corner
36
+ * @slot `top` The top middle corner
37
+ * @slot `top-right` The top-right corner
38
+ * @slot `bottom-left` The bottom-left corner
39
+ * @slot `bottom` The bottom middle corner
40
+ * @slot `bottom-right` The bottom-right corner
41
+ * @example
42
+ * ```html
43
+ * <!-- Basic example -->
44
+ * <pnx-photo-viewer
45
+ * endpoint="https://panoramax.openstreetmap.fr/"
46
+ * />
47
+ *
48
+ * <!-- With slotted widgets -->
49
+ * <pnx-photo-viewer
50
+ * endpoint="https://panoramax.openstreetmap.fr/"
51
+ * >
52
+ * <p slot="top-right">My custom text</p>
53
+ * </pnx-photo-viewer>
54
+ *
55
+ * <!-- With only your custom widgets -->
56
+ * <pnx-photo-viewer
57
+ * endpoint="https://panoramax.openstreetmap.fr/"
58
+ * widgets="false"
59
+ * >
60
+ * <p slot="top-right">My custom text</p>
61
+ * </pnx-photo-viewer>
62
+ * ```
63
+ */
64
+ export default class PhotoViewer extends Basic {
65
+ /**
66
+ * Component properties. All of [Basic properties](#Panoramax.components.core.Basic+properties) are available as well.
67
+ * @memberof Panoramax.components.core.PhotoViewer#
68
+ * @mixes Panoramax.components.core.Basic#properties
69
+ * @type {Object}
70
+ * @property {string} endpoint URL to API to use (must be a [STAC API](https://github.com/radiantearth/stac-api-spec/blob/main/overview.md))
71
+ * @property {object} [psv] [Any option to pass to Photo component](#Panoramax.components.ui.Photo) as an object.<br />Example: `psv="{'transitionDuration': 500, 'picturesNavigation': 'pic'}"`
72
+ * @property {string} [widgets=true] Use default set of widgets ? Set to false to avoid any widget to show up, and use slots to populate as you like.
73
+ * @property {string} [picture] The picture ID to display
74
+ * @property {string} [sequence] The sequence ID of the picture displayed
75
+ * @property {object} [fetchOptions] Set custom options for fetch calls made against API ([same syntax as fetch options parameter](https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters))
76
+ * @property {string} [lang] To override language used for labels. Defaults to using user's preferred languages.
77
+ * @property {string} [url-parameters=true] Should the component add and update URL query parameters to save viewer state ?
78
+ */
79
+ static properties = {
80
+ psv: {type: Object},
81
+ widgets: {type: String},
82
+ "url-parameters": {type: String},
83
+ ...Basic.properties
84
+ };
85
+
86
+ constructor() {
87
+ super();
88
+
89
+ // Defaults
90
+ this.psv = {};
91
+ this["url-parameters"] = this.getAttribute("url-parameters") || true;
92
+ this.widgets = this.getAttribute("widgets") || "true";
93
+
94
+ // Init DOM containers
95
+ this.grid = createWebComp("pnx-cornered-grid");
96
+ this.psvContainer = document.createElement("div");
97
+ this.psvContainer.setAttribute("slot", "bg");
98
+ this.grid.appendChild(this.psvContainer);
99
+ this.popup = createWebComp("pnx-popup", {_parent: this, onclose: this._onPopupClose.bind(this)});
100
+
101
+ if(this["url-parameters"] && this["url-parameters"] !== "false") {
102
+ this.urlHandler = new URLHandler(this);
103
+ this.onceReady().then(() => {
104
+ this.urlHandler.listenToChanges();
105
+ this.urlHandler._onParentChange();
106
+ });
107
+ }
108
+ }
109
+
110
+ /** @private */
111
+ _createInitParamsHandler() {
112
+ this._initParams = new InitParameters(
113
+ InitParameters.GetComponentProperties(PhotoViewer, this),
114
+ Object.assign({}, this.urlHandler?.currentURLParams(), this.urlHandler?.currentURLParams(true)),
115
+ {},
116
+ );
117
+ }
118
+
119
+ /** @private */
120
+ _initWidgets() {
121
+ if(this._initParams.getParentInit().widgets !== "false") {
122
+ if(!this.isWidthSmall()) {
123
+ this.grid.appendChild(createWebComp("pnx-widget-zoom", {
124
+ slot: "bottom-right",
125
+ class: "pnx-print-hidden",
126
+ _parent: this
127
+ }));
128
+ }
129
+
130
+ this.grid.appendChild(createWebComp("pnx-widget-share", {slot: "bottom-right", class: "pnx-print-hidden", _parent: this}));
131
+
132
+ this.legend = createWebComp("pnx-widget-legend", {
133
+ slot: this.isWidthSmall() ? "top" : "top-left",
134
+ _parent: this,
135
+ focus: this._initParams.getParentPostInit().focus,
136
+ picture: this._initParams.getParentPostInit().picture,
137
+ });
138
+ this.grid.appendChild(this.legend);
139
+ this.grid.appendChild(createWebComp("pnx-widget-player", {slot: "top", _parent: this, class: "pnx-only-psv pnx-print-hidden"}));
140
+ }
141
+ }
142
+
143
+ /** @private */
144
+ connectedCallback() {
145
+ super.connectedCallback();
146
+ this._moveChildToGrid();
147
+
148
+ this.onceAPIReady().then(async () => {
149
+ this.loader.setAttribute("value", 30);
150
+ this._createInitParamsHandler();
151
+
152
+ const myPostInitParams = this._initParams.getParentPostInit();
153
+
154
+ this._initPSV();
155
+ this._initWidgets();
156
+ alterPhotoViewerState(this, myPostInitParams);
157
+ this._handleKeyboardManagement();
158
+
159
+ if(myPostInitParams.picture) {
160
+ this.psv.addEventListener("picture-loaded", () => this.loader.dismiss(), {once: true});
161
+ }
162
+ else {
163
+ this.loader.dismiss();
164
+ }
165
+ });
166
+ }
167
+
168
+ getClassName() {
169
+ return "PhotoViewer";
170
+ }
171
+
172
+ /**
173
+ * Waits for PhotoViewer to be completely ready (map & PSV loaded, first picture also if one is wanted)
174
+ * @returns {Promise} When viewer is ready
175
+ * @memberof Panoramax.components.core.PhotoViewer#
176
+ */
177
+ onceReady() {
178
+ return this.oncePSVReady().then(() => {
179
+ if(this._initParams.getParentPostInit().picture && !this.psv.getPictureMetadata()) { return this.onceFirstPicLoaded(); }
180
+ else { return Promise.resolve(); }
181
+ });
182
+ }
183
+
184
+ /** @private */
185
+ render() {
186
+ return [this.loader, this.grid, this.popup, this.slot];
187
+ }
188
+
189
+ /**
190
+ * Waiting for Photo Sphere Viewer to be available.
191
+ * @returns {Promise} When PSV is ready to use
192
+ * @memberof Panoramax.components.core.PhotoViewer#
193
+ */
194
+ oncePSVReady() {
195
+ let waiter;
196
+ return new Promise(resolve => {
197
+ waiter = setInterval(() => {
198
+ if(typeof this.psv === "object") {
199
+ if(this.psv.container) {
200
+ clearInterval(waiter);
201
+ resolve();
202
+ }
203
+ else if(this.psv.addEventListener) {
204
+ this.psv.addEventListener("ready", () => {
205
+ clearInterval(waiter);
206
+ resolve();
207
+ }, {once: true});
208
+ }
209
+ }
210
+ }, 250);
211
+ });
212
+ }
213
+
214
+ /**
215
+ * Waits for first picture to display on PSV.
216
+ * @returns {Promise}
217
+ * @fulfil {undefined} When picture is shown
218
+ * @memberof Panoramax.components.core.PhotoViewer#
219
+ */
220
+ onceFirstPicLoaded() {
221
+ return this.oncePSVReady().then(() => {
222
+ if(this.psv.getPictureMetadata()) { return Promise.resolve(); }
223
+ else {
224
+ return new Promise(resolve => {
225
+ this.psv.addEventListener("picture-loaded", resolve, {once: true});
226
+ });
227
+ }
228
+ });
229
+ }
230
+
231
+ /** @private */
232
+ _initPSV() {
233
+ try {
234
+ this.psv = new Photo(this, this.psvContainer, {
235
+ shouldGoFast: this._psvShouldGoFast.bind(this),
236
+ keyboard: "always",
237
+ keyboardActions: {
238
+ ...PSDefaults.keyboardActions,
239
+ "8": "ROTATE_UP",
240
+ "2": "ROTATE_DOWN",
241
+ "4": "ROTATE_LEFT",
242
+ "6": "ROTATE_RIGHT",
243
+
244
+ "PageUp": () => this.psv.goToNextPicture(),
245
+ "9": () => this.psv.goToNextPicture(),
246
+
247
+ "PageDown": () => this.psv.goToPrevPicture(),
248
+ "3": () => this.psv.goToPrevPicture(),
249
+
250
+ "5": () => this.moveCenter(),
251
+ "*": () => this.moveCenter(),
252
+
253
+ "Home": () => this._toggleFocus(),
254
+ "7": () => this._toggleFocus(),
255
+
256
+ "End": () => this.mini.toggleAttribute("collapsed"),
257
+ "1": () => this.mini.toggleAttribute("collapsed"),
258
+
259
+ " ": () => this.psv.toggleSequencePlaying(),
260
+ "0": () => this.psv.toggleSequencePlaying(),
261
+ },
262
+ ...this._initParams.getPSVInit()
263
+ });
264
+ this.oncePSVReady().then(() => {
265
+ this.loader.setAttribute("value", 50);
266
+ alterPSVState(this.psv, this._initParams.getPSVPostInit());
267
+ });
268
+ }
269
+ catch(e) {
270
+ let err = !PSSystem.isWebGLSupported ? this._t.pnx.error_webgl : this._t.pnx.error_psv;
271
+ this.loader.dismiss(e, err);
272
+ }
273
+ }
274
+
275
+ /** @private */
276
+ _handleKeyboardManagement() {
277
+ // Switchers
278
+ const keytonone = () => this.psv.stopKeyboardControl();
279
+ const keytopsv = () => this.psv.startKeyboardControl();
280
+
281
+ // Popup
282
+ this.popup.addEventListener("open", () => keytonone());
283
+ this.popup.addEventListener("close", () => keytopsv());
284
+
285
+ // Widgets
286
+ for(let cn of this.grid.childNodes) {
287
+ if(cn.getAttribute("slot") !== "bg") {
288
+ cn.addEventListener("focusin", () => keytonone());
289
+ cn.addEventListener("focusout", () => keytopsv());
290
+ }
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Given context, should tiles be loaded in PSV.
296
+ * @private
297
+ */
298
+ _psvShouldGoFast() {
299
+ return (this.psv._sequencePlaying && this.psv.getTransitionDuration() < 1000);
300
+ }
301
+
302
+ /** @private */
303
+ _moveChildToGrid() {
304
+ for(let i=0; i < this.childNodes.length; i++) {
305
+ let n = this.childNodes[i];
306
+ if(n.getAttribute?.("slot")) {
307
+ // Add parent + translation for our components
308
+ if(n.tagName?.toLowerCase().startsWith("pnx-")) {
309
+ n._parent = this;
310
+ n._t = this._t;
311
+ }
312
+ this.grid.appendChild(n);
313
+ }
314
+ }
315
+ }
316
+
317
+ /**
318
+ * Change full-page popup visibility and content
319
+ * @memberof Panoramax.components.core.PhotoViewer#
320
+ * @param {boolean} visible True to make it appear
321
+ * @param {string|Element[]} [content] The new popup content
322
+ */
323
+ setPopup(visible, content = null) {
324
+ if(visible) { this.popup.setAttribute("visible", ""); }
325
+ else { this.popup.removeAttribute("visible"); }
326
+
327
+ this.popup.innerHTML = "";
328
+ if(typeof content === "string") { this.popup.innerHTML = content; }
329
+ else if(Array.isArray(content)) { content.forEach(c => this.popup.appendChild(c)); }
330
+ }
331
+
332
+ /** @private */
333
+ _onPopupClose() {
334
+ this.dispatchEvent(new CustomEvent("focus-changed", { detail: { focus: this.map && this.isMapWide() ? "map" : "pic" } }));
335
+ }
336
+
337
+ /** @private */
338
+ _showQualityScoreDoc() {
339
+ this.setPopup(true, [createWebComp("pnx-quality-score-doc", {_t: this._t})]);
340
+ }
341
+
342
+ /** @private */
343
+ _showReportForm() {
344
+ if(!this.psv.getPictureMetadata()) { throw new Error("No picture currently selected"); }
345
+ this.setPopup(true, [createWebComp("pnx-report-form", {_parent: this})]);
346
+ this.dispatchEvent(new CustomEvent("focus-changed", { detail: { focus: "meta" } }));
347
+ }
348
+
349
+ /** @private */
350
+ _showPictureMetadata() {
351
+ if(!this.psv.getPictureMetadata()) { throw new Error("No picture currently selected"); }
352
+ this.setPopup(true, [createWebComp("pnx-picture-metadata", {_parent: this})]);
353
+ this.dispatchEvent(new CustomEvent("focus-changed", { detail: { focus: "meta" } }));
354
+ }
355
+
356
+ /**
357
+ * Move the view of main component to its center.
358
+ * For map, center view on selected picture.
359
+ * For picture, center view on image center.
360
+ * @memberof Panoramax.components.core.PhotoViewer#
361
+ */
362
+ moveCenter() {
363
+ const meta = this.psv.getPictureMetadata();
364
+ if(!meta) { return; }
365
+
366
+ this._psvAnimate({
367
+ speed: PSV_ANIM_DURATION,
368
+ yaw: 0,
369
+ pitch: 0,
370
+ zoom: PSV_DEFAULT_ZOOM
371
+ });
372
+ }
373
+
374
+ /**
375
+ * Moves the view of main component slightly to the left.
376
+ * @memberof Panoramax.components.core.PhotoViewer#
377
+ */
378
+ moveLeft() {
379
+ this._moveToDirection("left");
380
+ }
381
+
382
+ /**
383
+ * Moves the view of main component slightly to the right.
384
+ * @memberof Panoramax.components.core.PhotoViewer#
385
+ */
386
+ moveRight() {
387
+ this._moveToDirection("right");
388
+ }
389
+
390
+ /**
391
+ * Moves the view of main component slightly to the top.
392
+ * @memberof Panoramax.components.core.PhotoViewer#
393
+ */
394
+ moveUp() {
395
+ this._moveToDirection("up");
396
+ }
397
+
398
+ /**
399
+ * Moves the view of main component slightly to the bottom.
400
+ * @memberof Panoramax.components.core.PhotoViewer#
401
+ */
402
+ moveDown() {
403
+ this._moveToDirection("down");
404
+ }
405
+
406
+ /**
407
+ * Moves map or picture viewer to given direction.
408
+ * @param {string} dir Direction to move to (up, left, down, right)
409
+ * @private
410
+ */
411
+ _moveToDirection(dir) {
412
+ let pos = this.psv.getPosition();
413
+ switch(dir) {
414
+ case "up":
415
+ pos.pitch += PSV_MOVE_DELTA;
416
+ break;
417
+ case "left":
418
+ pos.yaw -= PSV_MOVE_DELTA;
419
+ break;
420
+ case "down":
421
+ pos.pitch -= PSV_MOVE_DELTA;
422
+ break;
423
+ case "right":
424
+ pos.yaw += PSV_MOVE_DELTA;
425
+ break;
426
+ }
427
+ this._psvAnimate({ speed: PSV_ANIM_DURATION, ...pos });
428
+ }
429
+
430
+ /**
431
+ * Overrided PSV animate function to ensure a single animation plays at once.
432
+ * @param {object} options PSV animate options
433
+ * @private
434
+ */
435
+ _psvAnimate(options) {
436
+ if(this._lastPsvAnim) { this._lastPsvAnim.cancel(); }
437
+ this._lastPsvAnim = this.psv.animate(options);
438
+ }
439
+ }
440
+
441
+ customElements.define("pnx-photo-viewer", PhotoViewer);