@panoramax/web-viewer 4.0.3 → 4.1.0-develop-55fdf56c

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 (106) hide show
  1. package/CHANGELOG.md +46 -1
  2. package/build/index.css +9 -9
  3. package/build/index.css.map +1 -1
  4. package/build/index.js +640 -456
  5. package/build/index.js.map +1 -1
  6. package/build/map.html +1 -1
  7. package/build/photo.html +1 -1
  8. package/build/viewer.html +3 -3
  9. package/build/widgets.html +1 -1
  10. package/config/jest/mocks.js +9 -1
  11. package/docs/03_URL_settings.md +21 -0
  12. package/docs/09_Develop.md +6 -0
  13. package/docs/images/comparative_3drender.jpg +0 -0
  14. package/docs/index.md +13 -0
  15. package/docs/reference/components/core/Editor.md +18 -0
  16. package/docs/reference/components/core/PhotoViewer.md +1 -0
  17. package/docs/reference/components/core/Viewer.md +1 -0
  18. package/docs/reference/components/menus/MapLegend.md +17 -0
  19. package/docs/reference/components/menus/MiniPictureLegend.md +15 -0
  20. package/docs/reference/components/menus/PictureLegend.md +17 -0
  21. package/docs/reference/components/ui/AnnotationsSwitch.md +15 -0
  22. package/docs/reference/components/ui/Button.md +1 -1
  23. package/docs/reference/components/ui/CopyButton.md +1 -1
  24. package/docs/reference/components/ui/LinkButton.md +1 -1
  25. package/docs/reference/components/ui/Map.md +18 -2
  26. package/docs/reference/components/ui/MapMore.md +6 -2
  27. package/docs/reference/components/ui/SemanticsEditor.md +87 -0
  28. package/docs/reference/components/ui/widgets/Legend.md +5 -4
  29. package/docs/reference/utils/URLHandler.md +7 -0
  30. package/docs/reference.md +3 -1
  31. package/docs/tutorials/aerial_imagery.md +13 -11
  32. package/mkdocs.yml +3 -1
  33. package/package.json +7 -7
  34. package/public/map.html +3 -3
  35. package/public/photo.html +1 -1
  36. package/public/viewer.html +3 -3
  37. package/public/widgets.html +32 -0
  38. package/src/components/core/Basic.css +2 -0
  39. package/src/components/core/Basic.js +3 -1
  40. package/src/components/core/CoverageMap.css +1 -0
  41. package/src/components/core/CoverageMap.js +6 -0
  42. package/src/components/core/Editor.css +2 -0
  43. package/src/components/core/Editor.js +56 -7
  44. package/src/components/core/PhotoViewer.css +10 -10
  45. package/src/components/core/PhotoViewer.js +56 -23
  46. package/src/components/core/Viewer.css +16 -4
  47. package/src/components/core/Viewer.js +62 -33
  48. package/src/components/layout/BottomDrawer.js +2 -1
  49. package/src/components/layout/Tabs.js +4 -0
  50. package/src/components/menus/AnnotationsList.js +13 -9
  51. package/src/components/menus/MapBackground.js +8 -3
  52. package/src/components/menus/MapFilters.js +11 -2
  53. package/src/components/menus/MapLayers.js +3 -2
  54. package/src/components/menus/MapLegend.js +35 -4
  55. package/src/components/menus/MiniPictureLegend.js +74 -0
  56. package/src/components/menus/PictureLegend.js +89 -33
  57. package/src/components/menus/PictureMetadata.js +49 -17
  58. package/src/components/menus/PlayerOptions.js +3 -3
  59. package/src/components/menus/Share.js +3 -3
  60. package/src/components/menus/index.js +5 -4
  61. package/src/components/styles.js +11 -0
  62. package/src/components/ui/AnnotationsSwitch.js +171 -0
  63. package/src/components/ui/Button.js +1 -1
  64. package/src/components/ui/CopyButton.js +1 -1
  65. package/src/components/ui/LinkButton.js +1 -1
  66. package/src/components/ui/Map.css +4 -0
  67. package/src/components/ui/Map.js +17 -5
  68. package/src/components/ui/MapMore.js +61 -25
  69. package/src/components/ui/Photo.css +11 -2
  70. package/src/components/ui/Photo.js +6 -3
  71. package/src/components/ui/SemanticsEditor.js +157 -0
  72. package/src/components/ui/index.js +2 -1
  73. package/src/components/ui/widgets/GeoSearch.js +3 -2
  74. package/src/components/ui/widgets/Legend.js +76 -15
  75. package/src/components/ui/widgets/MapFiltersButton.js +3 -3
  76. package/src/components/ui/widgets/MapLayersButton.js +3 -3
  77. package/src/components/ui/widgets/OSMEditors.js +2 -2
  78. package/src/components/ui/widgets/PictureLegendActions.js +24 -42
  79. package/src/components/ui/widgets/Player.js +3 -3
  80. package/src/components/ui/widgets/Zoom.js +4 -2
  81. package/src/translations/ar.json +1 -0
  82. package/src/translations/da.json +3 -2
  83. package/src/translations/de.json +64 -13
  84. package/src/translations/en.json +5 -1
  85. package/src/translations/eo.json +32 -2
  86. package/src/translations/fr.json +7 -1
  87. package/src/translations/it.json +33 -2
  88. package/src/translations/nl.json +53 -11
  89. package/src/translations/zh_Hant.json +29 -2
  90. package/src/utils/API.js +17 -1
  91. package/src/utils/InitParameters.js +46 -4
  92. package/src/utils/URLHandler.js +9 -1
  93. package/src/utils/map.js +24 -1
  94. package/src/utils/semantics.js +53 -1
  95. package/src/utils/services.js +16 -0
  96. package/src/utils/widgets.js +38 -0
  97. package/tests/components/core/Editor.test.js +1 -1
  98. package/tests/components/core/__snapshots__/PhotoViewer.test.js.snap +18 -6
  99. package/tests/components/core/__snapshots__/Viewer.test.js.snap +15 -3
  100. package/tests/components/ui/Photo.test.js +1 -0
  101. package/tests/components/ui/__snapshots__/Map.test.js.snap +164 -0
  102. package/tests/utils/InitParameters.test.js +27 -0
  103. package/tests/utils/map.test.js +12 -0
  104. package/tests/utils/semantics.test.js +34 -5
  105. package/docs/reference/components/ui/HashTags.md +0 -15
  106. package/src/components/ui/HashTags.js +0 -98
