@panoramax/web-viewer 5.2.0-develop-6a40c2ee → 5.2.0-develop-434137e7

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.
@@ -7,6 +7,7 @@
7
7
  --red-transparent: rgba(247, 0, 0, 0.3);
8
8
  --red-pale: #ff726f;
9
9
  --grey: #f5f5f5;
10
+ --grey-very-pale: #e6e6e6;
10
11
  --grey-pale: #cfd2cf;
11
12
  --grey-semi-dark: #808080;
12
13
  --grey-dark: #3e3e3e;
@@ -9,7 +9,7 @@ import BasicStyles from "./Basic.css" with { type: "css" };
9
9
  document.adoptedStyleSheets.push(AtkinsonStyles);
10
10
  document.adoptedStyleSheets.push(BasicStyles);
11
11
 
12
- const __COMMIT_HASH__ = "6a40c2e";
12
+ const __COMMIT_HASH__ = "434137e";
13
13
  const __PACKAGE_VERSION__ = "5.2.0";
14
14
  const __PACKAGE_ISSUES_URL__ = "https://gitlab.com/panoramax/clients/web-viewer/-/work_items";
15
15
 
@@ -3,7 +3,7 @@ import URLHandler from "../../utils/URLHandler.js";
3
3
  import Basic from "./Basic.js";
4
4
  import Photo, { PSV_ANIM_DURATION, PSV_DEFAULT_ZOOM } from "../ui/Photo.js";
5
5
  import { createWebComp } from "../../utils/widgets.js";
6
- import { DISABLE_ANNOTATIONS_PARAM, isInIframe, isNullId } from "../../utils/utils.js";
6
+ import { DISABLE_ANNOTATIONS_PARAM, isInIframe } from "../../utils/utils.js";
7
7
  import { default as InitParameters, alterPSVState, alterPhotoViewerState } from "../../utils/InitParameters.js";
8
8
  import PresetManager from "../../utils/PresetsManager.js";
9
9
  import PhotoViewerStyles from "./PhotoViewer.css" with { type: "css" };
@@ -193,14 +193,12 @@ export default class PhotoViewer extends Basic {
193
193
  this.bottomDrawer = createWebComp("pnx-bottom-drawer", {
194
194
  slot: "bottom",
195
195
  _parent: this,
196
- class: this._initParams.getParentPostInit().willLoadPicture ? undefined: "pnx-hidden",
196
+ openness: this._initParams.getParentPostInit().willLoadPicture ? undefined: "hidden",
197
197
  });
198
- this.bottomDrawer.appendChild(this.legend);
198
+ if(this._initParams.getParentPostInit().willLoadPicture) {
199
+ this.bottomDrawer.appendChild(this.legend);
200
+ }
199
201
  this.grid.appendChild(this.bottomDrawer);
200
- this.addEventListener("select", e => {
201
- if(isNullId(e.detail.picId)) { this.bottomDrawer.classList.add("pnx-hidden"); }
202
- else { this.bottomDrawer.classList.remove("pnx-hidden"); }
203
- });
204
202
  }
205
203
  }
206
204
  }
@@ -534,6 +532,44 @@ export default class PhotoViewer extends Basic {
534
532
  });
535
533
  }
536
534
 
