@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.
- package/CHANGELOG.md +46 -1
- package/build/index.css +9 -9
- package/build/index.css.map +1 -1
- package/build/index.js +640 -456
- package/build/index.js.map +1 -1
- package/build/map.html +1 -1
- package/build/photo.html +1 -1
- package/build/viewer.html +3 -3
- package/build/widgets.html +1 -1
- package/config/jest/mocks.js +9 -1
- package/docs/03_URL_settings.md +21 -0
- package/docs/09_Develop.md +6 -0
- package/docs/images/comparative_3drender.jpg +0 -0
- package/docs/index.md +13 -0
- package/docs/reference/components/core/Editor.md +18 -0
- package/docs/reference/components/core/PhotoViewer.md +1 -0
- package/docs/reference/components/core/Viewer.md +1 -0
- package/docs/reference/components/menus/MapLegend.md +17 -0
- package/docs/reference/components/menus/MiniPictureLegend.md +15 -0
- package/docs/reference/components/menus/PictureLegend.md +17 -0
- package/docs/reference/components/ui/AnnotationsSwitch.md +15 -0
- package/docs/reference/components/ui/Button.md +1 -1
- package/docs/reference/components/ui/CopyButton.md +1 -1
- package/docs/reference/components/ui/LinkButton.md +1 -1
- package/docs/reference/components/ui/Map.md +18 -2
- package/docs/reference/components/ui/MapMore.md +6 -2
- package/docs/reference/components/ui/SemanticsEditor.md +87 -0
- package/docs/reference/components/ui/widgets/Legend.md +5 -4
- package/docs/reference/utils/URLHandler.md +7 -0
- package/docs/reference.md +3 -1
- package/docs/tutorials/aerial_imagery.md +13 -11
- package/mkdocs.yml +3 -1
- package/package.json +7 -7
- package/public/map.html +3 -3
- package/public/photo.html +1 -1
- package/public/viewer.html +3 -3
- package/public/widgets.html +32 -0
- package/src/components/core/Basic.css +2 -0
- package/src/components/core/Basic.js +3 -1
- package/src/components/core/CoverageMap.css +1 -0
- package/src/components/core/CoverageMap.js +6 -0
- package/src/components/core/Editor.css +2 -0
- package/src/components/core/Editor.js +56 -7
- package/src/components/core/PhotoViewer.css +10 -10
- package/src/components/core/PhotoViewer.js +56 -23
- package/src/components/core/Viewer.css +16 -4
- package/src/components/core/Viewer.js +62 -33
- package/src/components/layout/BottomDrawer.js +2 -1
- package/src/components/layout/Tabs.js +4 -0
- package/src/components/menus/AnnotationsList.js +13 -9
- package/src/components/menus/MapBackground.js +8 -3
- package/src/components/menus/MapFilters.js +11 -2
- package/src/components/menus/MapLayers.js +3 -2
- package/src/components/menus/MapLegend.js +35 -4
- package/src/components/menus/MiniPictureLegend.js +74 -0
- package/src/components/menus/PictureLegend.js +89 -33
- package/src/components/menus/PictureMetadata.js +49 -17
- package/src/components/menus/PlayerOptions.js +3 -3
- package/src/components/menus/Share.js +3 -3
- package/src/components/menus/index.js +5 -4
- package/src/components/styles.js +11 -0
- package/src/components/ui/AnnotationsSwitch.js +171 -0
- package/src/components/ui/Button.js +1 -1
- package/src/components/ui/CopyButton.js +1 -1
- package/src/components/ui/LinkButton.js +1 -1
- package/src/components/ui/Map.css +4 -0
- package/src/components/ui/Map.js +17 -5
- package/src/components/ui/MapMore.js +61 -25
- package/src/components/ui/Photo.css +11 -2
- package/src/components/ui/Photo.js +6 -3
- package/src/components/ui/SemanticsEditor.js +157 -0
- package/src/components/ui/index.js +2 -1
- package/src/components/ui/widgets/GeoSearch.js +3 -2
- package/src/components/ui/widgets/Legend.js +76 -15
- package/src/components/ui/widgets/MapFiltersButton.js +3 -3
- package/src/components/ui/widgets/MapLayersButton.js +3 -3
- package/src/components/ui/widgets/OSMEditors.js +2 -2
- package/src/components/ui/widgets/PictureLegendActions.js +24 -42
- package/src/components/ui/widgets/Player.js +3 -3
- package/src/components/ui/widgets/Zoom.js +4 -2
- package/src/translations/ar.json +1 -0
- package/src/translations/da.json +3 -2
- package/src/translations/de.json +64 -13
- package/src/translations/en.json +5 -1
- package/src/translations/eo.json +32 -2
- package/src/translations/fr.json +7 -1
- package/src/translations/it.json +33 -2
- package/src/translations/nl.json +53 -11
- package/src/translations/zh_Hant.json +29 -2
- package/src/utils/API.js +17 -1
- package/src/utils/InitParameters.js +46 -4
- package/src/utils/URLHandler.js +9 -1
- package/src/utils/map.js +24 -1
- package/src/utils/semantics.js +53 -1
- package/src/utils/services.js +16 -0
- package/src/utils/widgets.js +38 -0
- package/tests/components/core/Editor.test.js +1 -1
- package/tests/components/core/__snapshots__/PhotoViewer.test.js.snap +18 -6
- package/tests/components/core/__snapshots__/Viewer.test.js.snap +15 -3
- package/tests/components/ui/Photo.test.js +1 -0
- package/tests/components/ui/__snapshots__/Map.test.js.snap +164 -0
- package/tests/utils/InitParameters.test.js +27 -0
- package/tests/utils/map.test.js +12 -0
- package/tests/utils/semantics.test.js +34 -5
- package/docs/reference/components/ui/HashTags.md +0 -15
- package/src/components/ui/HashTags.js +0 -98
package/src/components/ui/Map.js
CHANGED
|
@@ -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
|
-
(
|
|
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
|
-
|
|
203
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
239
|
-
mapSeqFilters.push(["==", ["get", "type"],
|
|
240
|
-
mapPicFilters.push(["==", ["get", "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
|
-
|
|
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
|
-
|
|
260
|
-
mapSeqFilters.push(["in", MAP_EXPR_QUALITYSCORE, ["literal",
|
|
261
|
-
mapPicFilters.push(["in", MAP_EXPR_QUALITYSCORE, ["literal",
|
|
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
|
-
|
|
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
|
-
|
|
39
|
-
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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.
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
<
|
|
133
|
+
|
|
134
|
+
${this.light ? html`
|
|
135
|
+
© <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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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 */
|