@@ -1,7 +1,7 @@
1
1
  import "./Map.css";
2
2
  import {
3
3
  VECTOR_STYLES, TILES_PICTURES_ZOOM, getThumbGif, RASTER_LAYER_ID, combineStyles,
4
- getMissingLayerStyles, isLabelLayer, getUserLayerId, getUserSourceId,
4
+ getMissingLayerStyles, isLabelLayer, getUserLayerId, getUserSourceId, isNullCoordinates,
5
5
  } from "../../utils/map";
6
6
  import { COLORS } from "../../utils/utils";
7
7
  import MarkerBaseSVG from "../../img/marker.svg";
@@ -37,6 +37,7 @@ maplibregl.addProtocol("pmtiles", new pmtiles.Protocol().tile);
37
37
  * @param {object} [options.raster] The MapLibre raster source for aerial background. This must be a JSON object following [MapLibre raster source definition](https://maplibre.org/maplibre-style-spec/sources/#raster).
38
38
  * @param {string} [options.background=streets] Choose default map background to display (streets or aerial, if raster aerial background available). Defaults to street.
39
39
  * @param {string} [options.attributionControl.customAttribution] To override default map attribution.
40
+ * @param {boolean} [options.picMarkerDraggable] To make the picture marker draggable, default to false.
40
41
  * @fires Panoramax.components.ui.Map#background-changed
41
42
  * @fires Panoramax.components.ui.Map#users-changed
42
43
  * @fires Panoramax.components.ui.Map#sequence-hover
@@ -205,7 +206,7 @@ export default class Map extends maplibregl.Map {
205
206
  */
206
207
  _initMapPosition() {
207
208
  if(
208
- (!this._options.center || this._options.center == [0,0])
209
+ isNullCoordinates(this._options.center)
209
210
  && (!this._options.zoom || this._options.zoom === 0)
210
211
  && (!this._options.hash)
211
212
  ) {
@@ -379,6 +380,9 @@ export default class Map extends maplibregl.Map {
379
380
  });
380
381
  });
381
382
 
383
+ // Force style reload
384
+ this.reloadLayersStyles();
385
+
382
386
  /**
383
387
  * Event for visible users changes
384
388
  *
@@ -413,13 +417,16 @@ export default class Map extends maplibregl.Map {
413
417
 
414
418
  /**
415
419
  * Shows on map a picture position and heading.
420
+ *
421
+ * If no longitude & latitude are set, marker is removed from map.
416
422
  * @memberof Panoramax.components.ui.Map#
417
423
  * @param {number} lon The longitude
418
424
  * @param {number} lat The latitude
419
425
  * @param {number} heading The heading
420
426
  * @param {boolean} [skipCenter=false] Set to true to avoid map centering on marker
427
+ * @param {string} [picId=null] The picture Id
421
428
  */
422
- displayPictureMarker(lon, lat, heading, skipCenter = false) {
429
+ displayPictureMarker(lon, lat, heading, skipCenter = false, picId = null) {
423
430
  this._picMarkerPreview.remove();
424
431
 
425
432
  // Show marker corresponding to selection
@@ -428,6 +435,7 @@ export default class Map extends maplibregl.Map {
428
435
  .setLngLat([lon, lat])
429
436
  .setRotation(heading)
430
437
  .addTo(this);
438
+ this._picMarker.picId = picId ;
431
439
  }
432
440
  else {
433
441
  this._picMarker.remove();
@@ -877,8 +885,12 @@ export default class Map extends maplibregl.Map {
877
885
  img.src = selected ? MarkerSelectedSVG : MarkerBaseSVG;
878
886
  img.alt = "";
879
887
  return new maplibregl.Marker({
880
- element: img
881
- });
888
+ element: img,
889
+ picId: null
890
+ })
891
+ // only picMarker could be draggable, don't for picMarkerPreview.
892
+ .setDraggable(selected && this._options.picMarkerDraggable)
893
+ ;
882
894
  }
883
895
 
884
896
  /**
@@ -198,32 +198,21 @@ export default class MapMore extends Map {
198
198
  return s;
199
199
  }
200
200
 
201
- /**
202
- * Change the map filters
203
- * @param {object} filters Filtering values
204
- * @param {string} [filters.minDate] Start date for pictures (format YYYY-MM-DD)
205
- * @param {string} [filters.maxDate] End date for pictures (format YYYY-MM-DD)
206
- * @param {string} [filters.pic_type] Type of picture to keep (flat, equirectangular)
207
- * @param {string} [filters.camera] Camera make and model to keep
208
- * @param {string} [filters.theme] Map theme to use
209
- * @param {number[]} [filters.qualityscore] QualityScore values, as a list of 1 to 5 grades
210
- * @param {boolean} [skipZoomIn=false] If true, doesn't force zoom in to map level >= 7
211
- * @memberof Panoramax.components.core.MapMore#
212
- */
213
- setFilters(filters, skipZoomIn = false) {
201
+ /** @private */
202
+ _mapFiltersToLayersFilters(filters) {
203
+ let mapFilters = {};
214
204
  let mapSeqFilters = [];
215
205
  let mapPicFilters = [];
216
206
  let reloadMapStyle = false;
217
- this._mapFilters = {};
218
207
 
219
208
  if(filters.minDate && filters.minDate !== "") {
220
- this._mapFilters.minDate = filters.minDate;
209
+ mapFilters.minDate = filters.minDate;
221
210
  mapSeqFilters.push([">=", ["get", "date"], filters.minDate]);
222
211
  mapPicFilters.push([">=", ["get", "ts"], filters.minDate]);
223
212
  }
224
213
 
225
214
  if(filters.maxDate && filters.maxDate !== "") {
226
- this._mapFilters.maxDate = filters.maxDate;
215
+ mapFilters.maxDate = filters.maxDate;
227
216
  mapSeqFilters.push(["<=", ["get", "date"], filters.maxDate]);
228
217
 
229
218
  // Get tomorrow date for pictures filtering
@@ -235,16 +224,16 @@ export default class MapMore extends Map {
235
224
  }
236
225
 
237
226
  if(filters.pic_type && filters.pic_type !== "") {
238
- this._mapFilters.pic_type = filters.pic_type === "flat" ? "flat" : "equirectangular";
239
- mapSeqFilters.push(["==", ["get", "type"], this._mapFilters.pic_type]);
240
- mapPicFilters.push(["==", ["get", "type"], this._mapFilters.pic_type]);
227
+ mapFilters.pic_type = filters.pic_type === "flat" ? "flat" : "equirectangular";
228
+ mapSeqFilters.push(["==", ["get", "type"], mapFilters.pic_type]);
229
+ mapPicFilters.push(["==", ["get", "type"], mapFilters.pic_type]);
241
230
  }
242
231
  if(this._hasGridStats()) {
243
232
  reloadMapStyle = true;
244
233
  }
245
234
 
246
235
  if(filters.camera && filters.camera !== "") {
247
- this._mapFilters.camera = filters.camera;
236
+ mapFilters.camera = filters.camera;
248
237
  // low/high model hack : to enable fuzzy filtering of camera make and model
249
238
  const lowModel = filters.camera.toLowerCase().trim() + " ";
250
239
  const highModel = filters.camera.toLowerCase().trim() + "zzzzzzzzzzzzzzzzzzzz";
@@ -256,13 +245,13 @@ export default class MapMore extends Map {
256
245
  }
257
246
 
258
247
  if(filters.qualityscore && filters.qualityscore.length > 0) {
259
- this._mapFilters.qualityscore = filters.qualityscore;
260
- mapSeqFilters.push(["in", MAP_EXPR_QUALITYSCORE, ["literal", this._mapFilters.qualityscore]]);
261
- mapPicFilters.push(["in", MAP_EXPR_QUALITYSCORE, ["literal", this._mapFilters.qualityscore]]);
248
+ mapFilters.qualityscore = filters.qualityscore;
249
+ mapSeqFilters.push(["in", MAP_EXPR_QUALITYSCORE, ["literal", mapFilters.qualityscore]]);
250
+ mapPicFilters.push(["in", MAP_EXPR_QUALITYSCORE, ["literal", mapFilters.qualityscore]]);
262
251
  }
263
252
 
264
253
  if(filters.theme && Object.values(MAP_THEMES).includes(filters.theme)) {
265
- this._mapFilters.theme = filters.theme;
254
+ mapFilters.theme = filters.theme;
266
255
  reloadMapStyle = true;
267
256
  }
268
257
 
@@ -280,6 +269,25 @@ export default class MapMore extends Map {
280
269
  ];
281
270
  }
282
271
 
272
+ return { mapFilters, mapSeqFilters, mapPicFilters, reloadMapStyle };
273
+ }
274
+
275
+ /**
276
+ * Change the map filters
277
+ * @param {object} filters Filtering values
278
+ * @param {string} [filters.minDate] Start date for pictures (format YYYY-MM-DD)
279
+ * @param {string} [filters.maxDate] End date for pictures (format YYYY-MM-DD)
280
+ * @param {string} [filters.pic_type] Type of picture to keep (flat, equirectangular)
281
+ * @param {string} [filters.camera] Camera make and model to keep
282
+ * @param {string} [filters.theme] Map theme to use
283
+ * @param {number[]} [filters.qualityscore] QualityScore values, as a list of 1 to 5 grades
284
+ * @param {boolean} [skipZoomIn=false] If true, doesn't force zoom in to map level >= 7
285
+ * @memberof Panoramax.components.core.MapMore#
286
+ */
287
+ setFilters(filters, skipZoomIn = false) {
288
+ let { mapFilters, mapSeqFilters, mapPicFilters, reloadMapStyle } = this._mapFiltersToLayersFilters(filters);
289
+
290
+ this._mapFilters = mapFilters;
283
291
  if(reloadMapStyle) {
284
292
  this.reloadLayersStyles();
285
293
  }
@@ -291,9 +299,10 @@ export default class MapMore extends Map {
291
299
  7, mapSeqFilters
292
300
  ];
293
301
  }
294
-
302
+
295
303
  this.filterUserLayersContent("sequences", mapSeqFilters);
296
304
  this.filterUserLayersContent("pictures", mapPicFilters);
305
+
297
306
  if(
298
307
  !skipZoomIn
299
308
  && (
@@ -321,4 +330,31 @@ export default class MapMore extends Map {
321
330
  */
322
331
  this.fire("filters-changed", Object.assign({}, this._mapFilters));
323
332
  }
333
+
334
+ /**
335
+ * Make given user layers visible on map, and hide all others (if any)
336
+ * @memberof Panoramax.components.ui.Map#
337
+ * @param {string|string[]} visibleIds The user layers IDs to display
338
+ */
339
+ async setVisibleUsers(visibleIds = []) {
340
+ await super.setVisibleUsers(visibleIds);
341
+
342
+ // Force reload of styles & filters
343
+ let { mapSeqFilters, mapPicFilters, reloadMapStyle } = this._mapFiltersToLayersFilters(this._mapFilters);
344
+
345
+ if(reloadMapStyle) {
346
+ this.reloadLayersStyles();
347
+ }
348
+
349
+ const allUsers = visibleIds.includes("geovisio");
350
+ if(mapSeqFilters && allUsers) {
351
+ mapSeqFilters = ["step", ["zoom"],
352
+ true,
353
+ 7, mapSeqFilters
354
+ ];
355
+ }
356
+
357
+ this.filterUserLayersContent("sequences", mapSeqFilters);
358
+ this.filterUserLayersContent("pictures", mapPicFilters);
359
+ }
324
360
  }
@@ -35,7 +35,16 @@
35
35
  height: auto;
36
36
  }
37
37
 
38
- /* No virtual tour arrows if photo is reduced */
39
- pnx-mini .psv-virtual-tour-arrows {
38
+ .pnx-psv-tour-arrows svg:hover {
39
+ filter: drop-shadow(0 0 15px var(--blue));
40
+ }
41
+
42
+ .pnx-psv-playing .pnx-psv-tour-arrows {
43
+ display: none;
44
+ }
45
+
46
+ /* No virtual tour arrows or annotations if photo is reduced */
47
+ pnx-mini .psv-virtual-tour-arrows,
48
+ pnx-mini .pnx-psv-annotation {
40
49
  display: none;
41
50
  }
@@ -94,7 +94,7 @@ export default class Photo extends PSViewer {
94
94
  resolution: parent.isWidthSmall() ? 32 : 64,
95
95
  shouldGoFast: options.shouldGoFast,
96
96
  }],
97
- withCredentials: parent?.fetchOptions?.credentials == "include",
97
+ withCredentials: parent.api._getPSVWithCredentials(),
98
98
  requestHeaders: parent?.fetchOptions?.headers,
99
99
  panorama: BASE_PANORAMA,
100
100
  lang: parent._t.psv,
@@ -803,6 +803,7 @@ export default class Photo extends PSViewer {
803
803
  */
804
804
  playSequence() {
805
805
  this._sequencePlaying = true;
806
+ this.container.classList.add("pnx-psv-playing");
806
807
 
807
808
  /**
808
809
  * Event for sequence starting to play
@@ -841,6 +842,7 @@ export default class Photo extends PSViewer {
841
842
  */
842
843
  stopSequence() {
843
844
  this._sequencePlaying = false;
845
+ this.container.classList.remove("pnx-psv-playing");
844
846
 
845
847
  // Next picture timer is pending
846
848
  if(this._playTimer) {
@@ -952,8 +954,8 @@ export default class Photo extends PSViewer {
952
954
 
953
955
  if(!visible) { this._myMarkers.clearMarkers(); }
954
956
  else {
955
- let annotations = meta.properties.annotations;
956
- if(annotations.length === 0) { console.warn("No annotation available on picture", meta.id); }
957
+ let annotations = meta.properties.annotations || [];
958
+ if(annotations?.length === 0) { console.warn("No annotation available on picture", meta.id); }
957
959
 
958
960
  const picBData = this.state.textureData.panoData?.baseData;
959
961
 
@@ -990,6 +992,7 @@ export default class Photo extends PSViewer {
990
992
  id: `annotation-${a.id}`,
991
993
  polygon: shape,
992
994
  data: { id: a.id },
995
+ className: "pnx-psv-annotation",
993
996
  svgStyle: {
994
997
  stroke: "var(--orange)",
995
998
  strokeWidth: "3px",
@@ -0,0 +1,157 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import Basic from "../core/Basic";
3
+ import { parseSemanticsString, computeDiffTags } from "../../utils/semantics";
4
+ import { textarea } from "../styles";
5
+ import JSON5 from "json5";
6
+
7
+ /**
8
+ * Semantics Editor offer an easy-to-use input for adding or editing semantics tags.
9
+ *
10
+ * It manipulates list of `{key: "mypanokey", value: "myvalue"}` entries through `semantics` attribute.
11
+ *
12
+ * @class Panoramax.components.ui.SemanticsEditor
13
+ * @element pnx-semantics-editor
14
+ * @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
15
+ * @fires Panoramax.components.ui.SemanticsEditor#change
16
+ * @example
17
+ * ```html
18
+ * <!-- Basic example -->
19
+ * <pnx-semantics-editor
20
+ * id="editor"
21
+ * semantics=${mypic.semantics}
22
+ * ._t=${viewer._t}
23
+ * onchange=${e => console.log(e.detail.semantics)}
24
+ * />
25
+ *
26
+ * <!-- You can access editor and check its validity through native web browser functions -->
27
+ * <script>
28
+ * const editor = document.getElementById("editor");
29
+ * console.log(editor.checkValidity()); // True if input is valid
30
+ * </script>
31
+ *
32
+ * <!-- You can change specifically style of textarea -->
33
+ * <style>
34
+ * pnx-semantics-editor::part(text) {
35
+ * color: blue;
36
+ * }
37
+ * </style>
38
+ * ```
39
+ */
40
+ export default class SemanticsEditor extends LitElement {
41
+ static styles = [textarea, css`
42
+ textarea:invalid {
43
+ border: 2px solid var(--red);
44
+ background-color:var(--beige);
45
+ }
46
+ `];
47
+
48
+ /**
49
+ * Component properties.
50
+ * @memberof Panoramax.components.ui.SemanticsEditor#
51
+ * @type {Object}
52
+ * @property {object} [semantics] The `semantics` field of a picture or annotation feature. It is updated when field changes, but reflect the whole list of new tags (not delta needed by API). If you want delta, please use getDiff function.
53
+ * @property {number} [rows=3] The amount of rows shown for textarea
54
+ */
55
+ static properties = {
56
+ semantics: {converter: Basic.GetJSONConverter(), reflect: true},
57
+ rows: {type: Number},
58
+ _valid: {state: true},
59
+ _t: {converter: Basic.GetJSONConverter()},
60
+ _firstSemantics: {state: true},
61
+ };
62
+
63
+ constructor() {
64
+ super();
65
+ this.semantics = null;
66
+ this._valid = true;
67
+ this._firstSemantics = null;
68
+ this.rows = 3;
69
+ }
70
+
71
+ /** @private */
72
+ connectedCallback() {
73
+ super.connectedCallback();
74
+ this._firstSemantics = this.semantics === null ? [] : JSON5.parse(JSON5.stringify(this.semantics));
75
+ }
76
+
77
+ /**
78
+ * Get current delta between initial tags and user changes.
79
+ * @memberof Panoramax.components.ui.SemanticsEditor#
80
+ * @returns {object[]} The list of tag changes (in API format)
81
+ */
82
+ getDiff() {
83
+ return computeDiffTags(this._firstSemantics || [], this.semantics);
84
+ }
85
+
86
+ /**
87
+ * Check if input is having a valid value.
88
+ * @memberof Panoramax.components.ui.SemanticsEditor#
89
+ * @returns {boolean} True if it's valid
90
+ */
91
+ checkValidity() {
92
+ return this._valid;
93
+ }
94
+
95
+ /** @private */
96
+ _onInput(e) {
97
+ const tarea = e.target;
98
+ try {
99
+ this.semantics = parseSemanticsString(tarea.value) || [];
100
+ this._valid = true;
101
+ } catch (err) {
102
+ if(err.message !== "Invalid tags") { console.error(err); }
103
+ this._valid = false;
104
+ }
105
+ }
106
+
107
+ /** @private */
108
+ _onBlur(e) {
109
+ const prevValidity = e.target.checkValidity();
110
+
111
+ if(this._valid) {
112
+ e.target.setCustomValidity("");
113
+
114
+ /**
115
+ * Event for value change.
116
+ *
117
+ * Note that this event is launched only on valid input.
118
+ *
119
+ * @event Panoramax.components.ui.SemanticsEditor#change
120
+ * @type {CustomEvent}
121
+ * @property {object[]} detail.semantics The new tags list (in API semantics property format)
122
+ * @property {object[]} detail.delta The delta between old and current tags (expected by API)
123
+ */
124
+ this.dispatchEvent(new CustomEvent("change", {
125
+ detail: {
126
+ semantics: this.semantics || [],
127
+ delta: computeDiffTags(this._firstSemantics || [], this.semantics),
128
+ }
129
+ }));
130
+ }
131
+ else if(prevValidity) { // Do not call again if already shows up, to fix Chrome issue
132
+ e.target.setCustomValidity(this._t?.pnx.semantics_editor_error || "Invalid syntax");
133
+ e.target.reportValidity();
134
+ }
135
+ }
136
+
137
+ /** @private */
138
+ render() {
139
+ /* eslint-disable indent */
140
+ return html`
141
+ <textarea
142
+ part="text"
143
+ autocomplete="off"
144
+ autocorrect="off"
145
+ autocapitalize="off"
146
+ spellcheck="false"
147
+ placeholder="key1=value1\nprefix|key2=value2"
148
+ @input=${this._onInput}
149
+ @blur=${this._onBlur}
150
+ rows=${this.rows}
151
+ .value=${(this.semantics || []).map(s => `${s.key}=${s.value}`).join("\n")}
152
+ ></textarea>
153
+ `;
154
+ }
155
+ }
156
+
157
+ customElements.define("pnx-semantics-editor", SemanticsEditor);
@@ -7,7 +7,7 @@ export {default as ButtonGroup} from "./ButtonGroup";
7
7
  export {default as Button} from "./Button";
8
8
  export {default as CopyButton} from "./CopyButton";
9
9
  export {default as Grade} from "./Grade";
10
- export {default as HashTags} from "./HashTags";
10
+ export {default as AnnotationsSwitch} from "./AnnotationsSwitch";
11
11
  export {default as LinkButton} from "./LinkButton";
12
12
  export {default as ListGroup} from "./ListGroup";
13
13
  export {default as ListItem} from "./ListItem";
@@ -19,6 +19,7 @@ export {default as Popup} from "./Popup";
19
19
  export {default as ProgressBar} from "./ProgressBar";
20
20
  export {default as QualityScore} from "./QualityScore";
21
21
  export {default as SearchBar} from "./SearchBar";
22
+ export {default as SemanticsEditor} from "./SemanticsEditor";
22
23
  export {default as SemanticsTable} from "./SemanticsTable";
23
24
  export {default as TogglableGroup} from "./TogglableGroup";
24
25
  import * as widgets from "./widgets";
@@ -3,6 +3,7 @@ import maplibregl from "!maplibre-gl";
3
3
 
4
4
  import { LitElement, html } from "lit";
5
5
  import { forwardGeocodingBAN, forwardGeocodingStandard, forwardGeocodingNominatim } from "../../../utils/geocoder";
6
+ import { onceParentAvailable } from "../../../utils/widgets";
6
7
  import "./GeoSearch.css";
7
8
 
8
9
  const GEOCODER_ENGINES = {
@@ -63,10 +64,10 @@ export default class GeoSearch extends LitElement {
63
64
  super.connectedCallback();
64
65
 
65
66
  this._geocoderEngine = GEOCODER_ENGINES[this.geocoder] || (config => GEOCODER_ENGINES.standard(config, this.geocoder));
66
- this._parent?.onceMapReady?.().then(() => {
67
+ onceParentAvailable(this).then(() => this._parent?.onceMapReady?.().then(() => {
67
68
  this._geolocate = this._geolocateCtrl.onAdd(this._parent.map);
68
69
  this._geolocate.setAttribute("slot", "pre");
69
- });
70
+ }));
70
71
  }
71
72
 
72
73
  /** @private */
@@ -1,9 +1,10 @@
1
- import {LitElement, html, css} from "lit";
1
+ import { LitElement, html, css, nothing } from "lit";
2
2
  import { panel } from "../../styles";
3
- import { fa } from "../../../utils/widgets";
3
+ import { fa, onceParentAvailable } from "../../../utils/widgets";
4
4
  import { faInfoCircle } from "@fortawesome/free-solid-svg-icons/faInfoCircle";
5
5
  import PanoramaxImg from "../../../img/panoramax.svg";
6
6
  import { classMap } from "lit/directives/class-map.js";
7
+ import { PanoramaxWebsiteURL } from "../../../utils/services";
7
8
 
8
9
  /**
9
10
  * Legend widget, handling switch between map and photo components.
@@ -37,7 +38,7 @@ export default class Legend extends LitElement {
37
38
  .pnx-panel[part="panel"] {
38
39
  border-radius: 10px;
39
40
  position: relative;
40
- max-width: 80vw;
41
+ max-width: 450px;
41
42
  z-index: 121; /* To appear above mini component */
42
43
  min-width: unset;
43
44
  }
@@ -46,7 +47,8 @@ export default class Legend extends LitElement {
46
47
  width: 250px;
47
48
  }
48
49
  .presentation {
49
- font-size: 0.9em;
50
+ font-size: 0.85em;
51
+ line-height: 1em;
50
52
  display: flex;
51
53
  gap: 5px;
52
54
  align-items: center;
@@ -54,6 +56,40 @@ export default class Legend extends LitElement {
54
56
  .logo {
55
57
  width: 45px;
56
58
  }
59
+ pnx-map-legend {
60
+ display: block;
61
+ margin-top: 5px;
62
+ }
63
+
64
+ /* Lighter/smaller version */
65
+ .pnx-panel[part="panel"].pnx-legend-light {
66
+ display: flex;
67
+ gap: 3px;
68
+ padding: 3px 8px 3px 3px;
69
+ width: unset;
70
+ min-width: unset;
71
+ align-items: center;
72
+ font-size: 10px;
73
+ margin-right: -10px;
74
+ margin-bottom: -10px;
75
+ border-radius: 0;
76
+ border-top-left-radius: 5px;
77
+ border-right: none;
78
+ border-bottom: none;
79
+ }
80
+ .pnx-legend-light a,
81
+ .pnx-legend-light .presentation {
82
+ font-size: 10px;
83
+ }
84
+ .pnx-legend-light .logo {
85
+ width: 15px;
86
+ }
87
+
88
+ .pnx-legend-light pnx-map-legend {
89
+ width: max-content;
90
+ margin-top: unset;
91
+ font-size: 10px;
92
+ }
57
93
  `];
58
94
 
59
95
  /**
@@ -62,41 +98,65 @@ export default class Legend extends LitElement {
62
98
  * @type {Object}
63
99
  * @property {string} [focus] The focused main component (map, pic)
64
100
  * @property {string} [picture] The picture ID
101
+ * @property {boolean} [light=false] Lighter version (for iframes)
65
102
  */
66
103
  static properties = {
67
104
  focus: {type: String},
68
105
  picture: {type: String},
106
+ light: {type: Boolean},
69
107
  };
70
108
 
109
+ constructor() {
110
+ super();
111
+ this.light = false;
112
+ }
113
+
114
+ /** @private */
115
+ connectedCallback() {
116
+ super.connectedCallback();
117
+ onceParentAvailable(this).then(() => this.requestUpdate());
118
+ }
119
+
71
120
  render() {
72
121
  const classes = {
73
122
  "pnx-panel": true,
74
123
  "pnx-padded": this.focus == "map",
124
+ "pnx-legend-light": this.light,
75
125
  };
76
126
 
77
127
  return html`<div class=${classMap(classes)} part="panel">
78
128
  <div
79
129
  class="presentation"
80
- style=${this.focus != "map" && this.picture ? "display: none": ""}
130
+ style=${!this.light && this.focus != "map" && this.picture ? "display: none": ""}
81
131
  >
82
132
  <img class="logo" src=${PanoramaxImg} alt="" />
83
- <div>
84
- ${this._parent?._t.pnx.whats_panoramax}
85
- <pnx-link-button
133
+
134
+ ${this.light ? html`
135
+ &copy; <a
136
+ href=${PanoramaxWebsiteURL()}
86
137
  title=${this._parent?._t.map.more_panoramax}
87
- kind="superinline"
88
- href="https://panoramax.fr"
89
138
  target="_blank"
90
- size="sm"
91
- >
92
- ${fa(faInfoCircle)}
93
- </pnx-link-button>
94
- </div>
139
+ >${this._parent?._t.pnx.panoramax}</a> |
140
+ ` : html`
141
+ <div>
142
+ ${this._parent?._t.pnx.whats_panoramax}
143
+ <pnx-link-button
144
+ title=${this._parent?._t.map.more_panoramax}
145
+ kind="superinline"
146
+ href=${PanoramaxWebsiteURL()}
147
+ target="_blank"
148
+ size="sm"
149
+ >
150
+ ${fa(faInfoCircle)}
151
+ </pnx-link-button>
152
+ </div>
153
+ `}
95
154
  </div>
96
155
  <pnx-picture-legend
97
156
  ._parent=${this._parent}
98
157
  style=${this.focus == "map" ? "display: none": ""}
99
158
  collapsable
159
+ light=${this.light || nothing}
100
160
  >
101
161
  <slot slot="editors" name="editors">
102
162
  <pnx-widget-osmeditors ._parent=${this._parent} />
@@ -105,6 +165,7 @@ export default class Legend extends LitElement {
105
165
  <pnx-map-legend
106
166
  ._parent=${this._parent}
107
167
  style=${this.focus != "map" ? "display: none": ""}
168
+ light=${this.light || nothing}
108
169
  ></pnx-map-legend>
109
170
  </div>`;
110
171
  }
@@ -1,5 +1,5 @@
1
1
  import { LitElement, html, nothing } from "lit";
2
- import { fa } from "../../../utils/widgets";
2
+ import { fa, onceParentAvailable } from "../../../utils/widgets";
3
3
  import { faSliders } from "@fortawesome/free-solid-svg-icons/faSliders";
4
4
  import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown";
5
5
  import { faXmark } from "@fortawesome/free-solid-svg-icons/faXmark";
@@ -45,7 +45,7 @@ export default class MapFiltersButton extends LitElement {
45
45
  super.connectedCallback();
46
46
 
47
47
  // Listen to user visibility changes to switch the filter active icon
48
- this._parent?.onceMapReady?.().then(() => {
48
+ onceParentAvailable(this).then(() => this._parent?.onceMapReady?.().then(() => {
49
49
  this._active = (
50
50
  Object.keys(this._parent.map._mapFilters).filter(d => d && d !== "theme").length > 0
51
51
  || this._parent.map.getVisibleUsers().filter(u => u !== "geovisio").length > 0
@@ -64,7 +64,7 @@ export default class MapFiltersButton extends LitElement {
64
64
  || e.usersIds.filter(u => u !== "geovisio").length > 0
65
65
  );
66
66
  });
67
- });
67
+ }));
68
68
  }
69
69
 
70
70
  /** @private */
@@ -1,5 +1,5 @@
1
1
  import { LitElement, html, nothing } from "lit";
2
- import { fa } from "../../../utils/widgets";
2
+ import { fa, onceParentAvailable } from "../../../utils/widgets";
3
3
  import { faLayerGroup } from "@fortawesome/free-solid-svg-icons/faLayerGroup";
4
4
  import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown";
5
5
  import { faXmark } from "@fortawesome/free-solid-svg-icons/faXmark";
@@ -35,12 +35,12 @@ export default class MapLayersButton extends LitElement {
35
35
 
36
36
  const nullThemes = [undefined, null, "", "default"];
37
37
 
38
- this._parent?.onceMapReady?.().then(() => {
38
+ onceParentAvailable(this).then(() => this._parent?.onceMapReady?.().then(() => {
39
39
  this._active = !nullThemes.includes(this._parent.map._mapFilters.theme);
40
40
  this._parent.map.on("filters-changed", e => {
41
41
  this._active = !nullThemes.includes(e.theme);
42
42
  });
43
- });
43
+ }));
44
44
  }
45
45
 
46
46
  /** @private */