@panoramax/web-viewer 3.2.3-develop-97f87faf → 3.2.3-develop-f4edbdd3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@panoramax/web-viewer",
3
- "version": "3.2.3-develop-97f87faf",
3
+ "version": "3.2.3-develop-f4edbdd3",
4
4
  "description": "Panoramax web viewer for geolocated pictures",
5
5
  "main": "build/index.js",
6
6
  "author": "Panoramax team",
@@ -266,7 +266,12 @@ export default class Viewer extends PhotoViewer {
266
266
  // Select after none selected -> show pic wide
267
267
  else {
268
268
  this.mini.classList.remove("pnx-hidden");
269
- if(isNullId(old)) { this._setFocus("pic"); }
269
+ if(isNullId(old)) {
270
+ this._setFocus("pic");
271
+ if(this.bottomDrawer?.getAttribute?.("openness") === "closed") {
272
+ this.bottomDrawer.setAttribute("openness", "half-opened");
273
+ }
274
+ }
270
275
  }
271
276
  }
272
277
 
@@ -526,13 +531,17 @@ export default class Viewer extends PhotoViewer {
526
531
  const mapFiltersMenu = querySelectorDeep("#pnx-map-filters-menu");
527
532
  const fMinDate = mapFiltersMenu?.shadowRoot.getElementById("pnx-filter-date-from");
528
533
  const fMaxDate = mapFiltersMenu?.shadowRoot.getElementById("pnx-filter-date-end");
529
- const fTypeFlat = mapFiltersMenu?.shadowRoot.getElementById("pnx-filter-type-flat");
530
- const fType360 = mapFiltersMenu?.shadowRoot.getElementById("pnx-filter-type-360");
534
+ const fTypes = mapFiltersMenu?.shadowRoot.querySelectorAll("input[name='pnx-filter-type']");
531
535
  const fMapTheme = querySelectorDeep("#pnx-map-theme");
532
536
 
533
537
  let type = "";
534
- if(fType360?.checked && !fTypeFlat?.checked) { type = "equirectangular"; }
535
- if(!fType360?.checked && fTypeFlat?.checked) { type = "flat"; }
538
+ for(let fTypeId = 0 ; fTypeId < fTypes.length; fTypeId++) {
539
+ const fType = fTypes[fTypeId];
540
+ if(fType.checked) {
541
+ type = fType.value;
542
+ break;
543
+ }
544
+ }
536
545
 
537
546
  let qualityscore = [];
538
547
  if(this.map?._hasQualityScore()) {
@@ -2,7 +2,6 @@ import { LitElement, html, css } from "lit";
2
2
  import { classMap } from "lit/directives/class-map.js";
3
3
 
4
4
  const OPENNESS_Y_PCT = { "opened": 0, "half-opened": 0.7, "closed": 1 };
5
- const OPENNESS_Y_PCT_RANGE = { "opened": [0, 0.4], "half-opened": [0.4, 0.8], "closed": [0.8, 1] };
6
5
 
7
6
  /**
8
7
  * BottomDrawer layout offers a mobile-like drawer menu, coming from bottom of the screen.
@@ -27,6 +26,7 @@ export default class BottomDrawer extends LitElement {
27
26
  width: 100%;
28
27
  z-index: 130;
29
28
  pointer-events: none;
29
+ touch-action: none;
30
30
  }
31
31
 
32
32
  .drawer {
@@ -38,7 +38,7 @@ export default class BottomDrawer extends LitElement {
38
38
  flex-direction: column;
39
39
  transition: transform 0.3s ease;
40
40
  will-change: transform;
41
- touch-action: none;
41
+ touch-action: auto;
42
42
  pointer-events: auto;
43
43
  }
44
44
 
@@ -53,9 +53,7 @@ export default class BottomDrawer extends LitElement {
53
53
  display: flex;
54
54
  align-items: center;
55
55
  justify-content: center;
56
- cursor: grab;
57
- touch-action: none;
58
- pointer-events: auto;
56
+ cursor: pointer;
59
57
  }
60
58
 
61
59
  .handle-bar {
@@ -95,23 +93,37 @@ export default class BottomDrawer extends LitElement {
95
93
  /** @private */
96
94
  firstUpdated() {
97
95
  super.firstUpdated();
96
+
98
97
  this._boundTouchMove = this._onTouchMove.bind(this);
99
98
  this._boundTouchEnd = this._onTouchEnd.bind(this);
100
99
 
101
100
  this._drawerHeight = window.innerHeight - 30;
102
- const drawer = this.shadowRoot?.querySelector(".drawer");
101
+
102
+ const drawer = this._getDrawer();
103
103
  if (!drawer) { return; }
104
104
  drawer.style.height = `${this._drawerHeight}px`;
105
105
  drawer.style.maxHeight = `${this._drawerHeight}px`;
106
+
107
+ this._parent?.onceReady().then(() => {
108
+ this._parent.map?.addEventListener("click", () => this.openness = "closed");
109
+ this._parent.psv?.addEventListener("click", () => this.openness = "closed");
110
+ });
106
111
  }
107
112
 
108
113
  /** @private */
109
114
  attributeChangedCallback(name, old, value) {
110
115
  super.attributeChangedCallback(name, old, value);
111
116
 
112
- if(name === "openness" && value !== "opened") {
113
- const content = this.shadowRoot.querySelector(".content");
114
- if(content) { content.scrollTop = 0; }
117
+ if(name === "openness") {
118
+ // If not fully opened, force content scroll back to top
119
+ if(value !== "opened") {
120
+ const content = this.shadowRoot.querySelector(".content");
121
+ if(content) { content.scrollTop = 0; }
122
+ }
123
+
124
+ // Remove hand-defined transform
125
+ const drawer = this._getDrawer();
126
+ if (drawer) { drawer.style.transform = null; }
115
127
  }
116
128
  }
117
129
 
@@ -121,10 +133,16 @@ export default class BottomDrawer extends LitElement {
121
133
  this._cleanupTouchListeners();
122
134
  }
123
135
 
136
+ /** @private */
137
+ _getDrawer() {
138
+ return this.shadowRoot?.querySelector(".drawer");
139
+ }
140
+
124
141
  /** @private */
125
142
  _onHandleClick() {
126
- if(this.openness === "opened" || this.openness === "closed") { this.openness = "half-opened"; }
143
+ if(this.openness === "opened") { this.openness = "closed"; }
127
144
  else if(this.openness === "half-opened") { this.openness = "opened"; }
145
+ else if(this.openness === "closed") { this.openness = "half-opened"; }
128
146
  }
129
147
 
130
148
  /** @private */
@@ -154,10 +172,19 @@ export default class BottomDrawer extends LitElement {
154
172
  }
155
173
 
156
174
  /** @private */
157
- _onTouchEnd() {
158
- if (!this._isDragging) return;
175
+ _onTouchEnd(e) {
176
+ // Touchend is also called when "clicking" on mobile
177
+ if ((!this._isDragging || Math.abs(this._deltaFingerY) < 30) && !e.target.closest(".handle")) { return; }
178
+ e.preventDefault();
179
+
159
180
  this._isDragging = false;
160
- this._updateDrawerTransform(this._drawerY + this._deltaFingerY, true);
181
+
182
+ if(this._deltaFingerY === 0 && this.openness === "closed") {
183
+ this.openness = "half-opened";
184
+ }
185
+ else {
186
+ this._updateDrawerTransform(this._drawerY + this._deltaFingerY, true);
187
+ }
161
188
 
162
189
  this._cleanupTouchListeners();
163
190
  this._startFingerY = null;
@@ -173,25 +200,32 @@ export default class BottomDrawer extends LitElement {
173
200
 
174
201
  /** @private */
175
202
  _updateDrawerTransform(y, snap = false) {
176
- const drawer = this.shadowRoot?.querySelector(".drawer");
203
+ const drawer = this._getDrawer();
177
204
  if (!drawer) { return; }
178
205
 
179
206
  y = Math.max(0, Math.min(y, this._drawerHeight - 30));
180
207
 
181
208
  // Snap to nearest static position
182
209
  if(snap) {
183
-
184
- const pct = y / (this._drawerHeight - 30);
185
- if(pct > OPENNESS_Y_PCT_RANGE.opened[0] && pct <= OPENNESS_Y_PCT_RANGE.opened[1]) { this.openness = "opened"; }
186
- if(pct > OPENNESS_Y_PCT_RANGE["half-opened"][0] && pct <= OPENNESS_Y_PCT_RANGE["half-opened"][1]) { this.openness = "half-opened"; }
187
- if(pct > OPENNESS_Y_PCT_RANGE.closed[0] && pct <= OPENNESS_Y_PCT_RANGE.closed[1]) { this.openness = "closed"; }
210
+ // Gesture goes up
211
+ if(this._deltaFingerY < 0) {
212
+ if(this.openness === "closed") {
213
+ // How much it goes up ?
214
+ if(Math.abs(this._deltaFingerY) > this._drawerHeight * (1-OPENNESS_Y_PCT["half-opened"])) {
215
+ this.openness = "opened";
216
+ }
217
+ else { this.openness = "half-opened"; }
218
+ }
219
+ else { this.openness = "opened"; }
220
+ }
221
+ // Gesture goes down
222
+ else { this.openness = "closed"; }
223
+
188
224
  this._drawerY = null;
189
- drawer.style.transform = null;
190
- }
191
- // Live drag
192
- else {
193
- drawer.style.transform = `translateY(${y}px)`;
225
+ y = Math.max(0, Math.min(OPENNESS_Y_PCT[this.openness] * this._drawerHeight, this._drawerHeight - 30));
194
226
  }
227
+
228
+ drawer.style.transform = `translateY(${y}px)`;
195
229
  }
196
230
 
197
231
  /** @private */
@@ -5,7 +5,6 @@ import { faSvg, titles } from "../styles";
5
5
  import { faImage } from "@fortawesome/free-solid-svg-icons/faImage";
6
6
  import { faCalendar } from "@fortawesome/free-solid-svg-icons/faCalendar";
7
7
  import { faArrowRight } from "@fortawesome/free-solid-svg-icons/faArrowRight";
8
- import { faPanorama } from "@fortawesome/free-solid-svg-icons/faPanorama";
9
8
  import { faMedal } from "@fortawesome/free-solid-svg-icons/faMedal";
10
9
  import { faInfoCircle } from "@fortawesome/free-solid-svg-icons/faInfoCircle";
11
10
  import { faUser } from "@fortawesome/free-solid-svg-icons/faUser";
@@ -90,10 +89,8 @@ export default class MapFilters extends LitElement {
90
89
  }
91
90
  .pnx-input-shortcuts button {
92
91
  border: none;
93
- height: 20px;
94
- line-height: 20px;
95
- font-size: 0.8em;
96
- padding: 0 8px;
92
+ font-size: 0.75em;
93
+ padding: 2px 6px;
97
94
  vertical-align: middle;
98
95
  background-color: var(--grey-pale);
99
96
  color: var(--black);
@@ -132,12 +129,12 @@ export default class MapFilters extends LitElement {
132
129
  border-top-right-radius: 8px;
133
130
  border-bottom-right-radius: 8px;
134
131
  }
135
- .pnx-checkbox-btns input[type="checkbox"] { display: none; }
136
- .pnx-checkbox-btns input[type="checkbox"]:checked + label {
132
+ .pnx-checkbox-btns input[type="radio"] { display: none; }
133
+ .pnx-checkbox-btns input[type="radio"]:checked + label {
137
134
  background-color: var(--widget-bg-active);
138
135
  color: var(--widget-font-active);
139
136
  }
140
- .pnx-checkbox-btns input[type="checkbox"]:checked + label:first-of-type {
137
+ .pnx-checkbox-btns input[type="radio"]:checked + label:first-of-type {
141
138
  border-right-color: white;
142
139
  }
143
140
 
@@ -158,8 +155,7 @@ export default class MapFilters extends LitElement {
158
155
  showZoomIn: {state: true},
159
156
  minDate: {state: true},
160
157
  maxDate: {state: true},
161
- typeFlat: {state: true},
162
- type360: {state: true},
158
+ type: {state: true},
163
159
  score: {state: true},
164
160
  user: {state: true},
165
161
  };
@@ -217,14 +213,9 @@ export default class MapFilters extends LitElement {
217
213
 
218
214
  this.score = e?.qualityscore?.length < 5 ? e.qualityscore.join(",") : "";
219
215
 
216
+ this.type = "";
220
217
  if(e?.pic_type && e.pic_type != "") {
221
- this.type360 = e.pic_type == "equirectangular";
222
- this.typeFlat = e.pic_type == "flat";
223
- }
224
-
225
- if(this.type360 === this.typeFlat) {
226
- this.type360 = false;
227
- this.typeFlat = false;
218
+ this.type = e.pic_type == "flat" ? "flat" : "equirectangular";
228
219
  }
229
220
  }
230
221
 
@@ -265,8 +256,7 @@ export default class MapFilters extends LitElement {
265
256
  this.shadowRoot.querySelector("#pnx-filter-search-user")?.reset();
266
257
  this.minDate = null;
267
258
  this.maxDate = null;
268
- this.typeFlat = null;
269
- this.type360 = null;
259
+ this.type = "";
270
260
  this.score = null;
271
261
  this.user = null;
272
262
  this._onFormChange();
@@ -302,6 +292,9 @@ export default class MapFilters extends LitElement {
302
292
  <button
303
293
  @click=${this._onShortcutClick("pnx-filter-date-from", new Date(new Date().setMonth(new Date().getMonth() - 1)).toISOString().split("T")[0])}
304
294
  >${this._parent?._t.pnx.filter_date_1month}</button>
295
+ <button
296
+ @click=${this._onShortcutClick("pnx-filter-date-from", new Date(new Date().setMonth(new Date().getMonth() - 6)).toISOString().split("T")[0])}
297
+ >${this._parent?._t.pnx.filter_date_6months}</button>
305
298
  <button
306
299
  @click=${this._onShortcutClick("pnx-filter-date-from", new Date(new Date().setFullYear(new Date().getFullYear() - 1)).toISOString().split("T")[0])}
307
300
  >${this._parent?._t.pnx.filter_date_1year}</button>
@@ -327,19 +320,29 @@ export default class MapFilters extends LitElement {
327
320
  <h4>${fa(faImage)} ${this._parent?._t.pnx.filter_picture}</h4>
328
321
  <div class="pnx-input-group pnx-checkbox-btns" style="justify-content: center;">
329
322
  <input
330
- type="checkbox"
323
+ type="radio"
324
+ id="pnx-filter-type-all"
325
+ name="pnx-filter-type"
326
+ value=""
327
+ .checked=${!this.type || this.type === ""}
328
+ />
329
+ <label for="pnx-filter-type-all">${this._parent?._t.pnx.picture_all}</label>
330
+ <input
331
+ type="radio"
331
332
  id="pnx-filter-type-flat"
332
- name="flat"
333
- .checked=${this.typeFlat}
333
+ name="pnx-filter-type"
334
+ value="flat"
335
+ .checked=${this.type === "flat"}
334
336
  />
335
- <label for="pnx-filter-type-flat">${fa(faImage)} ${this._parent?._t.pnx.picture_flat}</label>
337
+ <label for="pnx-filter-type-flat">${this._parent?._t.pnx.picture_flat}</label>
336
338
  <input
337
- type="checkbox"
339
+ type="radio"
338
340
  id="pnx-filter-type-360"
339
- name="360"
340
- .checked=${this.type360}
341
+ name="pnx-filter-type"
342
+ value="equirectangular"
343
+ .checked=${this.type === "equirectangular"}
341
344
  />
342
- <label for="pnx-filter-type-360">${fa(faPanorama)} ${this._parent?._t.pnx.picture_360}</label>
345
+ <label for="pnx-filter-type-360">${this._parent?._t.pnx.picture_360}</label>
343
346
  </div>
344
347
  </div>
345
348
 
@@ -72,9 +72,15 @@ export default class CopyButton extends LitElement {
72
72
  else if(this.text !== "") {
73
73
  textToCopy = this.text;
74
74
  }
75
- navigator.clipboard.writeText(textToCopy);
76
- this._active = true;
77
- setTimeout(() => this._active = false, 2000);
75
+
76
+ if(!navigator?.clipboard) {
77
+ alert("Clipboard is not available");
78
+ }
79
+ else {
80
+ navigator.clipboard.writeText(textToCopy);
81
+ this._active = true;
82
+ setTimeout(() => this._active = false, 2000);
83
+ }
78
84
  }
79
85
 
80
86
  /** @private */
@@ -235,9 +235,9 @@ export default class MapMore extends Map {
235
235
  }
236
236
 
237
237
  if(filters.pic_type && filters.pic_type !== "") {
238
- this._mapFilters.pic_type = filters.pic_type;
239
- mapSeqFilters.push(["==", ["get", "type"], filters.pic_type]);
240
- mapPicFilters.push(["==", ["get", "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]);
241
241
  }
242
242
  if(this._hasGridStats()) {
243
243
  reloadMapStyle = true;
@@ -98,6 +98,7 @@ export default class TogglableGroup extends LitElement {
98
98
  menu.style.right = null;
99
99
  menu.style.left = null;
100
100
  menu.style.overflowY = null;
101
+ menu.style.marginTop = null;
101
102
 
102
103
  // Get positions on screen
103
104
  const btnRect = btn.getBoundingClientRect();
@@ -130,7 +131,7 @@ export default class TogglableGroup extends LitElement {
130
131
  }
131
132
  // Overflows width = move to left
132
133
  else if(!fitsWidth) {
133
- menu.style.top = `${btnRect.bottom+btnMenuMargin}px`;
134
+ menu.style.marginTop = `${btnMenuMargin}px`;
134
135
  menu.style.right = `${window.innerWidth - btnRect.right}px`;
135
136
  }
136
137
  }
@@ -105,11 +105,13 @@
105
105
  "error_api_compatibility": "The pictures server is not compatible with this viewer version",
106
106
  "filter_date": "Date",
107
107
  "filter_date_1month": "1 month",
108
+ "filter_date_6months": "6 months",
108
109
  "filter_date_1year": "1 year",
109
110
  "filter_user": "User",
110
111
  "filter_user_mypics": "My pictures",
111
112
  "filter_picture": "Picture type",
112
- "filter_zoom_in": "Zoom-in to see this filter",
113
+ "filter_zoom_in": "Zoom-in the map to see this filter",
114
+ "picture_all": "All",
113
115
  "picture_flat": "Classic",
114
116
  "picture_360": "360°",
115
117
  "picture_flat_long": "Classic picture",
@@ -105,10 +105,12 @@
105
105
  "error_api_compatibility": "Le serveur de photos n'est pas compatible avec cette visionneuse",
106
106
  "filter_date": "Date",
107
107
  "filter_date_1month": "1 mois",
108
+ "filter_date_6months": "6 mois",
108
109
  "filter_date_1year": "1 an",
109
110
  "filter_user": "Utilisateur",
110
111
  "filter_user_mypics": "Mes photos",
111
112
  "filter_picture": "Type d'image",
113
+ "picture_all": "Tout",
112
114
  "picture_flat": "Classique",
113
115
  "picture_360": "360°",
114
116
  "picture_flat_long": "Photo classique",
@@ -120,7 +122,7 @@
120
122
  "qualityscore_doc_2": "Le score est affiché sous la forme d'une lettre A/B/C/D/E (A étant le meilleur, E le moins bon), et visualisable grâce à cette échelle :",
121
123
  "qualityscore_doc_3": "Il est calculé en fonction de la précision du GPS ainsi que la résolution de la photo. Un matériel professionnel aura une note de A, une caméra sportive 360° une note de B, et un smartphone une note de C à E.",
122
124
  "qualityscore_doc_link": "En savoir plus sur le Score de Qualité",
123
- "filter_zoom_in": "Zoomez plus pour voir ce filtre",
125
+ "filter_zoom_in": "Zoomez la carte pour voir ce filtre",
124
126
  "map_background": "Fond de carte",
125
127
  "map_background_aerial": "Satellite",
126
128
  "map_background_streets": "Plan",