@panoramax/web-viewer 5.0.0-develop-7f1fc6a0 → 5.0.0-develop-4a32f7a0

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.
@@ -9,7 +9,7 @@ import BasicStyles from "./Basic.css" with { type: "css" };
9
9
  document.adoptedStyleSheets.push(AtkinsonStyles);
10
10
  document.adoptedStyleSheets.push(BasicStyles);
11
11
 
12
- const __COMMIT_HASH__ = "7f1fc6a";
12
+ const __COMMIT_HASH__ = "4a32f7a";
13
13
  const __PACKAGE_VERSION__ = "5.0.0";
14
14
  const __PACKAGE_ISSUES_URL__ = "https://gitlab.com/panoramax/clients/web-viewer/-/work_items";
15
15
 
@@ -298,7 +298,16 @@ export default class PhotoViewer extends Basic {
298
298
  if(this.psv.getPictureMetadata()) { return Promise.resolve(); }
299
299
  else {
300
300
  return new Promise(resolve => {
301
- this.psv.addEventListener("picture-loaded", resolve, {once: true});
301
+ const onLoadSuccess = () => {
302
+ this.psv.removeEventListener("picture-failed", onLoadFailure);
303
+ resolve();
304
+ };
305
+ const onLoadFailure = () => {
306
+ this.psv.removeEventListener("picture-loaded", onLoadSuccess);
307
+ resolve();
308
+ };
309
+ this.psv.addEventListener("picture-loaded", onLoadSuccess, {once: true});
310
+ this.psv.addEventListener("picture-failed", onLoadFailure, {once: true});
302
311
  });
303
312
  }
304
313
  });
@@ -324,7 +333,16 @@ export default class PhotoViewer extends Basic {
324
333
  this.loader.dismiss();
325
334
  }
326
335
  else {
327
- this.psv.addEventListener("picture-loaded", () => this.loader.dismiss(), {once: true});
336
+ const onLoadSuccess = () => {
337
+ this.loader.dismiss();
338
+ this.psv.removeEventListener("picture-failed", onLoadFailure);
339
+ };
340
+ const onLoadFailure = () => {
341
+ this.loader.dismiss(true, this._t.pnx.error_pic);
342
+ this.psv.removeEventListener("picture-loaded", onLoadSuccess);
343
+ };
344
+ this.psv.addEventListener("picture-loaded", onLoadSuccess, {once: true});
345
+ this.psv.addEventListener("picture-failed", onLoadFailure, {once: true});
328
346
  }
329
347
  }