535
+ /**
536
+ * Check bottom drawer visibility
537
+ * @memberof Panoramax.components.core.PhotoViewer#
538
+ * @returns {boolean} True if visible (even partially)
539
+ */
540
+ isBottomDrawerVisible() {
541
+ return this.bottomDrawer.getAttribute("openness") !== "hidden";
542
+ }
543
+
544
+ /**
545
+ * Change bottom drawer visibility and content
546
+ * @memberof Panoramax.components.core.PhotoViewer#
547
+ * @param {boolean} visible True to make it appear
548
+ * @param {string|Element[]} [content] The new bottom drawer content
549
+ * @param {object} [options] [Options to pass to Bottom Drawer](#Panoramax.components.layout.BottomDrawer)
550
+ * @param {boolean} [options.autoclose=false] Should the drawer be closed automatically when dragged down?
551
+ * @param {boolean} [options.autoadjust=false] Should the drawer adjust to content size?
552
+ */
553
+ setBottomDrawer(visible, content = null, options = null) {
554
+ if(!this.bottomDrawer) { return; }
555
+ if(!visible) { this.bottomDrawer.setAttribute("openness", "hidden"); }
556
+
557
+ if(options?.autoclose) { this.bottomDrawer.setAttribute("autoclose", ""); }
558
+ else { this.bottomDrawer.removeAttribute("autoclose", ""); }
559
+ if(options?.autoadjust) { this.bottomDrawer.setAttribute("autoadjust", ""); }
560
+ else { this.bottomDrawer.removeAttribute("autoadjust", ""); }
561
+
562
+ this.bottomDrawer.clearContent();
563
+
564
+ if(content) {
565
+ if(typeof content === "string") { this.bottomDrawer.setContent(content); }
566
+ else if(Array.isArray(content)) { content.forEach(c => this.bottomDrawer.appendChild(c)); }
567
+ else if(typeof content === "object") { this.bottomDrawer.appendChild(content); }
568
+ }
569
+
570
+ if(visible) { this.bottomDrawer.setAttribute("openness", options?.autoadjust ? "opened" : "half-opened"); }
571
+ }
572
+
537
573
  /**
538
574
  * Change full-page popup visibility and content
539
575
  * @memberof Panoramax.components.core.PhotoViewer#
@@ -209,14 +209,9 @@ export default class Viewer extends PhotoViewer {
209
209
  this.bottomDrawer = createWebComp("pnx-bottom-drawer", {
210
210
  slot: "bottom",
211
211
  _parent: this,
212
- class: this._initParams.getParentPostInit().willLoadPicture ? undefined: "pnx-hidden",
212
+ openness: "hidden",
213
213
  });
214
- this.bottomDrawer.appendChild(this.legend);
215
214
  this.grid.appendChild(this.bottomDrawer);
216
- this.addEventListener("select", e => {
217
- if(isNullId(e.detail.picId)) { this.bottomDrawer.classList.add("pnx-hidden"); }
218
- else { this.bottomDrawer.classList.remove("pnx-hidden"); }
219
- });
220
215
  }
221
216
 
222
217
  if(!isInIframe()) {
@@ -347,6 +342,13 @@ export default class Viewer extends PhotoViewer {
347
342
  this.mini.removeAttribute("collapsed");
348
343
  }
349
344
 
345
+ // Switch bottom drawer based on next picture
346
+ if(!isNullId(value)) {
347
+ this.setBottomDrawer(true, this.legend);
348
+ } else if(this.legend.parentNode === this.bottomDrawer) {
349
+ this.setBottomDrawer(false);
350
+ }
351
+
350
352
  // Unselect -> show map wide instead
351
353
  if(isNullId(value)) {
352
354
  if(this.map && this.isMapWide()) { this.mini.classList.add("pnx-hidden"); }
@@ -362,19 +364,11 @@ export default class Viewer extends PhotoViewer {
362
364
  ) {
363
365
  this.mini.classList.remove("pnx-hidden");
364
366
  this.mini.removeAttribute("collapsed");
365
- if(this.bottomDrawer?.getAttribute?.("openness") === "closed") {
366
- this.bottomDrawer.setAttribute("openness", "half-opened");
367
- }
368
367
  }
369
368
  // Select after none selected -> show pic wide
370
369
  else {
371
370
  const postLoad = () => {
372
371
  this.mini.classList.remove("pnx-hidden");
373
- if(isNullId(old)) {
374
- if(this.bottomDrawer?.getAttribute?.("openness") === "closed") {
375
- this.bottomDrawer.setAttribute("openness", "half-opened");
376
- }
377
- }
378
372
  };
379
373
  this.psv.addEventListener("picture-loaded", postLoad, {once: true});
380
374
  this.psv.addEventListener("picture-failed", () => {
@@ -727,6 +721,9 @@ export default class Viewer extends PhotoViewer {
727
721
  this.psv.autoSize();
728
722
  this.psv.forceRefresh();
729
723
  this.legend?.setAttribute?.("focus", this.focus);
724
+ if(!isNullId(this.picture) && !this.legend.parentNode === this.bottomDrawer) {
725
+ this.setBottomDrawer(true, this.legend);
726
+ }
730
727
 
731
728
  if(!skipEvent) {
732
729
  /**
@@ -1,8 +1,9 @@
1
1
  import { LitElement, css, html } from "lit";
2
2
  import { classMap } from "lit/directives/class-map.js";
3
- import { onceParentAvailable } from "../../utils/widgets.js";
3
+ import { fa, onceParentAvailable } from "../../utils/widgets.js";
4
+ import { faTimes } from "@fortawesome/free-solid-svg-icons";
4
5
 
5
- const OPENNESS_Y_PCT = { "opened": 0, "half-opened": 0.7, "closed": 1 };
6
+ const OPENNESS_Y_PCT = { "opened": 0, "half-opened": 0.7, "closed": 1, "hidden": 1 };
6
7
 
7
8
  /**
8
9
  * BottomDrawer layout offers a mobile-like drawer menu, coming from bottom of the screen.
@@ -48,6 +49,12 @@ export default class BottomDrawer extends LitElement {
48
49
  .drawer.half-opened { transform: translateY(70%); }
49
50
  .drawer.half-opened .content { overflow-y: hidden; }
50
51
  .drawer.opened { transform: translateY(0%); }
52
+ .drawer.hidden { display: none; }
53
+
54
+ .drawer.autoadjust.opened {
55
+ transform: unset;
56
+ height: max-content !important;
57
+ }
51
58
 
52
59
  .handle {
53
60
  height: 30px;
@@ -57,6 +64,12 @@ export default class BottomDrawer extends LitElement {
57
64
  cursor: pointer;
58
65
  }
59
66
 
67
+ .handle.autoclose {
68
+ align-items: end;
69
+ justify-content: end;
70
+ padding: 0 15px;
71
+ }
72
+
60
73
  .handle-bar {
61
74
  width: 40px;
62
75
  height: 5px;
@@ -74,7 +87,9 @@ export default class BottomDrawer extends LitElement {
74
87
  * Component properties.
75
88
  * @memberof Panoramax.components.layout.BottomDrawer#
76
89
  * @type {Object}
77
- * @property {string} [openness=half-opened] How much the drawer should be opened (closed, half-opened, opened)
90
+ * @property {string} [openness=half-opened] How much the drawer should be opened (hidden, closed, half-opened, opened)
91
+ * @property {boolean} [autoclose=false] Set true if the drawer should close when dragged down.
92
+ * @property {boolean} [autoadjust=false] Automatically adjust to defined content. If not, size is based on fixed value (fully opened, 30% opened).
78
93
  */
