@panoramax/web-viewer 4.0.1 → 4.0.2-develop-9b499e28

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 (56) hide show
  1. package/CHANGELOG.md +31 -3
  2. package/build/index.css +1 -1
  3. package/build/index.css.map +1 -1
  4. package/build/index.js +150 -51
  5. package/build/index.js.map +1 -1
  6. package/config/jest/mocks.js +2 -1
  7. package/docs/03_URL_settings.md +1 -1
  8. package/docs/09_Develop.md +5 -1
  9. package/docs/reference/components/core/Viewer.md +1 -1
  10. package/docs/reference/components/ui/CopyButton.md +1 -0
  11. package/docs/reference/components/ui/Map.md +13 -0
  12. package/docs/reference/components/ui/MapMore.md +13 -0
  13. package/docs/reference/components/ui/Photo.md +1 -1
  14. package/docs/reference/components/ui/widgets/CopyCoordinates.md +32 -0
  15. package/docs/reference/components/ui/widgets/GeoSearch.md +5 -1
  16. package/docs/reference/utils/API.md +1 -1
  17. package/docs/reference.md +1 -0
  18. package/docs/tutorials/migrate_v4.md +1 -1
  19. package/docs/tutorials/synced_coverage.md +1 -1
  20. package/mkdocs.yml +1 -0
  21. package/package.json +1 -1
  22. package/src/components/core/CoverageMap.js +2 -2
  23. package/src/components/core/PhotoViewer.js +5 -1
  24. package/src/components/core/Viewer.js +10 -5
  25. package/src/components/menus/PictureLegend.js +7 -4
  26. package/src/components/menus/PictureMetadata.js +23 -2
  27. package/src/components/styles.js +61 -0
  28. package/src/components/ui/ButtonGroup.css +2 -0
  29. package/src/components/ui/CopyButton.js +3 -1
  30. package/src/components/ui/Map.js +35 -4
  31. package/src/components/ui/Photo.js +4 -2
  32. package/src/components/ui/TogglableGroup.js +1 -1
  33. package/src/components/ui/widgets/CopyCoordinates.js +75 -0
  34. package/src/components/ui/widgets/GeoSearch.js +13 -5
  35. package/src/components/ui/widgets/Legend.js +1 -1
  36. package/src/components/ui/widgets/OSMEditors.js +2 -2
  37. package/src/components/ui/widgets/PictureLegendActions.js +1 -1
  38. package/src/components/ui/widgets/Player.js +1 -0
  39. package/src/components/ui/widgets/index.js +1 -0
  40. package/src/translations/en.json +6 -2
  41. package/src/translations/fr.json +6 -2
  42. package/src/translations/it.json +3 -1
  43. package/src/translations/ti.json +9 -0
  44. package/src/utils/API.js +1 -1
  45. package/src/utils/InitParameters.js +2 -2
  46. package/src/utils/geocoder.js +137 -83
  47. package/src/utils/index.js +2 -1
  48. package/src/utils/picture.js +6 -1
  49. package/src/utils/services.js +57 -0
  50. package/src/utils/utils.js +18 -5
  51. package/tests/components/ui/Map.test.js +7 -3
  52. package/tests/data/Map_geocoder_nominatim.json +25 -40
  53. package/tests/utils/InitParameters.test.js +15 -15
  54. package/tests/utils/__snapshots__/geocoder.test.js.snap +5 -16
  55. package/tests/utils/geocoder.test.js +2 -2
  56. package/tests/utils/utils.test.js +136 -109