330
348
  else {
@@ -374,6 +392,9 @@ export default class PhotoViewer extends Basic {
374
392
  this.psv.addEventListener("sequence-playing", () => this.classList.add("pnx-playing"));
375
393
  this.psv.addEventListener("sequence-stopped", () => this.classList.remove("pnx-playing"));
376
394
 
395
+ // Pic loading failures
396
+ this.psv.addEventListener("picture-failed", () => this.select(null, null));
397
+
377
398
  // One-click focusing
378
399
  this.psv.addEventListener("dblclick", () => clearTimeout(this._gridFocus));
379
400
  this.psv.addEventListener("click", e => {
@@ -329,6 +329,10 @@ export default class Viewer extends PhotoViewer {
329
329
  if(name === "picture") {
330
330
  this.legend?.setAttribute?.("picture", value);
331
331
 
332
+ // Look for eventual GeoSearch input
333
+ const geosearch = document.getElementById("pnx-widget-search-bar");
334
+ const isNotGeosearch = (!geosearch || geosearch.value !== value);
335
+
332
336
  // First pic load : show map in mini component
333
337
  if(isNullId(old) && !isNullId(value)) {
334
338
  this.mini.removeAttribute("collapsed");
@@ -345,6 +349,7 @@ export default class Viewer extends PhotoViewer {
345
349
  // eslint-disable-next-line eqeqeq
346
350
  && this.sequence == this._initParams.getParentPostInit().sequence
347
351
  && !this._initParams.getParentPostInit().picture
352
+ && isNotGeosearch
348
353
  ) {
349
354
  this.mini.classList.remove("pnx-hidden");
350
355
  this.mini.removeAttribute("collapsed");
@@ -354,13 +359,22 @@ export default class Viewer extends PhotoViewer {
354
359
  }
355
360
  // Select after none selected -> show pic wide
356
361
  else {
357
- this.mini.classList.remove("pnx-hidden");
358
- if(isNullId(old)) {
359
- this._setFocus("pic");
360
- if(this.bottomDrawer?.getAttribute?.("openness") === "closed") {
361
- this.bottomDrawer.setAttribute("openness", "half-opened");
362
+ const postLoad = () => {
363
+ this.mini.classList.remove("pnx-hidden");
364
+ if(isNullId(old)) {
365
+ this._setFocus("pic");
366
+ if(this.bottomDrawer?.getAttribute?.("openness") === "closed") {
367
+ this.bottomDrawer.setAttribute("openness", "half-opened");
368
+ }
362
369
  }
363
- }
370
+ };
371
+ this.psv.addEventListener("picture-loaded", postLoad, {once: true});
372
+ this.psv.addEventListener("picture-failed", () => {
373
+ if(isNotGeosearch) {
374
+ alert(this._t.pnx.error_pic_load);
375
+ }
376
+ this.psv.removeEventListener("picture-loaded", postLoad);
377
+ }, {once: true});
364
378
  }
365
379
  }
366
380
 
@@ -436,7 +450,15 @@ export default class Viewer extends PhotoViewer {
436
450
  */
437
451
  getAPI() {
438
452
  let apiToUse = this.mapStyleComposer.panoramax || this._initParams.getParentPostInit().users || (this.hasTwoAPIs() ? "metacatalog" : "default");
439
- if(apiToUse === "metacatalog" && this.metacatalog === "false") { apiToUse = "default"; }
453
+
454
+ // Fallbacks & checks
455
+ if(
456
+ apiToUse.startsWith("metacatalog")
457
+ && (!this.hasTwoAPIs() || this.metacatalog === "false")
458
+ ) {
459
+ apiToUse = apiToUse.replace("metacatalog", "default");
460
+ }
461
+
440
462
  return apiToUse?.startsWith("metacatalog") ? this.apiMC : this.api;
441
463
  }
442
464
 
@@ -450,7 +472,7 @@ export default class Viewer extends PhotoViewer {
450
472
  // Load Panoramax endpoints in map style
451
473
  const loadDefault = [null, undefined, "", "default"].includes(myPostInitParams.users);
452
474
  await this._lookForMetacatalog(!loadDefault);
453
- this.mapStyleComposer._createPanoramaxEndpointFromAPI("default", this.api, loadDefault);
475
+ this.mapStyleComposer._createPanoramaxEndpointFromAPI("default", this.api, loadDefault || !this.apiMC);
454
476
 
455
477
  this._initPSV();
456
478
  await this._initMap();
@@ -466,10 +488,19 @@ export default class Viewer extends PhotoViewer {
466
488
  }
467
489
 
468
490
  if(myPostInitParams.willLoadPicture) {
469
- this.psv.addEventListener("picture-loaded", () => {
491
+ const onLoadSuccess = () => {
470
492
  alterViewerState(this, myPostInitParams); // Do it again for forcing focus
471
493
  this.loader.dismiss();
472
- }, {once: true});
494
+ this.psv.removeEventListener("picture-failed", onLoadFailure);
495
+ };
496
+ const onLoadFailure = () => {
497
+ alterViewerState(this, myPostInitParams); // Do it again for forcing focus
498
+ this.select();
499
+ this.psv.removeEventListener("picture-loaded", onLoadSuccess);
500
+ this.loader.dismiss(true, this._t.pnx.error_pic, () => this.loader.dismiss());
501
+ };
502
+ this.psv.addEventListener("picture-loaded", onLoadSuccess, {once: true});
503
+ this.psv.addEventListener("picture-failed", onLoadFailure, {once: true});
473
504
  }
474
505
  else {
475
506
  this.map.waitForEnoughMapLoaded().then(() => {
@@ -479,11 +510,21 @@ export default class Viewer extends PhotoViewer {
479
510
  }
480
511
 
481
512
  /** @private */
482
- async _lookForMetacatalog(switchOn) {
513
+ _lookForMetacatalog(switchOn) {
483
514
  if(this.hasTwoAPIs()) {
484
515
  this.apiMC = new API(this.metacatalog+"/api");
485
- await this.apiMC.onceReady();
486
- this.mapStyleComposer._createPanoramaxEndpointFromAPI("metacatalog", this.apiMC, switchOn);
516
+ return new Promise(resolve => {
517
+ this.apiMC.onceReady().then(() => {
518
+ this.mapStyleComposer._createPanoramaxEndpointFromAPI("metacatalog", this.apiMC, switchOn);
519
+ resolve();
520
+ }).catch(() => {
521
+ console.error("Metacatalog API is not available, fallback to local instance");
522
+ this.apiMC = null;
523
+ this["endpoint-to-use"] = (this["endpoint-to-use"] || "default").replace("metacatalog", "default");
524
+ this.metacatalog = "false";
525
+ resolve();
526
+ });
527
+ });
487
528
  }
488
529
  }
489
530
 
@@ -404,7 +404,7 @@ export default class Map extends maplibregl.Map {
404
404
  .setLngLat([lon, lat])
405
405
  .setRotation(heading)
406
406
  .addTo(this);
407
- this._picMarker.picId = picId ;
407
+ this._picMarker.picId = picId;
408
408
  }
409
409
  else {
410
410
  this._picMarker.remove();
@@ -95,6 +95,7 @@ const keepOnlyPSVOptions = opts => Object.fromEntries(
95
95
  * @fires Panoramax.components.ui.Photo#picture-preview-stopped
96
96
  * @fires Panoramax.components.ui.Photo#view-rotated
97
97
  * @fires Panoramax.components.ui.Photo#picture-loaded
98
+ * @fires Panoramax.components.ui.Photo#picture-failed
98
99
  * @fires Panoramax.components.ui.Photo#picture-tiles-loaded
99
100
  * @fires Panoramax.components.ui.Photo#transition-duration-changed
100
101
  * @fires Panoramax.components.ui.Photo#sequence-playing
@@ -210,9 +211,6 @@ export default class Photo extends PSViewer {
210
211
 
211
212
  if(metadata.features) { metadata = metadata.features.pop(); }
212
213
  if(!metadata || Object.keys(metadata).length === 0 || !picApiResponse.ok) {
213
- if(this._parent.loader) {
214
- this._parent.loader.dismiss(true, this._parent._t.pnx.error_pic);
215
- }
216
214
  throw new Error("Picture with ID " + picId + " was not found");
217
215
  }
218
216
 
@@ -601,6 +599,18 @@ export default class Photo extends PSViewer {
601
599
  this._picturesSequences[e.detail.picId] = e.detail.seqId;
602
600
  }
603
601
 
602
+ const onLoadFail = e => {
603
+ /**
604
+ * Event for picture load failure
605
+ * @event Panoramax.components.ui.Photo#picture-failed
606
+ * @type {CustomEvent}
607
+ * @property {string} detail.picId The failing picture unique identifier
608
+ * @property {Error} detail.error The original error
609
+ */
610
+ const event = new CustomEvent("picture-failed", { detail: { picId: this._parent.picture, error: e } });
611
+ this.dispatchEvent(event);
612
+ };
613
+
604
614
  if(e.detail.seqId && !e.detail.picId) {
605
615
  // Call API to find first picture ID in sequence
606
616
  this.loader.show();
@@ -611,18 +621,16 @@ export default class Photo extends PSViewer {
611
621
  this._picturesSequences[picId] = e.detail.seqId;
612
622
  this._parent.select(e.detail.seqId, picId);
613
623
  }
614
- }).catch(e => this.showErrorOverlay(e, this._parent._t.pnx.error_pic, true));
624
+ }).catch(onLoadFail);
615
625
  }
616
626
  else if(!e.detail.picId) {
617
- this._myVTour.setCurrentNode(null);
618
- this._myVTour.state.currentNode.id = null;
627
+ this._myVTour.state.currentNode = null;
619
628
  this._myVTour.state.loadingNode = null;
629
+ this._myVTour.setCurrentNode(null);
620
630
  }
621
631
  else if(this.getPictureId() !== e.detail.picId) {
622
632
  this.loader.show();
623
- this._myVTour.setCurrentNode(e.detail.picId).catch(e => {
624
- this.showErrorOverlay(e, this._parent._t.pnx.error_pic, true);
625
- });
633
+ this._myVTour.setCurrentNode(e.detail.picId).catch(onLoadFail);
626
634
  }
627
635
  }
628
636
 
@@ -173,8 +173,10 @@ export default class SearchBar extends LitElement {
173
173
 
174
174
  /** @private */
175
175
  _onInputChange(e) {
176
- this.value = e.target.value;
177
- this._throttledSearch();
176
+ if(this.value !== e.target.value) {
177
+ this.value = e.target.value;
178
+ this._throttledSearch();
179
+ }
178
180
  }
179
181
 
180
182
  /** @private */
@@ -89,14 +89,16 @@ export default class GeoSearch extends LitElement {
89
89
  return Promise.resolve(true);
90
90
  }
91
91
  else if(uuidMatch) {
92
- this._parent.select(null, query);
93
- return Promise.resolve(true);
92
+ return new Promise((resolve, reject) => {
93
+ this._parent.psv.addEventListener("picture-failed", reject, {once: true});
94
+ this._parent.psv.addEventListener("picture-loaded", () => resolve(true), {once: true});
95
+ this._parent.select(null, query);
96
+ });
94
97
  }
95
98
  else {
96
99
  return this._geocoderEngine({
97
100
  query,
98
101
  limit: 5,
99
- //Bbox: this._parent.map.getBounds().toArray().map(d => d.join(",")).join(","),
100
102
  proximity: this._parent.map.getCenter().lat+","+this._parent.map.getCenter().lng,
101
103
  }).then(data => {
102
104
  data = data.features.map(f => ({
@@ -102,6 +102,7 @@
102
102
  "error_pic": "Requested picture wasn't found",
103
103
  "error_nopic": "No picture available at given position",
104
104
  "error_api": "The pictures server is not available",
105
+ "error_pic_load": "The picture can't be loaded for now.\nPlease check your Internet connection. If you're connected, make sure the picture ID is valid.",
105
106
  "error_webgl": "WebGL is not supported by your browser",
106
107
  "error_josm": "JOSM doesn't respond, is it running and is remote enabled?",
107
108
  "error_api_compatibility": "The pictures server is not compatible with this viewer version",
@@ -102,6 +102,7 @@
102
102
  "error_pic": "La photo demandée est introuvable",
103
103
  "error_nopic": "Aucune photo disponible autour de la localisation recherchée",
104
104
  "error_api": "Le serveur de photos n'est pas disponible",
105
+ "error_pic_load": "La photo n'a pas pu être chargée.\nVérifiez votre connexion Internet. Si vous êtes bien connecté, vérifiez que l'identifiant de photo est bien valide.",
105
106
  "error_webgl": "WebGL n'est pas supporté par votre navigateur",
106
107
  "error_josm": "JOSM n'a pas répondu, est-il bien lancé et la télécommande activée ?",
107
108
  "error_api_compatibility": "Le serveur de photos n'est pas compatible avec cette visionneuse",
@@ -1,3 +1,4 @@
1
+ /* eslint-disable class-methods-use-this */
1
2
  import {
2
3
  MAP_FILTERS_JS2URL, alterMapState, alterPSVState, alterPhotoViewerState, alterViewerState
3
4
  } from "./InitParameters.js";
@@ -12,7 +13,10 @@ const MANAGED_PARAMETERS = [
12
13
 
13
14
  // Events to listen on parent and PSV
14
15
  const UPDATE_PARENT_EVENTS = ["focus-changed", "pictures-navigation-changed"];
15
- const UPDATE_PSV_EVENTS = ["position-updated", "zoom-updated", "view-rotated", "picture-loaded", "transition-duration-changed", "annotation-focused", "annotations-unfocused"];
16
+ const UPDATE_PSV_EVENTS = [
17
+ "position-updated", "zoom-updated", "view-rotated", "picture-loaded", "picture-failed",
18
+ "transition-duration-changed", "annotation-focused", "annotations-unfocused"
19
+ ];
16
20
  const UPDATE_MAP_EVENTS = ["moveend", "zoomend", "boxzoomend", "styledata"];
17
21
  const UPDATE_MSC_EVENTS = ["filters-changed", "theme-changed", "panoramax-changed", "basemap-changed"];
18
22
 
@@ -471,12 +471,12 @@ export function mapFiltersToLayersFilters(filters, hasGridStats = false, feature
471
471
  }
472
472
 
473
473
 
474
- if(mapSeqFilters.length == 0) { mapSeqFilters = null; }
474
+ if(mapSeqFilters.length === 0) { mapSeqFilters = null; }
475
475
  else {
476
476
  mapSeqFilters.unshift("all");
477
477
  }
478
478
 
479
- if(mapPicFilters.length == 0) { mapPicFilters = null; }
479
+ if(mapPicFilters.length === 0) { mapPicFilters = null; }
480
480
  else {
481
481
  mapPicFilters.unshift("all");
482
482
  mapPicFilters = ["step", ["zoom"],
@@ -553,6 +553,7 @@ export function linkMapAndPhoto(parent) {
553
553
  };
554
554
  parent.psv.addEventListener("picture-loading", onPicLoad);
555
555
  parent.psv.addEventListener("picture-loaded", onPicLoad);
556
+ parent.psv.addEventListener("picture-failed", onPicLoad);
556
557
 
557
558
  // Picture view rotated
558
559
  parent.psv.addEventListener("view-rotated", () => {
@@ -575,7 +576,7 @@ export function linkMapAndPhoto(parent) {
575
576
  });
576
577
 
577
578
  parent.psv.addEventListener("picture-loaded", e => {
578
- if (parent.isWidthSmall() && parent._picPopup && e.detail.picId == parent._picPopup._picId) {
579
+ if (parent.isWidthSmall() && parent._picPopup && e.detail.picId === parent._picPopup._picId) {
579
580
  parent._picPopup.remove();
580
581
  }
581
582
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@panoramax/web-viewer",
3
- "version": "5.0.0-develop-7f1fc6a0",
3
+ "version": "5.0.0-develop-4a32f7a0",
4
4
  "description": "Panoramax web viewer for geolocated pictures",
5
5
  "main": "./build/cjs/index.js",
6
6
  "module": "./build/esm/index.js",