@panoramax/web-viewer 4.0.3 → 4.1.0

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 (103) hide show
  1. package/CHANGELOG.md +40 -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/photo.html +1 -1
  7. package/build/viewer.html +3 -3
  8. package/build/widgets.html +1 -1
  9. package/config/jest/mocks.js +9 -1
  10. package/docs/03_URL_settings.md +21 -0
  11. package/docs/09_Develop.md +6 -0
  12. package/docs/images/comparative_3drender.jpg +0 -0
  13. package/docs/index.md +13 -0
  14. package/docs/reference/components/core/Editor.md +18 -0
  15. package/docs/reference/components/core/PhotoViewer.md +1 -0
  16. package/docs/reference/components/core/Viewer.md +1 -0
  17. package/docs/reference/components/menus/MapLegend.md +17 -0
  18. package/docs/reference/components/menus/MiniPictureLegend.md +15 -0
  19. package/docs/reference/components/menus/PictureLegend.md +17 -0
  20. package/docs/reference/components/ui/AnnotationsSwitch.md +15 -0
  21. package/docs/reference/components/ui/Button.md +1 -1
  22. package/docs/reference/components/ui/CopyButton.md +1 -1
  23. package/docs/reference/components/ui/LinkButton.md +1 -1
  24. package/docs/reference/components/ui/Map.md +18 -2
  25. package/docs/reference/components/ui/MapMore.md +6 -2
  26. package/docs/reference/components/ui/SemanticsEditor.md +87 -0
  27. package/docs/reference/components/ui/widgets/Legend.md +5 -4
  28. package/docs/reference/utils/URLHandler.md +7 -0
  29. package/docs/reference.md +3 -1
  30. package/docs/tutorials/aerial_imagery.md +13 -11
  31. package/mkdocs.yml +3 -1
  32. package/package.json +7 -7
  33. package/public/photo.html +1 -1
  34. package/public/viewer.html +3 -3
  35. package/public/widgets.html +32 -0
  36. package/src/components/core/Basic.css +2 -0
  37. package/src/components/core/Basic.js +3 -1
  38. package/src/components/core/CoverageMap.js +6 -0
  39. package/src/components/core/Editor.css +1 -0
  40. package/src/components/core/Editor.js +58 -7
  41. package/src/components/core/PhotoViewer.css +5 -10
  42. package/src/components/core/PhotoViewer.js +55 -20
  43. package/src/components/core/Viewer.css +9 -2
  44. package/src/components/core/Viewer.js +62 -33
  45. package/src/components/layout/BottomDrawer.js +2 -1
  46. package/src/components/layout/Tabs.js +4 -0
  47. package/src/components/menus/AnnotationsList.js +13 -9
  48. package/src/components/menus/MapBackground.js +8 -3
  49. package/src/components/menus/MapFilters.js +11 -2
  50. package/src/components/menus/MapLayers.js +3 -2
  51. package/src/components/menus/MapLegend.js +28 -4
  52. package/src/components/menus/MiniPictureLegend.js +74 -0
  53. package/src/components/menus/PictureLegend.js +88 -33
  54. package/src/components/menus/PictureMetadata.js +49 -17
  55. package/src/components/menus/PlayerOptions.js +3 -3
  56. package/src/components/menus/Share.js +3 -3
  57. package/src/components/menus/index.js +5 -4
  58. package/src/components/styles.js +11 -0
  59. package/src/components/ui/AnnotationsSwitch.js +169 -0
  60. package/src/components/ui/Button.js +1 -1
  61. package/src/components/ui/CopyButton.js +1 -1
  62. package/src/components/ui/LinkButton.js +1 -1
  63. package/src/components/ui/Map.css +4 -0
  64. package/src/components/ui/Map.js +17 -5
  65. package/src/components/ui/MapMore.js +61 -25
  66. package/src/components/ui/Photo.css +11 -2
  67. package/src/components/ui/Photo.js +6 -3
  68. package/src/components/ui/SemanticsEditor.js +157 -0
  69. package/src/components/ui/index.js +2 -1
  70. package/src/components/ui/widgets/GeoSearch.js +3 -2
  71. package/src/components/ui/widgets/Legend.js +69 -14
  72. package/src/components/ui/widgets/MapFiltersButton.js +3 -3
  73. package/src/components/ui/widgets/MapLayersButton.js +3 -3
  74. package/src/components/ui/widgets/OSMEditors.js +2 -2
  75. package/src/components/ui/widgets/PictureLegendActions.js +24 -42
  76. package/src/components/ui/widgets/Player.js +3 -3
  77. package/src/components/ui/widgets/Zoom.js +4 -2
  78. package/src/translations/ar.json +1 -0
  79. package/src/translations/da.json +3 -2
  80. package/src/translations/de.json +64 -13
  81. package/src/translations/en.json +5 -1
  82. package/src/translations/eo.json +32 -2
  83. package/src/translations/fr.json +7 -1
  84. package/src/translations/it.json +33 -2
  85. package/src/translations/nl.json +53 -11
  86. package/src/translations/zh_Hant.json +29 -2
  87. package/src/utils/API.js +17 -1
  88. package/src/utils/InitParameters.js +46 -4
  89. package/src/utils/URLHandler.js +9 -1
  90. package/src/utils/map.js +24 -1
  91. package/src/utils/semantics.js +53 -1
  92. package/src/utils/services.js +16 -0
  93. package/src/utils/widgets.js +20 -0
  94. package/tests/components/core/Editor.test.js +1 -1
  95. package/tests/components/core/__snapshots__/PhotoViewer.test.js.snap +18 -6
  96. package/tests/components/core/__snapshots__/Viewer.test.js.snap +15 -3
  97. package/tests/components/ui/Photo.test.js +1 -0
  98. package/tests/components/ui/__snapshots__/Map.test.js.snap +164 -0
  99. package/tests/utils/InitParameters.test.js +27 -0
  100. package/tests/utils/map.test.js +12 -0
  101. package/tests/utils/semantics.test.js +34 -5
  102. package/docs/reference/components/ui/HashTags.md +0 -15
  103. package/src/components/ui/HashTags.js +0 -98