79
94
  static properties = {
80
95
  _fingerY: {state: true},
@@ -83,12 +98,16 @@ export default class BottomDrawer extends LitElement {
83
98
  _isDragging: {state: true},
84
99
  _drawerHeight: {state: true},
85
100
  openness: {type: String, reflect: true},
101
+ autoclose: {type: Boolean},
102
+ autoadjust: {type: Boolean},
86
103
  };
87
104
 
88
105
  constructor() {
89
106
  super();
90
107
  this._isDragging = false;
91
108
  this.openness = "half-opened";
109
+ this.autoclose = false;
110
+ this.autoadjust = false;
92
111
  }
93
112
 
94
113
  /** @private */
@@ -106,8 +125,13 @@ export default class BottomDrawer extends LitElement {
106
125
  drawer.style.maxHeight = `${this._drawerHeight}px`;
107
126
 
108
127
  onceParentAvailable(this).then(() => this._parent.onceReady()).then(() => {
109
- this._parent.map?.addEventListener("click", () => this.openness = "closed");
110
- this._parent.psv?.addEventListener("click", () => this.openness = "closed");
128
+ const onParentClick = () => {
129
+ if(!["hidden", "closed", null].includes(this.openness)) {
130
+ this.openness = this.autoclose ? "hidden" : "closed";
131
+ }
132
+ };
133
+ this._parent.map?.addEventListener("click", onParentClick);
134
+ this._parent.psv?.addEventListener("click", onParentClick);
111
135
  });
112
136
  }
113
137
 
@@ -116,9 +140,17 @@ export default class BottomDrawer extends LitElement {
116
140
  super.attributeChangedCallback(name, old, value);
117
141
 
118
142
  if(name === "openness") {
143
+ // Send menu open/close event
144
+ if(["hidden", "closed", null].includes(old) && value?.includes("open")) {
145
+ this._parent?.dispatchEvent(new CustomEvent("menu-opened", { detail: { menu: this }}));
146
+ }
147
+ else if(old?.includes("open") && ["hidden", "closed", null].includes(value)) {
148
+ this._parent?.dispatchEvent(new CustomEvent("menu-closed", { detail: { menu: this }}));
149
+ }
150
+
119
151
  // If not fully opened, force content scroll back to top
120
152
  if(value !== "opened") {
121
- const content = this.shadowRoot.querySelector(".content");
153
+ const content = this.shadowRoot?.querySelector(".content");
122
154
  if(content) { content.scrollTop = 0; }
123
155
  }
124
156
 
@@ -134,6 +166,27 @@ export default class BottomDrawer extends LitElement {
134
166
  this._cleanupTouchListeners();
135
167
  }
136
168
 
169
+ /**
170
+ * Set child content from string.
171
+ * @param {string} newContent The new HTML string content
172
+ * @memberof Panoramax.components.layout.BottomDrawer#
173
+ */
174
+ setContent(newContent) {
175
+ const content = this.shadowRoot?.querySelector(".content slot");
176
+ if(content) { content.innerHTML = newContent; }
177
+ }
178
+
179
+ /**
180
+ * Removes all slotted child elements.
181
+ * @memberof Panoramax.components.layout.BottomDrawer#
182
+ */
183
+ clearContent() {
184
+ const content = this.shadowRoot?.querySelector(".content slot")?.assignedElements();
185
+ if(content && content.length > 0) {
186
+ content.forEach(c => c.parentNode.removeChild(c));
187
+ }
188
+ }
189
+
137
190
  /** @private */
138
191
  _getDrawer() {
139
192
  return this.shadowRoot?.querySelector(".drawer");
@@ -141,8 +194,8 @@ export default class BottomDrawer extends LitElement {
141
194
 
142
195
  /** @private */
143
196
  _onHandleClick() {
144
- if(this.openness === "opened") { this.openness = "closed"; }
145
- else if(this.openness === "half-opened") { this.openness = "opened"; }
197
+ if(this.openness === "opened") { this.openness = this.autoclose ? "hidden" : "closed"; }
198
+ else if(this.openness === "half-opened") { this.openness = this.autoclose ? "hidden" : "opened"; }
146
199
  else if(this.openness === "closed") { this.openness = "half-opened"; }
147
200
  }
148
201
 
@@ -220,7 +273,7 @@ export default class BottomDrawer extends LitElement {
220
273
  else { this.openness = "opened"; }
221
274
  }
222
275
  // Gesture goes down
223
- else { this.openness = "closed"; }
276
+ else { this.openness = this.autoclose ? "hidden" : "closed"; }
224
277
 
225
278
  this._drawerY = null;
226
279
  y = Math.max(0, Math.min(OPENNESS_Y_PCT[this.openness] * this._drawerHeight, this._drawerHeight - 30));
@@ -235,6 +288,7 @@ export default class BottomDrawer extends LitElement {
235
288
  "drawer": true,
236
289
  [this.openness]: true,
237
290
  "dragging": this._isDragging,
291
+ "autoadjust": this.autoadjust,
238
292
  };
239
293
 
240
294
  return html`
@@ -244,8 +298,15 @@ export default class BottomDrawer extends LitElement {
244
298
  @touchmove="${this._onTouchMove}"
245
299
  @touchend="${this._onTouchEnd}"
246
300
  >
247
- <div class="handle" @click=${this._onHandleClick}>
248
- <div class="handle-bar"></div>
301
+ <div
302
+ class=${classMap({"handle": true, "autoclose": this.autoclose})}
303
+ @click=${this._onHandleClick}
304
+ >
305
+ ${this.autoclose ? html`
306
+ ${fa(faTimes, { styles: { "height": "20px" }})}
307
+ ` : html`
308
+ <div class="handle-bar"></div>
309
+ `}
249
310
  </div>
250
311
  <div class="content">
251
312
  <slot></slot>
@@ -2,6 +2,8 @@ import { LitElement, css, html } from "lit";
2
2
  import { onceParentAvailable } from "../../utils/widgets.js";
3
3
  const BgStreets = new URL("../../img/bg_streets.jpg", import.meta.url).href;
4
4
  const BgAerial = new URL("../../img/bg_aerial.jpg", import.meta.url).href;
5
+ const BgMore = new URL("../../img/bg_more.png", import.meta.url).href;
6
+ import { BASEMAPS_DEFAULT_IDS } from "../../utils/MapStyleComposer.js";
5
7
 
6
8
  /**
7
9
  * Map Background menu allows user to select background.
@@ -23,23 +25,48 @@ export default class MapBackground extends LitElement {
23
25
  gap: 10px;
24
26
  }
25
27
  input { display: none; }
26
- label { cursor: pointer; }
28
+ label {
29
+ cursor: pointer;
30
+ display: flex;
31
+ flex-direction: column;
32
+ align-items: center;
33
+ gap: 3px;
34
+ font-size: 0.8rem;
35
+ color: var(--grey-dark);
36
+ }
27
37
 
28
38
  img {
29
39
  width: 40px;
30
40
  border-radius: 5px;
31
41
  vertical-align: middle;
32
- margin-right: 5px;
33
42
  border: 2px solid var(--widget-bg);
34
43
  }
35
44
 
36
- img.sm {
37
- width: 32px;
45
+ img.sm { width: 32px; }
46
+
47
+ @media screen and (max-width: 576px) {
48
+ label { font-size: 0.6rem; }
49
+ img { width: 32px; }
38
50
  }
39
51
 
40
52
  input:checked + label img {
41
53
  outline: 3px solid var(--widget-border-btn);
42
54
  }
55
+
56
+ input:disabled + label {
57
+ opacity: 0.4;
58
+ cursor: not-allowed;
59
+ }
60
+
61
+ .select-as-radio {
62
+ position: relative;
63
+ }
64
+
65
+ .select-as-radio select {
66
+ opacity: 0;
67
+ position: absolute;
68
+ inset: 0;
69
+ }
43
70
  ` ];
44
71
 
45
72
  /**
@@ -73,12 +100,13 @@ export default class MapBackground extends LitElement {
73
100
  /** @private */
74
101
  _onBgSelect(e) {
75
102
  this.bg = e.target.value;
103
+ this.shadowRoot.querySelector("#pnx-map-bg-more").value = null;
76
104
  this._parent?.mapStyleComposer?.switchBasemap(this.bg);
77
105
  }
78
106
 
79
107
  /** @private */
80
108
  render() {
81
- return html`
109
+ const parts = [html`
82
110
  <input
83
111
  type="radio"
84
112
  id="bg-streets"
@@ -91,19 +119,57 @@ export default class MapBackground extends LitElement {
91
119
  <img src=${BgStreets} class=${this.size} alt="" />
92
120
  ${this._parent?._t.pnx.map_background_streets}
93
121
  </label>
94
- <input
95
- type="radio"
96
- id="bg-aerial"
97
- name="bg"
98
- value="aerial"
99
- @change=${this._onBgSelect}
100
- .checked=${this.bg === "aerial"}
101
- />
102
- <label for="bg-aerial">
103
- <img src=${BgAerial} class=${this.size} alt="" />
104
- ${this._parent?._t.pnx.map_background_aerial}
105
- </label>
106
- `;
122
+ `];
123
+
124
+ if(this._parent.mapStyleComposer.hasStreetsAerialBasemaps()) {
125
+ parts.push(html`
126
+ <input
127
+ type="radio"
128
+ id="bg-aerial"
129
+ name="bg"
130
+ value="aerial"
131
+ @change=${this._onBgSelect}
132
+ .checked=${this.bg === "aerial"}
133
+ />
134
+ <label for="bg-aerial">
135
+ <img src=${BgAerial} class=${this.size} alt="" />
136
+ ${this._parent?._t.pnx.map_background_aerial}
137
+ </label>
138
+ `);
139
+ }
140
+
141
+ if(this._parent.mapStyleComposer.hasComplementaryBasemaps()) {
142
+ const layers = Object
143
+ .entries(this._parent.mapStyleComposer.layerRanges.basemaps)
144
+ .filter(([bid,]) => !BASEMAPS_DEFAULT_IDS.includes(bid))
145
+ .map(([bid, b]) => html`
146
+ <option value="${bid}" .selected=${this._parent.mapStyleComposer.basemap === bid}>
147
+ ${b.layers[0]?.metadata?.name || bid}
148
+ </option>
149
+ `);
150
+
151
+ parts.push(html`
152
+ <div class="select-as-radio">
153
+ <input
154
+ type="radio"
155
+ id="bg-more"
156
+ name="bg"
157
+ value="more"
158
+ @change=${this._onBgSelect}
159
+ .checked=${!["aerial", "streets"].includes(this.bg)}
160
+ />
161
+ <label for="bg-more">
162
+ <img src=${BgMore} class=${this.size} alt="" />
163
+ ${this._parent?._t.pnx.map_background_more}
164
+ </label>
165
+ <select id="pnx-map-bg-more" @change=${this._onBgSelect}>
166
+ ${layers}
167
+ </select>
168
+ </div>
169
+ `);
170
+ }
171
+
172
+ return parts;
107
173
  }
108
174
  }
109
175