@@ -97,9 +97,10 @@ jest.mock("maplibre-gl", () => ({
97
97
  addControl() {;}
98
98
  addSource() {;}
99
99
  addLayer() {;}
100
- getLayer() {;}
100
+ getLayer(l) { return {id: l}; }
101
101
  setLayoutProperty() {;}
102
102
  setPaintProperty() {;}
103
+ loaded() { return true; }
103
104
  getStyle() {
104
105
  return {
105
106
  layers: [],
@@ -31,7 +31,7 @@ By default, picture is shown wide.
31
31
 
32
32
  ### :simple-speedtest: `speed`: sequence play speed
33
33
 
34
- The duration of stay on a picture during sequence play (excluding image dowloading time), in milliseconds. Authorized values are between 0 and 3000. Example:
34
+ The duration of stay on a picture during sequence play (excluding image downloading time), in milliseconds. Authorized values are between 0 and 3000. Example:
35
35
 
36
36
  ```urlencoded
37
37
  speed=1000
@@ -14,7 +14,7 @@ The current code is split between various elements:
14
14
 
15
15
  - __Core components__: a single functional entry, like [Viewer](./reference/components/core/Viewer.md) (map + picture), [Photo Viewer](./reference/components/core/PhotoViewer.md) (picture only), [Coverage Map](./reference/components/core/CoverageMap.md) or [Editor](./reference/components/core/Editor.md). They share parts of code in [Basic](./reference/components/core/Basic.md) class. They specialized the behaviour of components depending on the needs.
16
16
  - __UI components, menus & widgets__: reusable web components, like [Map](./reference/components/ui/Map.md), [Photo](./reference/components/ui/Photo.md) or [Loader](./reference/components/ui/Loader.md). They are used in some views depending of the context.
17
- - __Utils__: utility functions, splitted in various files for clarity.
17
+ - __Utils__: utility functions, split into various files for clarity.
18
18
 
19
19
  ![Class diagram of Panoramax web viewer](./images/class_diagram.jpg)
20
20
 
@@ -54,6 +54,10 @@ This allows a flexible way to interact with viewer for users. Prioritization of
54
54
 
55
55
  This means that, when developing, if you want to check if your attributes are well-defined, you may want to get rid of URL search parameters, as well as remove the `pnx-map-parameters` local storage item. Otherwise, they may not be read as they are lower priority than others.
56
56
 
57
+ ### Third-party services URL
58
+
59
+ All third-party services URL (like geocoding API, OSM iD editor) are grouped into a dedicated `src/utils/services.js` file. You can easily change them to deploy a custom version of the viewer.
60
+
57
61
  ## Testing
58
62
 
59
63
  We're trying to make Panoramax as reliable and secure as possible. To ensure this, we rely heavily on code testing. A variety of testing tools is made available:
@@ -114,7 +114,7 @@ Component properties. All of [Basic properties](Basic.md/#Panoramax.components.c
114
114
  | [psv] | <code>object</code> | | [Any option to pass to Photo component](../ui/Photo.md/#Panoramax.components.ui.Photo) as an object.<br />Example: `psv="{'transitionDuration': 500, 'picturesNavigation': 'pic'}"` |
115
115
  | [url-parameters] | <code>string</code> | <code>true</code> | Should the component add and update URL query parameters to save viewer state ? |
116
116
  | [focus] | <code>string</code> | <code>&quot;pic&quot;</code> | The component showing up as main component (pic, map) |
117
- | [geocoder] | <code>string</code> | <code>&quot;nominatim&quot;</code> | The geocoder engine to use (nominatim, ban) |
117
+ | [geocoder] | <code>string</code> | <code>&quot;nominatim&quot;</code> | The geocoder engine to use (nominatim, ban, or URL to a standard [GeocodeJSON-compliant](https://github.com/geocoders/geocodejson-spec/blob/master/draft/README.md) API) |
118
118
  | [widgets] | <code>string</code> | <code>true</code> | Use default set of widgets ? Set to false to avoid any widget to show up, and use slots to populate as you like. |
119
119
  | [picture] | <code>string</code> | | The picture ID to display |
120
120
  | [sequence] | <code>string</code> | | The sequence ID of the picture displayed |
@@ -35,4 +35,5 @@ Component properties.
35
35
  | [kind] | <code>string</code> | <code>&quot;full&quot;</code> | The style variation of the button (full, outline, flat, superflat, inline, superinline) |
36
36
  | [size] | <code>string</code> | <code>&quot;md&quot;</code> | The size of the button (sm, md, l, xl, xxl) |
37
37
  | [unstyled] | <code>boolean</code> | <code>false</code> | Disable all styling (for list group integration) |
38
+ | [title] | <code>string</code> | | Tooltip text displayed when hovering over the button |
38
39
 
@@ -14,6 +14,7 @@
14
14
  * [.getBackground()](#Panoramax.components.ui.Map+getBackground) ⇒ <code>string</code>
15
15
  * [.setBackground(bg)](#Panoramax.components.ui.Map+setBackground)
16
16
  * [.getVisibleUsers()](#Panoramax.components.ui.Map+getVisibleUsers) ⇒ <code>Array.&lt;string&gt;</code>
17
+ * [.onceLayerReady(layerId)](#Panoramax.components.ui.Map+onceLayerReady) ⇒ <code>Promise</code>
17
18
  * [.setVisibleUsers(visibleIds)](#Panoramax.components.ui.Map+setVisibleUsers)
18
19
  * [.filterUserLayersContent(dataType, filter)](#Panoramax.components.ui.Map+filterUserLayersContent)
19
20
  * [.displayPictureMarker(lon, lat, heading, [skipCenter])](#Panoramax.components.ui.Map+displayPictureMarker)
@@ -109,6 +110,18 @@ Get the currently visible users
109
110
 
110
111
  **Kind**: instance method of [<code>Map</code>](#Panoramax.components.ui.Map)
111
112
  **Returns**: <code>Array.&lt;string&gt;</code> - List of visible users
113
+ <a name="Panoramax.components.ui.Map+onceLayerReady"></a>
114
+
115
+ ### map.onceLayerReady(layerId) ⇒ <code>Promise</code>
116
+ Wait for a given map layer to be really available.
117
+
118
+ **Kind**: instance method of [<code>Map</code>](#Panoramax.components.ui.Map)
119
+ **Fulfil**: <code>null</code> When layer is ready.
120
+
121
+ | Param | Type | Description |
122
+ | --- | --- | --- |
123
+ | layerId | <code>string</code> | the layer ID |
124
+
112
125
  <a name="Panoramax.components.ui.Map+setVisibleUsers"></a>
113
126
 
114
127
  ### map.setVisibleUsers(visibleIds)
@@ -14,6 +14,7 @@
14
14
  * [.getBackground()](Map.md/#Panoramax.components.ui.Map+getBackground) ⇒ <code>string</code>
15
15
  * [.setBackground(bg)](Map.md/#Panoramax.components.ui.Map+setBackground)
16
16
  * [.getVisibleUsers()](Map.md/#Panoramax.components.ui.Map+getVisibleUsers) ⇒ <code>Array.&lt;string&gt;</code>
17
+ * [.onceLayerReady(layerId)](Map.md/#Panoramax.components.ui.Map+onceLayerReady) ⇒ <code>Promise</code>
17
18
  * [.setVisibleUsers(visibleIds)](Map.md/#Panoramax.components.ui.Map+setVisibleUsers)
18
19
  * [.filterUserLayersContent(dataType, filter)](Map.md/#Panoramax.components.ui.Map+filterUserLayersContent)
19
20
  * [.displayPictureMarker(lon, lat, heading, [skipCenter])](#Panoramax.components.ui.Map+displayPictureMarker)
@@ -109,6 +110,18 @@ Get the currently visible users
109
110
 
110
111
  **Kind**: instance method of [<code>MapMore</code>](#Panoramax.components.ui.MapMore)
111
112
  **Returns**: <code>Array.&lt;string&gt;</code> - List of visible users
113
+ <a name="Panoramax.components.ui.Map+onceLayerReady"></a>
114
+
115
+ ### mapMore.onceLayerReady(layerId) ⇒ <code>Promise</code>
116
+ Wait for a given map layer to be really available.
117
+
118
+ **Kind**: instance method of [<code>MapMore</code>](#Panoramax.components.ui.MapMore)
119
+ **Fulfil**: <code>null</code> When layer is ready.
120
+
121
+ | Param | Type | Description |
122
+ | --- | --- | --- |
123
+ | layerId | <code>string</code> | the layer ID |
124
+
112
125
  <a name="Panoramax.components.ui.Map+setVisibleUsers"></a>
113
126
 
114
127
  ### mapMore.setVisibleUsers(visibleIds)
@@ -289,7 +289,7 @@ Event for picture preview
289
289
  | --- | --- | --- |
290
290
  | detail.picId | <code>string</code> | The picture ID |
291
291
  | detail.coordinates | <code>Array.&lt;number&gt;</code> | [x,y] coordinates |
292
- | detail.direction | <code>number</code> | The theorical picture orientation |
292
+ | detail.direction | <code>number</code> | The theoretical picture orientation |
293
293
 
294
294
  <a name="Panoramax.components.ui.Photo+event_picture-preview-stopped"></a>
295
295
 
@@ -0,0 +1,32 @@
1
+ <a name="Panoramax.components.ui.widgets.CopyCoordinates"></a>
2
+
3
+ ## Panoramax.components.ui.widgets.CopyCoordinates ⇐ <code>[lit.LitElement](https://lit.dev/docs/api/LitElement/)</code>
4
+ **Kind**: static class of <code>Panoramax.components.ui.widgets</code>
5
+ **Extends**: <code>[lit.LitElement](https://lit.dev/docs/api/LitElement/)</code>
6
+ **Element**: pnx-copy-coordinates
7
+
8
+ * [.CopyCoordinates](#Panoramax.components.ui.widgets.CopyCoordinates) ⇐ <code>[lit.LitElement](https://lit.dev/docs/api/LitElement/)</code>
9
+ * [new CopyCoordinates()](#new_Panoramax.components.ui.widgets.CopyCoordinates_new)
10
+ * [.properties](#Panoramax.components.ui.widgets.CopyCoordinates+properties) : <code>Object</code>
11
+
12
+ <a name="new_Panoramax.components.ui.widgets.CopyCoordinates_new"></a>
13
+
14
+ ### new CopyCoordinates()
15
+ Copy Coordinates button allows easy copy of several format of map coordinates.
16
+
17
+ **Example**
18
+ ```html
19
+ <pnx-copy-coordinates gps=${[-1.7, 48.6]} _parent=${viewer} />
20
+ ```
21
+ <a name="Panoramax.components.ui.widgets.CopyCoordinates+properties"></a>
22
+
23
+ ### copyCoordinates.properties : <code>Object</code>
24
+ Component properties.
25
+
26
+ **Kind**: instance property of [<code>CopyCoordinates</code>](#Panoramax.components.ui.widgets.CopyCoordinates)
27
+ **Properties**
28
+
29
+ | Name | Type | Description |
30
+ | --- | --- | --- |
31
+ | gps | <code>Array.&lt;number&gt;</code> | GPS/map coordinates, as [lon, lat] |
32
+
@@ -16,7 +16,11 @@ Ready-to-use geocoder search bar.
16
16
 
17
17
  **Example**
18
18
  ```html
19
+ <!-- Default geocoder -->
19
20
  <pnx-widget-geosearch _parent=${viewer} />
21
+
22
+ <!-- Custom-URL geocoder -->
23
+ <pnx-widget-geosearch geocoder="https://photon.komoot.io/api" _parent=${viewer} />
20
24
  ```
21
25
  <a name="Panoramax.components.ui.widgets.GeoSearch+properties"></a>
22
26
 
@@ -28,5 +32,5 @@ Component properties.
28
32
 
29
33
  | Name | Type | Default | Description |
30
34
  | --- | --- | --- | --- |
31
- | [geocoder] | <code>string</code> | <code>&quot;nominatim&quot;</code> | The geocoder engine to use (nominatim, ban) |
35
+ | [geocoder] | <code>string</code> | <code>&quot;nominatim&quot;</code> | The geocoder engine to use (nominatim, ban, or URL to a standard [GeocodeJSON-compliant](https://github.com/geocoders/geocodejson-spec/blob/master/draft/README.md) API) |
32
36
 
@@ -39,7 +39,7 @@ API contains various utility functions to communicate with Panoramax/STAC API
39
39
 
40
40
  | Param | Type | Default | Description |
41
41
  | --- | --- | --- | --- |
42
- | endpoint | <code>string</code> | | The endpoint. It corresponds to the <a href="https://github.com/radiantearth/stac-api-spec/blob/main/overview.md#example-landing-page">STAC landing page</a>, with all links describing the API capabilites. |
42
+ | endpoint | <code>string</code> | | The endpoint. It corresponds to the <a href="https://github.com/radiantearth/stac-api-spec/blob/main/overview.md#example-landing-page">STAC landing page</a>, with all links describing the API capabilities. |
43
43
  | [options] | <code>object</code> | | Options |
44
44
  | [options.style] | <code>string</code> \| <code>object</code> | | General map style |
45
45
  | [options.tiles] | <code>string</code> | | API route serving pictures & sequences vector tiles |
package/docs/reference.md CHANGED
@@ -61,6 +61,7 @@ Basic UI components:
61
61
 
62
62
  More complex UI components (but not menus):
63
63
 
64
+ - [CopyCoordinates](./reference/components/ui/widgets/CopyCoordinates.md) : a copy-to-clipboard button for coordinates, with many format options.
64
65
  - [GeoSearch](./reference/components/ui/widgets/GeoSearch.md) : a geocoder search bar with GPS location tool.
65
66
  - [Legend](./reference/components/ui/widgets/Legend.md) : a togglable map/picture legend.
66
67
  - [MapFiltersButton](./reference/components/ui/widgets/MapFiltersButton.md) : a togglable map filters button & menu.
@@ -68,7 +68,7 @@ Many stuff was moved around, and have a bit different naming. Main classes shoul
68
68
 
69
69
  A new main component is available, named __Photo Viewer__ (`<pnx-photo-viewer>`) for showing up only picture (without map).
70
70
 
71
- And source code is splitted in more packages:
71
+ And source code is split into more packages:
72
72
 
73
73
  * `Panoramax.components` : everything you can see graphically
74
74
  * `.core` : Viewer, Editor, Coverage Map and Basic (common code)
@@ -32,7 +32,7 @@ coverage.addEventListener("ready", () => {
32
32
  console.log("Selected sequence", e.detail.seqId, "picture", e.detail.picId);
33
33
  });
34
34
 
35
- // You can also programatically change selection on map
35
+ // You can also programmatically change selection on map
36
36
  coverage.select(
37
37
  "c463d190-06b0-47fb-98a8-b4a775a39ad6", // A sequence ID
38
38
  "bdea1eb4-4496-46da-a4d5-b22b16e75fa8" // A picture ID (can be null if unknown)
package/mkdocs.yml CHANGED
@@ -77,6 +77,7 @@ nav:
77
77
  - ShareMenu: 'reference/components/menus/ShareMenu.md'
78
78
  - ui:
79
79
  - widgets:
80
+ - CopyCoordinates: 'reference/components/ui/widgets/CopyCoordinates.md'
80
81
  - GeoSearch: 'reference/components/ui/widgets/GeoSearch.md'
81
82
  - Legend: 'reference/components/ui/widgets/Legend.md'
82
83
  - MapFiltersButton: 'reference/components/ui/widgets/MapFiltersButton.md'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@panoramax/web-viewer",
3
- "version": "4.0.1",
3
+ "version": "4.0.2-develop-9b499e28",
4
4
  "description": "Panoramax web viewer for geolocated pictures",
5
5
  "main": "build/index.js",
6
6
  "author": "Panoramax team",
@@ -108,8 +108,8 @@ export default class CoverageMap extends Basic {
108
108
  this.map.on("picture-click", e => this.select(e.seqId, e.picId));
109
109
  this.map.on("sequence-click", e => this.select(e.seqId));
110
110
 
111
- this.map.waitForEnoughMapLoaded().then(() => {
112
- alterMapState(this.map, this._initParams.getMapPostInit());
111
+ this.map.waitForEnoughMapLoaded().then(async () => {
112
+ await alterMapState(this.map, this._initParams.getMapPostInit());
113
113
  this.map.reloadLayersStyles();
114
114
  this.loader.dismiss();
115
115
  });
@@ -323,7 +323,11 @@ export default class PhotoViewer extends Basic {
323
323
  for(let cn of this.grid.childNodes) {
324
324
  if(cn.getAttribute("slot") !== "bg") {
325
325
  cn.addEventListener("focusin", () => keytonone());
326
- cn.addEventListener("focusout", () => keytopsv());
326
+ cn.addEventListener("focusout", () => {
327
+ if(this.popup.getAttribute("visible") === null) {
328
+ keytopsv();
329
+ }
330
+ });
327
331
  }
328
332
  }
329
333
  }
@@ -95,7 +95,7 @@ export default class Viewer extends PhotoViewer {
95
95
  * @property {object} [psv] [Any option to pass to Photo component](#Panoramax.components.ui.Photo) as an object.<br />Example: `psv="{'transitionDuration': 500, 'picturesNavigation': 'pic'}"`
96
96
  * @property {string} [url-parameters=true] Should the component add and update URL query parameters to save viewer state ?
97
97
  * @property {string} [focus=pic] The component showing up as main component (pic, map)
98
- * @property {string} [geocoder=nominatim] The geocoder engine to use (nominatim, ban)
98
+ * @property {string} [geocoder=nominatim] The geocoder engine to use (nominatim, ban, or URL to a standard [GeocodeJSON-compliant](https://github.com/geocoders/geocodejson-spec/blob/master/draft/README.md) API)
99
99
  * @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.
100
100
  * @property {string} [picture] The picture ID to display
101
101
  * @property {string} [sequence] The sequence ID of the picture displayed
@@ -294,7 +294,7 @@ export default class Viewer extends PhotoViewer {
294
294
  });
295
295
  });
296
296
 
297
- alterMapState(this.map, this._initParams.getMapPostInit());
297
+ await alterMapState(this.map, this._initParams.getMapPostInit());
298
298
  initMapKeyboardHandler(this);
299
299
  linkMapAndPhoto(this);
300
300
  }
@@ -336,11 +336,11 @@ export default class Viewer extends PhotoViewer {
336
336
  };
337
337
  const keytopsv = () => {
338
338
  this.psv.startKeyboardControl();
339
- this.map?.keyboard?.disable();
339
+ this.map.keyboard.disable();
340
340
  };
341
341
  const keytonone = () => {
342
342
  this.psv.stopKeyboardControl();
343
- this.map?.keyboard?.disable();
343
+ this.map.keyboard.disable();
344
344
  };
345
345
  const keytofocused = () => {
346
346
  if(this.map && this.isMapWide()) { keytomap(); }
@@ -350,6 +350,7 @@ export default class Viewer extends PhotoViewer {
350
350
  // General focus change
351
351
  this.addEventListener("focus-changed", e => {
352
352
  if(e.detail.focus === "map") { keytomap(); }
353
+ if(this.popup.getAttribute("visible")) { keytonone(); }
353
354
  else { keytopsv(); }
354
355
  });
355
356
 
@@ -361,7 +362,11 @@ export default class Viewer extends PhotoViewer {
361
362
  for(let cn of this.grid.childNodes) {
362
363
  if(cn.getAttribute("slot") !== "bg") {
363
364
  cn.addEventListener("focusin", () => keytonone());
364
- cn.addEventListener("focusout", () => keytofocused());
365
+ cn.addEventListener("focusout", () => {
366
+ if(this.popup.getAttribute("visible") === null) {
367
+ keytofocused();
368
+ }
369
+ });
365
370
  }
366
371
  }
367
372
  }
@@ -145,6 +145,9 @@ export default class PictureLegend extends LitElement {
145
145
  this._parent.psv.addEventListener("picture-loaded", () => {
146
146
  this._onPicChange(this._parent.psv.getPictureMetadata());
147
147
  });
148
+ this._parent.psv.addEventListener("sequence-stopped", () => {
149
+ this._onPicChange(this._parent.psv.getPictureMetadata());
150
+ });
148
151
  });
149
152
  }
150
153
 
@@ -154,12 +157,12 @@ export default class PictureLegend extends LitElement {
154
157
  this._caption = picMeta?.caption;
155
158
 
156
159
  if(picMeta) {
157
- const coordsHash = `${picMeta.gps[0]}/${picMeta.gps[1]}`;
160
+ const coordsHash = `${picMeta.gps[0].toFixed(4)}/${picMeta.gps[1].toFixed(4)}`;
158
161
  if(this._prevSearches[coordsHash]) {
159
162
  this._addr = this._prevSearches[coordsHash];
160
163
  }
161
- else {
162
- this._addrTimer2 = setTimeout(() => this._addr = "", 250);
164
+ else if(!this._parent.psv._sequencePlaying) {
165
+ this._addr = "";
163
166
  this._addrTimer1 = setTimeout(() => {
164
167
  reverseGeocodingNominatim(picMeta.gps[1], picMeta.gps[0])
165
168
  .then(addr => {
@@ -167,7 +170,7 @@ export default class PictureLegend extends LitElement {
167
170
  this._addr = addr;
168
171
  this._prevSearches[coordsHash] = addr;
169
172
  });
170
- }, 750);
173
+ }, 500);
171
174
  }
172
175
  }
173
176
  else {
@@ -146,8 +146,18 @@ export default class PictureMetadata extends LitElement {
146
146
  // Location tab
147
147
  const orientation = this._meta?.properties?.["view:azimuth"] !== undefined && `${this._meta.properties["view:azimuth"]}°`;
148
148
  const locationData = [
149
- { title: this._parent?._t.pnx.metadata_location_longitude, content: this._meta.gps[0] },
150
- { title: this._parent?._t.pnx.metadata_location_latitude, content: this._meta.gps[1] },
149
+ {
150
+ title: this._parent?._t.pnx.metadata_location_coordinates,
151
+ content: html`
152
+ ${this._meta.gps[0]}, ${this._meta.gps[1]}
153
+ <pnx-copy-coordinates
154
+ ._parent=${this._parent}
155
+ .gps=${this._meta.gps}
156
+ style="margin-left: 10px"
157
+ ></pnx-copy-coordinates>
158
+ `,
159
+ style: "width: 100%"
160
+ },
151
161
  { title: this._parent?._t.pnx.metadata_location_orientation, content: orientation || missing() },
152
162
  { title: this._parent?._t.pnx.metadata_location_precision, content: getGPSPrecision(this._meta) || missing() },
153
163
  ];
@@ -211,6 +221,17 @@ export default class PictureMetadata extends LitElement {
211
221
  style: value.length > 30 ? "width: 100%" : undefined
212
222
  };
213
223
  });
224
+ exifData.unshift({
225
+ content: html`
226
+ <pnx-link-button
227
+ size="sm"
228
+ target="_blank"
229
+ href="https://exiv2.org/metadata.html"
230
+ title=${this._parent?._t.pnx.metadata_exif_doc_title}
231
+ >${fa(faInfoCircle)} ${this._parent?._t.pnx.metadata_exif_doc}</pnx-link-button>
232
+ `,
233
+ style: "width: 100%"
234
+ });
214
235
 
215
236
  // General metadata
216
237
  const overview = [
@@ -274,6 +274,67 @@ export const btn = css`
274
274
  }
275
275
  `;
276
276
 
277
+ // Button group
278
+ export const btngroup = css`
279
+ pnx-button-group > pnx-button {
280
+ display: inline-flex;
281
+ }
282
+
283
+ pnx-button-group > pnx-button::part(btn) {
284
+ height: unset;
285
+ }
286
+
287
+ /* Togglable in group */
288
+ pnx-button-group > pnx-togglable-group > pnx-button {
289
+ width: 100%;
290
+ height: 100%;
291
+ }
292
+
293
+ /* Row */
294
+ pnx-button-group[dir="row"] > :not(:first-child):not(:last-child)::part(btn),
295
+ pnx-button-group[dir="row"] > :not(:first-child):not(:last-child) > ::part(btn) {
296
+ border-radius: 0;
297
+ border-left: none;
298
+ border-right: none;
299
+ }
300
+
301
+ pnx-button-group[dir="row"] > :first-child::part(btn),
302
+ pnx-button-group[dir="row"] > :first-child > ::part(btn) {
303
+ border-top-right-radius: 0;
304
+ border-bottom-right-radius: 0;
305
+ border-right: none;
306
+ }
307
+
308
+ pnx-button-group[dir="row"] > :last-child::part(btn),
309
+ pnx-button-group[dir="row"] > :last-child > ::part(btn) {
310
+ border-top-left-radius: 0;
311
+ border-bottom-left-radius: 0;
312
+ border-left: none;
313
+ }
314
+
315
+ /* Column */
316
+ pnx-button-group[dir="column"] > :not(:first-child):not(:last-child)::part(btn),
317
+ pnx-button-group[dir="column"] > :not(:first-child):not(:last-child) > ::part(btn) {
318
+ border-radius: 0;
319
+ border-top: none;
320
+ border-bottom: none;
321
+ }
322
+
323
+ pnx-button-group[dir="column"] > :first-child::part(btn),
324
+ pnx-button-group[dir="column"] > :first-child > ::part(btn) {
325
+ border-bottom-right-radius: 0;
326
+ border-bottom-left-radius: 0;
327
+ border-bottom: none;
328
+ }
329
+
330
+ pnx-button-group[dir="column"] > :last-child::part(btn),
331
+ pnx-button-group[dir="column"] > :last-child > ::part(btn) {
332
+ border-top-left-radius: 0;
333
+ border-top-right-radius: 0;
334
+ border-top: none;
335
+ }
336
+ `;
337
+
277
338
  // Titles
278
339
  export const titles = css`
279
340
  h1, h2, h3, h4, h5, h6 {
@@ -1,3 +1,5 @@
1
+ /* Also defined in styles.js */
2
+
1
3
  pnx-button-group > pnx-button {
2
4
  display: inline-flex;
3
5
  }
@@ -41,6 +41,7 @@ export default class CopyButton extends LitElement {
41
41
  * @property {string} [kind=full] The style variation of the button (full, outline, flat, superflat, inline, superinline)
42
42
  * @property {string} [size=md] The size of the button (sm, md, l, xl, xxl)
43
43
  * @property {boolean} [unstyled=false] Disable all styling (for list group integration)
44
+ * @property {string} [title] Tooltip text displayed when hovering over the button
44
45
  */
45
46
  static properties = {
46
47
  text: {type: String},
@@ -48,6 +49,7 @@ export default class CopyButton extends LitElement {
48
49
  kind: {type: String},
49
50
  size: {type: String},
50
51
  unstyled: {type: Boolean},
52
+ title: { type: String },
51
53
  _active: {state: true, type: Boolean},
52
54
  };
53
55
 
@@ -94,7 +96,7 @@ export default class CopyButton extends LitElement {
94
96
  [`pnx-btn-${this.kind}`]: !this.unstyled,
95
97
  [`pnx-btn-${this.size}`]: !this.unstyled,
96
98
  };
97
- return html`<button class=${classMap(classes)} part="btn">
99
+ return html`<button title=${this.title} class=${classMap(classes)} part="btn">
98
100
  ${this._active ?
99
101
  html`${this._t?.pnx.copied || ""} ${fa(faCheck)}` :
100
102
  html`<slot>${fa(faCopy)} ${this._t?.pnx.copy || ""}</slot>`
@@ -326,10 +326,29 @@ export default class Map extends maplibregl.Map {
326
326
  */
327
327
  getVisibleUsers() {
328
328
  return [...this._userLayers].filter(l => (
329
- this.getLayoutProperty(getUserLayerId(l, "pictures"), "visibility") === "visible"
329
+ this.getLayer(getUserLayerId(l, "pictures"))
330
+ && this.getLayoutProperty(getUserLayerId(l, "pictures"), "visibility") === "visible"
330
331
  ));
331
332
  }
332
333
 
334
+ /**
335
+ * Wait for a given map layer to be really available.
336
+ * @param {string} layerId the layer ID
337
+ * @returns {Promise}
338
+ * @fulfil {null} When layer is ready.
339
+ * @memberof Panoramax.components.ui.Map#
340
+ */
341
+ onceLayerReady(layerId) {
342
+ if(this.getLayer(layerId)) {
343
+ return Promise.resolve();
344
+ }
345
+ else {
346
+ return new Promise(resolve => {
347
+ setTimeout(resolve, 250);
348
+ }).then(() => this.onceLayerReady(layerId));
349
+ }
350
+ }
351
+
333
352
  /**
334
353
  * Make given user layers visible on map, and hide all others (if any)
335
354
  * @memberof Panoramax.components.ui.Map#
@@ -343,7 +362,10 @@ export default class Map extends maplibregl.Map {
343
362
  await Promise.all(
344
363
  visibleIds
345
364
  .filter(id => id != "" && !this._userLayers.has(id))
346
- .map(id => this._createPicturesTilesLayer(id))
365
+ .map(id => {
366
+ this._createPicturesTilesLayer(id);
367
+ return this.onceLayerReady(getUserLayerId(id, "pictures"));
368
+ })
347
369
  );
348
370
 
349
371
  // Switch visibility
@@ -375,6 +397,10 @@ export default class Map extends maplibregl.Map {
375
397
  */
376
398
  filterUserLayersContent(dataType, filter) {
377
399
  [...this._userLayers].forEach(l => {
400
+ if(!this.getLayer(getUserLayerId(l, dataType))) {
401
+ console.warn("Layer", getUserLayerId(l, dataType), "not ready");
402
+ return;
403
+ }
378
404
  this.setFilter(getUserLayerId(l, dataType), filter);
379
405
  if(dataType === "sequences" && this.getLayer(getUserLayerId(l, "sequences_plus"))) {
380
406
  this.setFilter(getUserLayerId(l, "sequences_plus"), filter);
@@ -428,11 +454,16 @@ export default class Map extends maplibregl.Map {
428
454
  reloadLayersStyles() {
429
455
  const updateStyle = (layer, style) => {
430
456
  [...this._userLayers].forEach(l => {
457
+ const layerId = getUserLayerId(l, layer);
458
+ if(!this.getLayer(layerId)) {
459
+ console.warn("Layer", layerId, "not ready");
460
+ return;
461
+ }
431
462
  for(let p in style.layout) {
432
- this.setLayoutProperty(getUserLayerId(l, layer), p, style.layout[p]);
463
+ this.setLayoutProperty(layerId, p, style.layout[p]);
433
464
  }
434
465
  for(let p in style.paint) {
435
- this.setPaintProperty(getUserLayerId(l, layer), p, style.paint[p]);
466
+ this.setPaintProperty(layerId, p, style.paint[p]);
436
467
  }
437
468
  });
438
469
  };
@@ -266,7 +266,9 @@ export default class Photo extends PSViewer {
266
266
  }
267
267
  // Different sequence -> Point to center + no animation
268
268
  else {
269
- nodeTransition = forwardNoAnim;
269
+ nodeTransition = Object.assign(forwardNoAnim, {
270
+ rotateTo: { pitch: 0, yaw: 0 },
271
+ });
270
272
  }
271
273
  }
272
274
 
@@ -338,7 +340,7 @@ export default class Photo extends PSViewer {
338
340
  * @type {CustomEvent}
339
341
  * @property {string} detail.picId The picture ID
340
342
  * @property {number[]} detail.coordinates [x,y] coordinates
341
- * @property {number} detail.direction The theorical picture orientation
343
+ * @property {number} detail.direction The theoretical picture orientation
342
344
  */
343
345
  const event = new CustomEvent("picture-preview-started", { detail: {
344
346
  picId: fromLink.nodeId,
@@ -145,7 +145,7 @@ export default class TogglableGroup extends LitElement {
145
145
  "pnx-padded": this.padded !== "false",
146
146
  };
147
147
 
148
- return html`<div class="container">
148
+ return html`<div class="container" @click=${e => e.stopPropagation()}>
149
149
  <slot name="button" @slotchange=${this.handleButtonSlotChange}></slot>
150
150
  <div class=${classMap(panelClasses)} part="menu">
151
151
  <slot></slot>