@@ -1,6 +1,6 @@
1
1
  import { LitElement, nothing, css } from "lit";
2
2
  import { html, unsafeStatic } from "lit/static-html.js";
3
- import { fa } from "../../utils/widgets";
3
+ import { fa, onceParentAvailable } from "../../utils/widgets";
4
4
  import { faLocationDot } from "@fortawesome/free-solid-svg-icons/faLocationDot";
5
5
  import { faMedal } from "@fortawesome/free-solid-svg-icons/faMedal";
6
6
  import { faCamera } from "@fortawesome/free-solid-svg-icons/faCamera";
@@ -14,7 +14,8 @@ import { faChevronUp } from "@fortawesome/free-solid-svg-icons/faChevronUp";
14
14
  import { faTags } from "@fortawesome/free-solid-svg-icons/faTags";
15
15
  import { faSvg, titles, textarea, hidden } from "../styles";
16
16
  import { createWebComp } from "../../utils/widgets";
17
- import { getGPSPrecision } from "../../utils/picture";
17
+ import { getGPSPrecision, getHashTags } from "../../utils/picture";
18
+ import { PanoramaxMetaCatalogURL } from "../../utils/services";
18
19
  import {
19
20
  getGrade, QUALITYSCORE_GPS_VALUES, QUALITYSCORE_RES_360_VALUES,
20
21
  QUALITYSCORE_RES_FLAT_VALUES, QUALITYSCORE_POND_GPS, QUALITYSCORE_POND_RES
@@ -81,14 +82,16 @@ export default class PictureMetadata extends LitElement {
81
82
  connectedCallback() {
82
83
  super.connectedCallback();
83
84
 
84
- this._meta = this._parent?.psv?.getPictureMetadata();
85
- this._parent?.oncePSVReady?.().then(() => {
86
- this._parent.psv.addEventListener("picture-loaded", () => {
87
- this._meta = this._parent.psv.getPictureMetadata();
88
- });
89
- this._parent.psv.addEventListener("annotation-click", () => {
90
- const tabs = this.shadowRoot.querySelector("pnx-tabs");
91
- if(tabs) { tabs.setAttribute("activeTabIndex", 4); }
85
+ onceParentAvailable(this).then(() => {
86
+ this._meta = this._parent?.psv?.getPictureMetadata();
87
+ this._parent?.oncePSVReady?.().then(() => {
88
+ this._parent.psv.addEventListener("picture-loaded", () => {
89
+ this._meta = this._parent.psv.getPictureMetadata();
90
+ });
91
+ this._parent.psv.addEventListener("annotation-click", () => {
92
+ const tabs = this.shadowRoot.querySelector("pnx-tabs");
93
+ if(tabs) { tabs.setAttribute("activeTabIndex", 4); }
94
+ });
92
95
  });
93
96
  });
94
97
  }
@@ -101,6 +104,14 @@ export default class PictureMetadata extends LitElement {
101
104
  }
102
105
  }
103
106
 
107
+ /** @private */
108
+ _onCaptureDateClick() {
109
+ const qsTab = this.shadowRoot.querySelector("pnx-tabs");
110
+ if(qsTab) {
111
+ qsTab.setAttribute("activeTabIndex", 1);
112
+ }
113
+ }
114
+
104
115
  /** @private */
105
116
  _toTab(title, data) {
106
117
  return html`
@@ -108,7 +119,7 @@ export default class PictureMetadata extends LitElement {
108
119
  <div slot="content" class="data-blocks">
109
120
  ${data.filter(b => b).map(b => html`<div class="data-block" style=${b.style}>
110
121
  <h5>${b.title}</h5>
111
- <div style=${b.content_style}>${b.content}</div>
122
+ <div style=${b.content_style} @click=${b.click}>${b.content}</div>
112
123
  </div>`)}
113
124
  </div>
114
125
  `;
@@ -234,6 +245,16 @@ export default class PictureMetadata extends LitElement {
234
245
  );
235
246
  let semanticsData = [];
236
247
  if(hasSemantics) {
248
+ // Hashtags
249
+ const hashtags = getHashTags(this._meta);
250
+ if(hashtags.length > 0) {
251
+ semanticsData.push({
252
+ title: this._parent?._t.pnx.semantics_hashtags,
253
+ style: "width: 100%",
254
+ content: hashtags.join(" ")
255
+ });
256
+ }
257
+
237
258
  // Full list of picture tags
238
259
  semanticsData.push({
239
260
  title: this._parent?._t.pnx.semantics_tags_picture,
@@ -284,7 +305,7 @@ export default class PictureMetadata extends LitElement {
284
305
  }
285
306
  return {
286
307
  title: key,
287
- content: value.length > 30 ? html`<textarea readonly>${value}</textarea>`: value,
308
+ content: value.length > 30 ? html`<textarea readonly .value=${value}></textarea>`: value,
288
309
  style: value.length > 30 ? "width: 100%" : undefined
289
310
  };
290
311
  });
@@ -313,9 +334,11 @@ export default class PictureMetadata extends LitElement {
313
334
  // Capture date
314
335
  this._meta?.caption?.date && {
315
336
  title: this._parent?._t.pnx.metadata_general_date,
337
+ click: this._onCaptureDateClick,
338
+ content_style: "cursor: pointer",
316
339
  content: html`
317
340
  <strong>${new Intl.DateTimeFormat(lang, {timeZone: this._meta.caption.tz, dateStyle: "long"}).format(this._meta.caption.date)}</strong>
318
- <br />${new Intl.DateTimeFormat(lang, {timeZone: this._meta.caption.tz, hour: "numeric",minute:"numeric"}).format(this._meta.caption.date)}
341
+ <br />${new Intl.DateTimeFormat(lang, {timeZone: this._meta.caption.tz, hour: "numeric",minute:"numeric", second: "numeric"}).format(this._meta.caption.date)}
319
342
  `
320
343
  },
321
344
  // Camera
@@ -334,10 +357,10 @@ export default class PictureMetadata extends LitElement {
334
357
  // Quality score
335
358
  hasQualityScore && {
336
359
  title: this._parent?._t.pnx.metadata_quality,
360
+ click: this._onQualityScoreClick,
337
361
  content: html`<pnx-quality-score
338
362
  grade=${generalGrade}
339
363
  style="font-size: 14px; cursor: pointer"
340
- @click=${this._onQualityScoreClick}
341
364
  />`
342
365
  },
343
366
  // Copy ID
@@ -367,9 +390,18 @@ export default class PictureMetadata extends LitElement {
367
390
  },
368
391
  this._meta?.origInstance && {
369
392
  title: this._parent?._t.pnx.metadata_general_instance,
370
- content: html`<strong><a href=${this._meta.origInstance.href+window.location.search} target="_blank">
371
- ${this._meta.origInstance.instance_name || this._meta.origInstance.href.replace(/^http.?:\/\//, "")}
372
- </a></strong>`
393
+ content: html`<strong>
394
+ <a href=${this._meta.origInstance.href+window.location.search} target="_blank" style="text-decoration: none">
395
+ <img
396
+ src=${PanoramaxMetaCatalogURL()+"/api/instances/"+this._meta.origInstance.instance_name+"/logo"}
397
+ style="height: 30px; max-width: 150px; vertical-align: middle"
398
+ alt=""
399
+ />
400
+ <span style="text-decoration: underline">
401
+ ${this._meta.origInstance.instance_name || this._meta.origInstance.href.replace(/^http.?:\/\//, "")}
402
+ </span>
403
+ </a>
404
+ </strong>`
373
405
  }
374
406
  ];
375
407
 
@@ -4,7 +4,7 @@ import { faRocket } from "@fortawesome/free-solid-svg-icons/faRocket";
4
4
  import { faLightbulb } from "@fortawesome/free-solid-svg-icons/faLightbulb";
5
5
  import { faPersonBiking } from "@fortawesome/free-solid-svg-icons/faPersonBiking";
6
6
  import { PIC_MAX_STAY_DURATION } from "../ui/Photo";
7
- import { fa } from "../../utils/widgets";
7
+ import { fa, onceParentAvailable } from "../../utils/widgets";
8
8
 
9
9
  /**
10
10
  * Player Options menu displays player speed and contrast settings.
@@ -40,9 +40,9 @@ export default class PlayerOptions extends LitElement {
40
40
  /** @private */
41
41
  connectedCallback() {
42
42
  super.connectedCallback();
43
- this._parent?.psv?.addEventListener("transition-duration-changed", e => {
43
+ onceParentAvailable(this).then(() => this._parent?.psv?.addEventListener("transition-duration-changed", e => {
44
44
  this.renderRoot.querySelector("#pnx-player-speed").value = PIC_MAX_STAY_DURATION - e.detail.value;
45
- });
45
+ }));
46
46
  }
47
47
 
48
48
  /** @private */
@@ -1,5 +1,5 @@
1
1
  import { LitElement, css, html } from "lit";
2
- import { fa } from "../../utils/widgets";
2
+ import { fa, onceParentAvailable } from "../../utils/widgets";
3
3
  import { faSvg, textarea, titles, input } from "../styles";
4
4
  import { faMap } from "@fortawesome/free-solid-svg-icons/faMap";
5
5
  import { faCircleInfo } from "@fortawesome/free-solid-svg-icons/faCircleInfo";
@@ -47,7 +47,7 @@ export default class ShareMenu extends LitElement {
47
47
  /** @private */
48
48
  connectedCallback() {
49
49
  super.connectedCallback();
50
- this._parent?.onceReady().then(() => {
50
+ onceParentAvailable(this).then(() => this._parent.onceReady()).then(() => {
51
51
  this._onUrlChange();
52
52
  this._parent.urlHandler.addEventListener("url-changed", this._onUrlChange.bind(this));
53
53
  });
@@ -85,7 +85,7 @@ export default class ShareMenu extends LitElement {
85
85
  >
86
86
  ${fa(faCircleInfo)} ${this._parent?._t.pnx.share_embed_docs}
87
87
  </pnx-link-button>
88
- <textarea readonly>${iframe}</textarea>
88
+ <textarea readonly .value=${iframe}></textarea>
89
89
  <pnx-copy-button
90
90
  ._t=${this._parent?._t}
91
91
  text=${iframe}
@@ -4,13 +4,14 @@
4
4
  */
5
5
 
6
6
  export {default as AnnotationsList} from "./AnnotationsList";
7
+ export {default as MapBackground} from "./MapBackground";
7
8
  export {default as MapFilters} from "./MapFilters";
8
9
  export {default as MapLayers} from "./MapLayers";
9
- export {default as MapBackground} from "./MapBackground";
10
+ export {default as MapLegend} from "./MapLegend";
11
+ export {default as MiniPictureLegend} from "./MiniPictureLegend";
12
+ export {default as PictureLegend} from "./PictureLegend";
13
+ export {default as PictureMetadata} from "./PictureMetadata";
10
14
  export {default as PlayerOptions} from "./PlayerOptions";
11
15
  export {default as QualityScoreDoc} from "./QualityScoreDoc";
12
16
  export {default as ReportForm} from "./ReportForm";
13
17
  export {default as Share} from "./Share";
14
- export {default as PictureLegend} from "./PictureLegend";
15
- export {default as MapLegend} from "./MapLegend";
16
- export {default as PictureMetadata} from "./PictureMetadata";
@@ -212,6 +212,17 @@ export const btn = css`
212
212
  background-color: var(--widget-bg-primary-hover);
213
213
  }
214
214
 
215
+ /* Fully-filled warning style */
216
+ .pnx-btn-fullwarn {
217
+ background-color: var(--widget-bg-warn);
218
+ color: var(--widget-font-warn);
219
+ border: none;
220
+ }
221
+
222
+ .pnx-btn-fullwarn:not(:disabled):hover {
223
+ background-color: var(--widget-bg-warn);
224
+ }
225
+
215
226
  /* Outline style */
216
227
  .pnx-btn-outline {
217
228
  border: 1px solid var(--widget-border-btn);
@@ -0,0 +1,169 @@
1
+ import { LitElement, html, css, nothing } from "lit";
2
+ import { fa } from "../../utils/widgets";
3
+ import { faTags } from "@fortawesome/free-solid-svg-icons/faTags";
4
+ import { faTriangleExclamation } from "@fortawesome/free-solid-svg-icons/faTriangleExclamation";
5
+ import { hasAnnotations } from "../../utils/picture";
6
+ import { classMap } from "lit/directives/class-map.js";
7
+ import { panel } from "../styles";
8
+
9
+ const DISABLE_ANNOTATIONS_PARAM = "pnx-disable-annotations";
10
+
11
+ /**
12
+ * AnnotationsSwitch component allows to switch on/off pictures annotations.
13
+ * @class Panoramax.components.ui.AnnotationsSwitch
14
+ * @element pnx-annotations-switch
15
+ * @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
16
+ * @example
17
+ * ```html
18
+ * <pnx-annotations-switch ._parent=${viewer} />
19
+ * ```
20
+ */
21
+ export default class AnnotationsSwitch extends LitElement {
22
+ /** @private */
23
+ static styles = [ panel, css`
24
+ .pnx-panel {
25
+ padding: 5px;
26
+ margin-top: 5px;
27
+ width: max-content;
28
+ min-width: unset;
29
+ }
30
+
31
+ /* Custom button look */
32
+ pnx-button::part(btn) {
33
+ border-radius: 8px;
34
+ height: 26px;
35
+ width: 26px;
36
+ }
37
+
38
+ pnx-button[size="xl"]::part(btn) {
39
+ border-radius: 8px;
40
+ height: 38px;
41
+ width: 38px;
42
+ }
43
+
44
+ pnx-button[active]::part(btn),
45
+ pnx-button[active]:hover::part(btn) {
46
+ background-color: var(--widget-bg-active) !important;
47
+ border-color: var(--widget-bg-active) !important;
48
+ color: var(--widget-font-active) !important;
49
+ }
50
+
51
+ /* No-annotations badge */
52
+ .pnx-annotations-switch-empty {
53
+ position: absolute;
54
+ top: 1px;
55
+ right: 1px;
56
+ color: var(--orange);
57
+ height: 8px;
58
+ }
59
+
60
+ pnx-button[size="xl"] .pnx-annotations-switch-empty {
61
+ top: 2px;
62
+ right: 2px;
63
+ height: 12px;
64
+ }
65
+
66
+ pnx-button[active] .pnx-annotations-switch-empty {
67
+ color: var(--yellow);
68
+ }
69
+ `];
70
+
71
+ /**
72
+ * Component properties.
73
+ * @memberof Panoramax.components.ui.AnnotationsSwitch#
74
+ * @type {Object}
75
+ * @property {string} [size=xl] Button size (md, xl)
76
+ */
77
+ /** @private */
78
+ static properties = {
79
+ size: {type: String},
80
+ _annotationsToggled: {state: true},
81
+ _warnNoAnnot: {state: true},
82
+ _warnNoAnnotTooltip: {state: true},
83
+ };
84
+
85
+ constructor() {
86
+ super();
87
+ this._annotationsToggled = false;
88
+ this._warnNoAnnot = false;
89
+ this._warnNoAnnotTooltip = false;
90
+ this.size = "xl";
91
+ }
92
+
93
+ /** @private */
94
+ connectedCallback() {
95
+ super.connectedCallback();
96
+
97
+ // Check if annotations have been explicitly disabled
98
+ const annotsDisabled = localStorage.getItem(DISABLE_ANNOTATIONS_PARAM) === "true";
99
+
100
+ this._parent.onceReady().then(() => {
101
+ this._parent.psv.addEventListener("annotations-toggled", e => {
102
+ this._annotationsToggled = e.detail.visible;
103
+ this._persistAnnotationsLocalStorage(this._annotationsToggled);
104
+ });
105
+
106
+ this._warnNoAnnot = !hasAnnotations(this._parent.psv.getPictureMetadata());
107
+ this._parent.psv.addEventListener("picture-loaded", () => {
108
+ this._warnNoAnnot = !hasAnnotations(this._parent.psv.getPictureMetadata());
109
+ });
110
+ });
111
+
112
+ this._parent.onceFirstPicLoaded().then(() => {
113
+ this._annotationsToggled = this._parent.psv.areAnnotationsVisible() || false;
114
+
115
+ if(!this._annotationsToggled && !annotsDisabled) {
116
+ this._parent.psv.toggleAllAnnotations(true);
117
+ this._annotationsToggled = true;
118
+ }
119
+ this._persistAnnotationsLocalStorage(this._annotationsToggled);
120
+ });
121
+ }
122
+
123
+ /** @private */
124
+ _persistAnnotationsLocalStorage(isAnnotToggled) {
125
+ localStorage.setItem(DISABLE_ANNOTATIONS_PARAM, isAnnotToggled ? "false": "true");
126
+ }
127
+
128
+ /** @private */
129
+ _onClick() {
130
+ if(!this._annotationsToggled && this._warnNoAnnot) {
131
+ this._warnNoAnnotTooltip = true;
132
+ setTimeout(() => this._warnNoAnnotTooltip = false, 2000);
133
+ }
134
+ this._persistAnnotationsLocalStorage(!this._annotationsToggled);
135
+ this._parent.psv.toggleAllAnnotations(!this._annotationsToggled);
136
+ }
137
+
138
+ /** @private */
139
+ render() {
140
+ /* eslint-disable indent */
141
+ const panelClasses = {
142
+ "pnx-panel": true,
143
+ "pnx-hidden": !this._warnNoAnnotTooltip,
144
+ };
145
+
146
+ return html`
147
+ <pnx-button
148
+ kind="superflat"
149
+ size=${this.size}
150
+ style="vertical-align: middle"
151
+ class="pnx-print-hidden"
152
+ title=${this._annotationsToggled ? this._parent._t?.pnx.semantics_hide_annotations : this._parent._t?.pnx.semantics_show_annotations}
153
+ active=${this._annotationsToggled ? "" : nothing}
154
+ @click=${this._onClick}
155
+ >
156
+ ${fa(faTags, { styles: { "height": "20px" }})}
157
+ ${this._warnNoAnnot ? fa(faTriangleExclamation, { classes: "pnx-annotations-switch-empty" }) : nothing}
158
+ </pnx-button>
159
+ <div
160
+ class=${classMap(panelClasses)}
161
+ @click=${() => this._warnNoAnnotTooltip = false}
162
+ >
163
+ ${this._parent._t?.pnx.semantics_zero_annotations}
164
+ </div>
165
+ `;
166
+ }
167
+ }
168
+
169
+ customElements.define("pnx-annotations-switch", AnnotationsSwitch);
@@ -23,7 +23,7 @@ export default class Button extends LitElement {
23
23
  * @memberof Panoramax.components.ui.Button#
24
24
  * @type {Object}
25
25
  * @property {boolean} [active=false] Whether the button is in an active state.
26
- * @property {string} [kind=full] The style variation of the button (full, outline, flat, superflat, inline, superinline)
26
+ * @property {string} [kind=full] The style variation of the button (full, fullwarn, outline, flat, superflat, inline, superinline)
27
27
  * @property {string} [size=md] The size of the button (sm, md, l, xl, xxl)
28
28
  * @property {boolean} [disabled=false] Whether the button is disabled.
29
29
  * @property {string} [type] The button type (e.g., 'submit').
@@ -38,7 +38,7 @@ export default class CopyButton extends LitElement {
38
38
  * @type {Object}
39
39
  * @property {string} [text] Text to copy in clipboard on click (use either this parameter or input, not both)
40
40
  * @property {input} [input] ID of a HTML input field to copy content from in clipboard (use either this parameter or text, not both)
41
- * @property {string} [kind=full] The style variation of the button (full, outline, flat, superflat, inline, superinline)
41
+ * @property {string} [kind=full] The style variation of the button (full, fullwarn, 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
44
  * @property {string} [title] Tooltip text displayed when hovering over the button
@@ -31,7 +31,7 @@ export default class LinkButton extends LitElement {
31
31
  * @property {string} [title] Tooltip text displayed when hovering over the button
32
32
  * @property {string} [download] Indicates if the linked resource should be downloaded
33
33
  * @property {string} [class] Custom CSS class for additional styling
34
- * @property {string} [kind=full] The style variation of the button (full, outline, flat, superflat)
34
+ * @property {string} [kind=full] The style variation of the button (full, fullwarn, outline, flat, superflat)
35
35
  * @property {string} [size=md] The size of the button (sm, md, l, xl, xxl)
36
36
  */
37
37
  static properties = {
@@ -1,3 +1,7 @@
1
+ .maplibregl-map.pnx-map {
2
+ background: white;
3
+ }
4
+
1
5
  /* Picture thumbnail on map */
2
6
  .maplibregl-popup-content {
3
7
  padding: 5px !important;
@@ -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
  }