@panoramax/web-viewer 3.2.3-develop-d7e5a16d → 3.2.3-develop-6257391e

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 (221) hide show
  1. package/.gitlab-ci.yml +3 -0
  2. package/CHANGELOG.md +19 -0
  3. package/CODE_OF_CONDUCT.md +1 -1
  4. package/README.md +1 -1
  5. package/build/editor.html +10 -1
  6. package/build/index.css +2 -2
  7. package/build/index.css.map +1 -1
  8. package/build/index.html +1 -1
  9. package/build/index.js +1682 -5
  10. package/build/index.js.map +1 -1
  11. package/build/map.html +1 -1
  12. package/build/viewer.html +10 -1
  13. package/build/widgets.html +1 -0
  14. package/config/jest/mocks.js +172 -0
  15. package/config/paths.js +1 -0
  16. package/config/webpack.config.js +26 -0
  17. package/docs/03_URL_settings.md +3 -11
  18. package/docs/05_Compatibility.md +59 -76
  19. package/docs/09_Develop.md +30 -11
  20. package/docs/90_Releases.md +2 -2
  21. package/docs/images/class_diagram.drawio +28 -28
  22. package/docs/images/class_diagram.jpg +0 -0
  23. package/docs/index.md +112 -0
  24. package/docs/reference/components/core/Basic.md +153 -0
  25. package/docs/reference/components/core/CoverageMap.md +160 -0
  26. package/docs/reference/components/core/Editor.md +172 -0
  27. package/docs/reference/components/core/Viewer.md +288 -0
  28. package/docs/reference/components/layout/CorneredGrid.md +29 -0
  29. package/docs/reference/components/layout/Mini.md +45 -0
  30. package/docs/reference/components/menus/MapBackground.md +32 -0
  31. package/docs/reference/components/menus/MapFilters.md +15 -0
  32. package/docs/reference/components/menus/MapLayers.md +15 -0
  33. package/docs/reference/components/menus/MapLegend.md +15 -0
  34. package/docs/reference/components/menus/PictureLegend.md +15 -0
  35. package/docs/reference/components/menus/PictureMetadata.md +15 -0
  36. package/docs/reference/components/menus/PlayerOptions.md +15 -0
  37. package/docs/reference/components/menus/QualityScoreDoc.md +15 -0
  38. package/docs/reference/components/menus/ReportForm.md +15 -0
  39. package/docs/reference/components/menus/ShareMenu.md +15 -0
  40. package/docs/reference/components/ui/Button.md +39 -0
  41. package/docs/reference/components/ui/ButtonGroup.md +36 -0
  42. package/docs/reference/components/ui/CopyButton.md +35 -0
  43. package/docs/reference/components/ui/Grade.md +32 -0
  44. package/docs/reference/components/ui/LinkButton.md +44 -0
  45. package/docs/reference/components/ui/Loader.md +54 -0
  46. package/docs/reference/components/ui/Map.md +214 -0
  47. package/docs/reference/components/ui/MapMore.md +233 -0
  48. package/docs/reference/components/ui/Photo.md +369 -0
  49. package/docs/reference/components/ui/Popup.md +56 -0
  50. package/docs/reference/components/ui/QualityScore.md +45 -0
  51. package/docs/reference/components/ui/SearchBar.md +63 -0
  52. package/docs/reference/components/ui/TogglableGroup.md +39 -0
  53. package/docs/reference/components/ui/widgets/GeoSearch.md +32 -0
  54. package/docs/reference/components/ui/widgets/Legend.md +32 -0
  55. package/docs/reference/components/ui/widgets/MapFiltersButton.md +33 -0
  56. package/docs/reference/components/ui/widgets/MapLayersButton.md +15 -0
  57. package/docs/reference/components/ui/widgets/Player.md +32 -0
  58. package/docs/reference/components/ui/widgets/Share.md +15 -0
  59. package/docs/reference/components/ui/widgets/Zoom.md +15 -0
  60. package/docs/reference/utils/API.md +311 -0
  61. package/docs/reference/utils/InitParameters.md +67 -0
  62. package/docs/reference/utils/URLHandler.md +102 -0
  63. package/docs/reference.md +73 -0
  64. package/docs/shortcuts.md +11 -0
  65. package/docs/tutorials/aerial_imagery.md +19 -0
  66. package/docs/tutorials/authentication.md +10 -0
  67. package/docs/tutorials/custom_widgets.md +64 -0
  68. package/docs/tutorials/map_style.md +27 -0
  69. package/docs/tutorials/migrate_v4.md +122 -0
  70. package/docs/tutorials/synced_coverage.md +42 -0
  71. package/mkdocs.yml +60 -5
  72. package/package.json +10 -7
  73. package/public/editor.html +21 -29
  74. package/public/index.html +3 -3
  75. package/public/map.html +19 -18
  76. package/public/viewer.html +18 -24
  77. package/public/widgets.html +265 -0
  78. package/scripts/doc.js +77 -0
  79. package/src/components/core/Basic.css +44 -0
  80. package/src/components/core/Basic.js +258 -0
  81. package/src/components/core/CoverageMap.css +9 -0
  82. package/src/components/core/CoverageMap.js +105 -0
  83. package/src/components/core/Editor.css +23 -0
  84. package/src/components/core/Editor.js +354 -0
  85. package/src/components/core/Viewer.css +109 -0
  86. package/src/components/core/Viewer.js +707 -0
  87. package/src/components/core/index.js +11 -0
  88. package/src/components/index.js +13 -0
  89. package/src/components/layout/CorneredGrid.js +109 -0
  90. package/src/components/layout/Mini.js +117 -0
  91. package/src/components/layout/index.js +7 -0
  92. package/src/components/menus/MapBackground.js +106 -0
  93. package/src/components/menus/MapFilters.js +386 -0
  94. package/src/components/menus/MapLayers.js +143 -0
  95. package/src/components/menus/MapLegend.js +54 -0
  96. package/src/components/menus/PictureLegend.js +103 -0
  97. package/src/components/menus/PictureMetadata.js +188 -0
  98. package/src/components/menus/PlayerOptions.js +96 -0
  99. package/src/components/menus/QualityScoreDoc.js +36 -0
  100. package/src/components/menus/ReportForm.js +133 -0
  101. package/src/components/menus/Share.js +228 -0
  102. package/src/components/menus/index.js +15 -0
  103. package/src/components/styles.js +365 -0
  104. package/src/components/ui/Button.js +75 -0
  105. package/src/components/ui/ButtonGroup.css +49 -0
  106. package/src/components/ui/ButtonGroup.js +68 -0
  107. package/src/components/ui/CopyButton.js +71 -0
  108. package/src/components/ui/Grade.js +54 -0
  109. package/src/components/ui/LinkButton.js +68 -0
  110. package/src/components/ui/Loader.js +188 -0
  111. package/src/components/{Map.css → ui/Map.css} +5 -17
  112. package/src/components/{Map.js → ui/Map.js} +114 -138
  113. package/src/components/ui/MapMore.js +324 -0
  114. package/src/components/{Photo.css → ui/Photo.css} +6 -6
  115. package/src/components/{Photo.js → ui/Photo.js} +279 -90
  116. package/src/components/ui/Popup.js +145 -0
  117. package/src/components/ui/QualityScore.js +152 -0
  118. package/src/components/ui/SearchBar.js +363 -0
  119. package/src/components/ui/TogglableGroup.js +162 -0
  120. package/src/components/ui/index.js +20 -0
  121. package/src/components/ui/widgets/GeoSearch.css +21 -0
  122. package/src/components/ui/widgets/GeoSearch.js +139 -0
  123. package/src/components/ui/widgets/Legend.js +51 -0
  124. package/src/components/ui/widgets/MapFiltersButton.js +104 -0
  125. package/src/components/ui/widgets/MapLayersButton.js +79 -0
  126. package/src/components/ui/widgets/Player.css +7 -0
  127. package/src/components/ui/widgets/Player.js +148 -0
  128. package/src/components/ui/widgets/Share.js +30 -0
  129. package/src/components/ui/widgets/Zoom.js +82 -0
  130. package/src/components/ui/widgets/index.js +12 -0
  131. package/src/img/panoramax.svg +13 -0
  132. package/src/img/switch_big.svg +20 -10
  133. package/src/index.js +6 -9
  134. package/src/translations/da.json +1 -1
  135. package/src/translations/de.json +1 -1
  136. package/src/translations/en.json +5 -3
  137. package/src/translations/eo.json +1 -1
  138. package/src/translations/es.json +1 -1
  139. package/src/translations/fr.json +5 -3
  140. package/src/translations/hu.json +1 -1
  141. package/src/translations/it.json +1 -1
  142. package/src/translations/ja.json +1 -1
  143. package/src/translations/nl.json +1 -1
  144. package/src/translations/pl.json +1 -1
  145. package/src/translations/sv.json +1 -1
  146. package/src/translations/zh_Hant.json +1 -1
  147. package/src/utils/API.js +74 -42
  148. package/src/utils/InitParameters.js +354 -0
  149. package/src/utils/URLHandler.js +364 -0
  150. package/src/utils/geocoder.js +116 -0
  151. package/src/utils/{I18n.js → i18n.js} +3 -1
  152. package/src/utils/index.js +11 -0
  153. package/src/utils/{Map.js → map.js} +216 -80
  154. package/src/utils/picture.js +433 -0
  155. package/src/utils/utils.js +315 -0
  156. package/src/utils/widgets.js +93 -0
  157. package/tests/components/ui/CopyButton.test.js +52 -0
  158. package/tests/components/ui/Loader.test.js +54 -0
  159. package/tests/components/{Map.test.js → ui/Map.test.js} +19 -61
  160. package/tests/components/{Photo.test.js → ui/Photo.test.js} +89 -57
  161. package/tests/components/ui/Popup.test.js +24 -0
  162. package/tests/components/ui/QualityScore.test.js +17 -0
  163. package/tests/components/ui/SearchBar.test.js +107 -0
  164. package/tests/components/ui/__snapshots__/CopyButton.test.js.snap +34 -0
  165. package/tests/components/ui/__snapshots__/Loader.test.js.snap +56 -0
  166. package/tests/components/{__snapshots__ → ui/__snapshots__}/Map.test.js.snap +11 -38
  167. package/tests/components/{__snapshots__ → ui/__snapshots__}/Photo.test.js.snap +57 -4
  168. package/tests/components/ui/__snapshots__/Popup.test.js.snap +29 -0
  169. package/tests/components/ui/__snapshots__/QualityScore.test.js.snap +11 -0
  170. package/tests/components/ui/__snapshots__/SearchBar.test.js.snap +65 -0
  171. package/tests/utils/API.test.js +1 -14
  172. package/tests/utils/InitParameters.test.js +485 -0
  173. package/tests/utils/URLHandler.test.js +350 -0
  174. package/tests/utils/__snapshots__/URLHandler.test.js.snap +21 -0
  175. package/tests/utils/__snapshots__/picture.test.js.snap +315 -0
  176. package/tests/utils/__snapshots__/widgets.test.js.snap +19 -0
  177. package/tests/utils/geocoder.test.js +37 -0
  178. package/tests/utils/{I18n.test.js → i18n.test.js} +1 -1
  179. package/tests/utils/map.test.js +67 -0
  180. package/tests/utils/picture.test.js +745 -0
  181. package/tests/utils/utils.test.js +288 -0
  182. package/tests/utils/widgets.test.js +90 -0
  183. package/docs/01_Start.md +0 -149
  184. package/docs/02_Usage.md +0 -831
  185. package/docs/04_Advanced_examples.md +0 -216
  186. package/src/Editor.css +0 -37
  187. package/src/Editor.js +0 -361
  188. package/src/StandaloneMap.js +0 -114
  189. package/src/Viewer.css +0 -203
  190. package/src/Viewer.js +0 -1246
  191. package/src/components/CoreView.css +0 -70
  192. package/src/components/CoreView.js +0 -175
  193. package/src/components/Loader.css +0 -74
  194. package/src/components/Loader.js +0 -120
  195. package/src/utils/Exif.js +0 -193
  196. package/src/utils/Utils.js +0 -631
  197. package/src/utils/Widgets.js +0 -562
  198. package/src/viewer/URLHash.js +0 -469
  199. package/src/viewer/Widgets.css +0 -880
  200. package/src/viewer/Widgets.js +0 -1470
  201. package/tests/Editor.test.js +0 -126
  202. package/tests/StandaloneMap.test.js +0 -45
  203. package/tests/Viewer.test.js +0 -366
  204. package/tests/__snapshots__/Editor.test.js.snap +0 -298
  205. package/tests/__snapshots__/StandaloneMap.test.js.snap +0 -30
  206. package/tests/__snapshots__/Viewer.test.js.snap +0 -195
  207. package/tests/components/CoreView.test.js +0 -92
  208. package/tests/components/Loader.test.js +0 -38
  209. package/tests/components/__snapshots__/Loader.test.js.snap +0 -15
  210. package/tests/utils/Exif.test.js +0 -124
  211. package/tests/utils/Map.test.js +0 -113
  212. package/tests/utils/Utils.test.js +0 -300
  213. package/tests/utils/Widgets.test.js +0 -107
  214. package/tests/utils/__snapshots__/Exif.test.js.snap +0 -43
  215. package/tests/utils/__snapshots__/Utils.test.js.snap +0 -41
  216. package/tests/utils/__snapshots__/Widgets.test.js.snap +0 -44
  217. package/tests/viewer/URLHash.test.js +0 -559
  218. package/tests/viewer/Widgets.test.js +0 -127
  219. package/tests/viewer/__snapshots__/URLHash.test.js.snap +0 -108
  220. package/tests/viewer/__snapshots__/Widgets.test.js.snap +0 -403
  221. /package/tests/utils/__snapshots__/{Map.test.js.snap → geocoder.test.js.snap} +0 -0
@@ -0,0 +1,707 @@
1
+ /* eslint-disable no-unused-vars */
2
+
3
+ import "./Viewer.css";
4
+ import { SYSTEM as PSSystem, DEFAULTS as PSDefaults } from "@photo-sphere-viewer/core";
5
+ import URLHandler from "../../utils/URLHandler";
6
+ import { linkMapAndPhoto } from "../../utils/map";
7
+ import Basic from "./Basic";
8
+ import Photo, { PSV_DEFAULT_ZOOM, PSV_ANIM_DURATION } from "../ui/Photo";
9
+ import MapMore from "../ui/MapMore";
10
+ import { initMapKeyboardHandler } from "../../utils/map";
11
+ import { createWebComp } from "../../utils/widgets";
12
+ import { fa } from "../../utils/widgets";
13
+ import { faPanorama } from "@fortawesome/free-solid-svg-icons/faPanorama";
14
+ import { faMap } from "@fortawesome/free-solid-svg-icons/faMap";
15
+ import { querySelectorDeep } from "query-selector-shadow-dom";
16
+ import { default as InitParameters, alterPSVState, alterMapState, alterViewerState } from "../../utils/InitParameters";
17
+
18
+
19
+ export const PSV_ZOOM_DELTA = 20;
20
+ const PSV_MOVE_DELTA = Math.PI / 6;
21
+ const MAP_MOVE_DELTA = 100;
22
+
23
+
24
+ /**
25
+ * Viewer is the main component of Panoramax JS library, showing pictures and map.
26
+ *
27
+ * This component has a [CorneredGrid](#Panoramax.components.layout.CorneredGrid) layout, you can use directly any slot element to pass custom widgets.
28
+ * @class Panoramax.components.core.Viewer
29
+ * @element pnx-viewer
30
+ * @extends Panoramax.components.core.Basic
31
+ * @property {Panoramax.components.ui.Loader} loader The loader screen
32
+ * @property {Panoramax.utils.API} api The API manager
33
+ * @property {Panoramax.components.ui.MapMore} [map] The MapLibre GL map itself (if map is enabled)
34
+ * @property {Panoramax.components.ui.Photo} psv The Photo Sphere Viewer component itself
35
+ * @property {Panoramax.components.layout.CorneredGrid} grid The grid layout manager
36
+ * @property {Panoramax.components.layout.Mini} [mini] The reduced/collapsed map/photo component (if map is enabled)
37
+ * @property {Panoramax.components.ui.Popup} popup The popup container
38
+ * @property {Panoramax.utils.URLHandler} urlHandler The URL query parameters manager
39
+ * @fires Panoramax.components.core.Basic#select
40
+ * @fires Panoramax.components.core.Basic#ready
41
+ * @fires Panoramax.components.core.Basic#broken
42
+ * @fires Panoramax.components.core.Viewer#focus-changed
43
+ * @slot `top-left` The top-left corner
44
+ * @slot `top` The top middle corner
45
+ * @slot `top-right` The top-right corner
46
+ * @slot `bottom-left` The bottom-left corner
47
+ * @slot `bottom` The bottom middle corner
48
+ * @slot `bottom-right` The bottom-right corner
49
+ * @example
50
+ * ```html
51
+ * <!-- Basic example -->
52
+ * <pnx-viewer
53
+ * endpoint="https://panoramax.openstreetmap.fr/"
54
+ * />
55
+ *
56
+ * <!-- With slotted widgets -->
57
+ * <pnx-viewer
58
+ * endpoint="https://panoramax.openstreetmap.fr/"
59
+ * >
60
+ * <p slot="top-right">My custom text</p>
61
+ * </pnx-viewer>
62
+ *
63
+ * <!-- With only your custom widgets -->
64
+ * <pnx-viewer
65
+ * endpoint="https://panoramax.openstreetmap.fr/"
66
+ * widgets="false"
67
+ * map="false"
68
+ * >
69
+ * <p slot="top-right">My custom text</p>
70
+ * </pnx-viewer>
71
+ * ```
72
+ */
73
+ export default class Viewer extends Basic {
74
+ /**
75
+ * Component properties. All of [Basic properties](#Panoramax.components.core.Basic+properties) are available as well.
76
+ * @memberof Panoramax.components.core.Viewer#
77
+ * @mixes Panoramax.components.core.Basic#properties
78
+ * @type {Object}
79
+ * @property {boolean|object} [map=true] Should map be used (true/false), or an object with [any map option available in Map class](#Panoramax.components.ui.MapMore)
80
+ * @property {object} [psv] [Any option to pass to Photo component](#Panoramax.components.ui.Photo) as an object
81
+ * @property {string} [url-parameters=true] Should the component add and update URL query parameters to save viewer state ?
82
+ * @property {string} [focus=pic] The component showing up as main component (pic, map)
83
+ * @property {string} [geocoder=nominatim] The geocoder engine to use (nominatim, ban)
84
+ * @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.
85
+ */
86
+ static properties = {
87
+ map: {type: Object},
88
+ psv: {type: Object},
89
+ "url-parameters": {type: String},
90
+ focus: {type: String, reflect: true},
91
+ geocoder: {type: String},
92
+ widgets: {type: String},
93
+ ...Basic.properties
94
+ };
95
+
96
+ constructor() {
97
+ super();
98
+
99
+ // Defaults
100
+ this.map = true;
101
+ this.psv = {};
102
+ this["url-parameters"] = this.getAttribute("url-parameters") || true;
103
+ this.geocoder = this.getAttribute("geocoder") || "nominatim";
104
+ this.widgets = this.getAttribute("widgets") || "true";
105
+
106
+ // Set variables
107
+ this._prevSequence = null;
108
+
109
+ // Init DOM containers
110
+ this.grid = createWebComp("pnx-cornered-grid");
111
+ this.mini = createWebComp("pnx-mini", {
112
+ slot: "bottom-left",
113
+ _parent: this,
114
+ onexpand: this._onMiniExpand.bind(this),
115
+ collapsed: this.picture ? undefined : true
116
+ });
117
+ this.mini.addEventListener("expand", this._toggleFocus.bind(this));
118
+ this.grid.appendChild(this.mini);
119
+ this.psvContainer = document.createElement("div");
120
+ this.mapContainer = document.createElement("div");
121
+ this.popup = createWebComp("pnx-popup", {_parent: this, onclose: this._onPopupClose.bind(this)});
122
+
123
+ if(this["url-parameters"] && this["url-parameters"] !== "false") {
124
+ this.urlHandler = new URLHandler(this);
125
+ this.onceReady().then(() => {
126
+ this.urlHandler.listenToChanges();
127
+ this.urlHandler._onParentChange();
128
+ });
129
+ }
130
+ }
131
+
132
+ /** @private */
133
+ connectedCallback() {
134
+ super.connectedCallback();
135
+ this._moveChildToGrid();
136
+
137
+ this.onceAPIReady().then(async () => {
138
+ this._initParams = new InitParameters(
139
+ InitParameters.GetComponentProperties(Viewer, this),
140
+ this.urlHandler?.currentURLParams()
141
+ );
142
+
143
+ const myInitParams = this._initParams.getParentInit();
144
+ const myPostInitParams = this._initParams.getParentPostInit();
145
+
146
+ this._initPSV();
147
+ if(myInitParams.map) { await this._initMap(); }
148
+ else {
149
+ this.grid.removeChild(this.mini);
150
+ this.psvContainer.setAttribute("slot", "bg");
151
+ this.grid.appendChild(this.psvContainer);
152
+ }
153
+
154
+ // Init various widgets
155
+ if(myInitParams.widgets !== "false") {
156
+ this.grid.appendChild(createWebComp("pnx-widget-zoom", {slot: "bottom-right", class: "pnx-print-hidden", _parent: this}));
157
+ this.grid.appendChild(createWebComp("pnx-widget-share", {slot: "bottom-right", class: "pnx-print-hidden", _parent: this}));
158
+
159
+ if(this.map) {
160
+ this.grid.appendChild(createWebComp("pnx-widget-geosearch", {
161
+ slot: this.isWidthSmall() ? "top-right" : "top-left",
162
+ _parent: this,
163
+ class: "pnx-only-map pnx-print-hidden",
164
+ geocoder: myPostInitParams.geocoder,
165
+ }));
166
+ this.grid.appendChild(createWebComp("pnx-widget-mapfilters", {
167
+ slot: this.isWidthSmall() ? "top-right" : "top-left",
168
+ _parent: this,
169
+ "user-search": this.api._endpoints.user_search !== null && this.api._endpoints.user_tiles !== null,
170
+ "quality-score": this.map?._hasQualityScore?.() || false,
171
+ class: "pnx-only-map pnx-print-hidden",
172
+ }));
173
+ this.grid.appendChild(createWebComp("pnx-widget-maplayers", { slot: "top-right", _parent: this, class: "pnx-only-map pnx-print-hidden" }));
174
+ }
175
+
176
+ this.legend = createWebComp("pnx-widget-legend", {
177
+ slot: this.isWidthSmall() ? "top" : "top-left",
178
+ _parent: this,
179
+ focus: myPostInitParams.focus
180
+ });
181
+ this.grid.appendChild(this.legend);
182
+ this.grid.appendChild(createWebComp("pnx-widget-player", {slot: "top", _parent: this, class: "pnx-only-psv pnx-print-hidden"}));
183
+ }
184
+
185
+ alterViewerState(this, myPostInitParams);
186
+ this._handleKeyboardManagement();
187
+ if(myPostInitParams.picture) {
188
+ this.psv.addEventListener("picture-loaded", () => this.loader.dismiss(), {once: true});
189
+ }
190
+ else {
191
+ this.loader.dismiss();
192
+ }
193
+ });
194
+ }
195
+
196
+ getClassName() {
197
+ return "Viewer";
198
+ }
199
+
200
+ /**
201
+ * Waits for Viewer to be completely ready (map & PSV loaded, first picture also if one is wanted)
202
+ * @returns {Promise} When viewer is ready
203
+ * @memberof Panoramax.components.core.Viewer#
204
+ */
205
+ onceReady() {
206
+ return Promise.all([this.oncePSVReady(), this.onceMapReady()])
207
+ .then(() => {
208
+ if(this._initParams.getParentPostInit().picture && !this.psv.getPictureMetadata()) { return this.onceFirstPicLoaded(); }
209
+ else { return Promise.resolve(); }
210
+ });
211
+ }
212
+
213
+ /** @private */
214
+ attributeChangedCallback(name, old, value) {
215
+ super.attributeChangedCallback(name, old, value);
216
+
217
+ // First pic load : show map in mini component
218
+ if(name === "picture" && old === null && value !== null) {
219
+ this.mini.removeAttribute("collapsed");
220
+ }
221
+ if(name === "focus") {
222
+ this._setFocus(value);
223
+ }
224
+ }
225
+
226
+ /** @private */
227
+ render() {
228
+ return [this.loader, this.grid, this.popup, this.slot];
229
+ }
230
+
231
+ /**
232
+ * Waiting for Photo Sphere Viewer to be available.
233
+ * @returns {Promise} When PSV is ready to use
234
+ * @memberof Panoramax.components.core.Viewer#
235
+ */
236
+ oncePSVReady() {
237
+ let waiter;
238
+ return new Promise(resolve => {
239
+ waiter = setInterval(() => {
240
+ if(typeof this.psv === "object") {
241
+ if(this.psv.container) {
242
+ clearInterval(waiter);
243
+ resolve();
244
+ }
245
+ else if(this.psv.addEventListener) {
246
+ this.psv.addEventListener("ready", () => {
247
+ clearInterval(waiter);
248
+ resolve();
249
+ }, {once: true});
250
+ }
251
+ }
252
+ }, 250);
253
+ });
254
+ }
255
+
256
+ /**
257
+ * Waiting for map to be available.
258
+ * @returns {Promise} When map is ready to use
259
+ * @memberof Panoramax.components.core.Viewer#
260
+ */
261
+ onceMapReady() {
262
+ if(!this.map) { return Promise.resolve(); }
263
+
264
+ let waiter;
265
+ return new Promise(resolve => {
266
+ waiter = setInterval(() => {
267
+ if(typeof this.map === "object") {
268
+ if(this.map.loaded?.()) {
269
+ clearInterval(waiter);
270
+ resolve();
271
+ }
272
+ else if(this.map.once) {
273
+ this.map.once("render", () => {
274
+ clearInterval(waiter);
275
+ resolve();
276
+ });
277
+ }
278
+ }
279
+ }, 250);
280
+ });
281
+ }
282
+
283
+ /**
284
+ * Waits for first picture to display on PSV.
285
+ * @returns {Promise}
286
+ * @fulfil {undefined} When picture is shown
287
+ * @memberof Panoramax.components.core.Viewer#
288
+ */
289
+ onceFirstPicLoaded() {
290
+ return this.oncePSVReady().then(() => new Promise(resolve => {
291
+ this.psv.addEventListener("picture-loaded", resolve, {once: true});
292
+ }));
293
+ }
294
+
295
+ /** @private */
296
+ _initPSV() {
297
+ try {
298
+ this.psv = new Photo(this, this.psvContainer, {
299
+ shouldGoFast: this._psvShouldGoFast.bind(this),
300
+ keyboard: "always",
301
+ keyboardActions: {
302
+ ...PSDefaults.keyboardActions,
303
+ "8": "ROTATE_UP",
304
+ "2": "ROTATE_DOWN",
305
+ "4": "ROTATE_LEFT",
306
+ "6": "ROTATE_RIGHT",
307
+
308
+ "PageUp": () => this.psv.goToNextPicture(),
309
+ "9": () => this.psv.goToNextPicture(),
310
+
311
+ "PageDown": () => this.psv.goToPrevPicture(),
312
+ "3": () => this.psv.goToPrevPicture(),
313
+
314
+ "5": () => this.moveCenter(),
315
+ "*": () => this.moveCenter(),
316
+
317
+ "Home": () => this._toggleFocus(),
318
+ "7": () => this._toggleFocus(),
319
+
320
+ "End": () => this.mini.toggleAttribute("collapsed"),
321
+ "1": () => this.mini.toggleAttribute("collapsed"),
322
+
323
+ " ": () => this.psv.toggleSequencePlaying(),
324
+ "0": () => this.psv.toggleSequencePlaying(),
325
+ },
326
+ ...this._initParams.getPSVInit()
327
+ });
328
+ this.oncePSVReady().then(() => alterPSVState(this.psv, this._initParams.getPSVPostInit()));
329
+ }
330
+ catch(e) {
331
+ let err = !PSSystem.isWebGLSupported ? this._t.pnx.error_webgl : this._t.pnx.error_psv;
332
+ this.loader.dismiss(e, err);
333
+ }
334
+ }
335
+
336
+ /**
337
+ * Inits MapLibre GL component
338
+ *
339
+ * @private
340
+ * @returns {Promise} Resolves when map is ready
341
+ */
342
+ async _initMap() {
343
+ await new Promise(resolve => {
344
+ this.map = new MapMore(this, this.mapContainer, this._initParams.getMapInit());
345
+ this.map.once("users-changed", resolve);
346
+ });
347
+
348
+ alterMapState(this.map, this._initParams.getMapPostInit());
349
+ initMapKeyboardHandler(this);
350
+ linkMapAndPhoto(this);
351
+ }
352
+
353
+ /** @private */
354
+ _handleKeyboardManagement() {
355
+ // Switchers
356
+ const keytomap = () => {
357
+ this.psv.stopKeyboardControl();
358
+ this.map.keyboard.enable();
359
+ };
360
+ const keytopsv = () => {
361
+ this.psv.startKeyboardControl();
362
+ this.map?.keyboard?.disable();
363
+ };
364
+ const keytonone = () => {
365
+ this.psv.stopKeyboardControl();
366
+ this.map?.keyboard?.disable();
367
+ };
368
+ const keytofocused = () => {
369
+ if(this.map && this.isMapWide()) { keytomap(); }
370
+ else { keytopsv(); }
371
+ };
372
+
373
+ // General focus change
374
+ this.addEventListener("focus-changed", e => {
375
+ if(e.detail.focus === "map") { keytomap(); }
376
+ else { keytopsv(); }
377
+ });
378
+
379
+ // Popup
380
+ this.popup.addEventListener("open", () => keytonone());
381
+ this.popup.addEventListener("close", () => keytofocused());
382
+
383
+ // Widgets
384
+ for(let cn of this.grid.childNodes) {
385
+ if(cn.getAttribute("slot") !== "bg") {
386
+ cn.addEventListener("focusin", () => keytonone());
387
+ cn.addEventListener("focusout", () => keytofocused());
388
+ }
389
+ }
390
+ }
391
+
392
+ /**
393
+ * Given context, should tiles be loaded in PSV.
394
+ * @private
395
+ */
396
+ _psvShouldGoFast() {
397
+ return (this.psv._sequencePlaying && this.psv.getTransitionDuration() < 1000)
398
+ || (this.map && this.isMapWide());
399
+ }
400
+
401
+ /** @private */
402
+ _moveChildToGrid() {
403
+ for(let i=0; i < this.childNodes.length; i++) {
404
+ let n = this.childNodes[i];
405
+ if(n.getAttribute?.("slot")) {
406
+ // Add parent + translation for our components
407
+ if(n.tagName?.toLowerCase().startsWith("pnx-")) {
408
+ n._parent = this;
409
+ n._t = this._t;
410
+ }
411
+ this.grid.appendChild(n);
412
+ }
413
+ }
414
+ }
415
+
416
+ /**
417
+ * Change full-page popup visibility and content
418
+ * @memberof Panoramax.components.core.Viewer#
419
+ * @param {boolean} visible True to make it appear
420
+ * @param {string|Element[]} [content] The new popup content
421
+ */
422
+ setPopup(visible, content = null) {
423
+ if(visible) { this.popup.setAttribute("visible", ""); }
424
+ else { this.popup.removeAttribute("visible"); }
425
+
426
+ this.popup.innerHTML = "";
427
+ if(typeof content === "string") { this.popup.innerHTML = content; }
428
+ else if(Array.isArray(content)) { content.forEach(c => this.popup.appendChild(c)); }
429
+ }
430
+
431
+ /** @private */
432
+ _onPopupClose() {
433
+ this.dispatchEvent(new CustomEvent("focus-changed", { detail: { focus: this.map && this.isMapWide() ? "map" : "pic" } }));
434
+ }
435
+
436
+ /** @private */
437
+ _showQualityScoreDoc() {
438
+ this.setPopup(true, [createWebComp("pnx-quality-score-doc", {_t: this._t})]);
439
+ }
440
+
441
+ /** @private */
442
+ _showReportForm() {
443
+ if(!this.psv.getPictureMetadata()) { throw new Error("No picture currently selected"); }
444
+ this.setPopup(true, [createWebComp("pnx-report-form", {_parent: this})]);
445
+ this.dispatchEvent(new CustomEvent("focus-changed", { detail: { focus: "meta" } }));
446
+ }
447
+
448
+ /** @private */
449
+ _showPictureMetadata() {
450
+ if(!this.psv.getPictureMetadata()) { throw new Error("No picture currently selected"); }
451
+ this.setPopup(true, [createWebComp("pnx-picture-metadata", {_parent: this})]);
452
+ this.dispatchEvent(new CustomEvent("focus-changed", { detail: { focus: "meta" } }));
453
+ }
454
+
455
+ /**
456
+ * Move the view of main component to its center.
457
+ * For map, center view on selected picture.
458
+ * For picture, center view on image center.
459
+ * @memberof Panoramax.components.core.Viewer#
460
+ */
461
+ moveCenter() {
462
+ const meta = this.psv.getPictureMetadata();
463
+ if(!meta) { return; }
464
+
465
+ if(this.map && this.isMapWide()) {
466
+ this.map.flyTo({ center: meta.gps, zoom: 20 });
467
+ }
468
+ else {
469
+ this._psvAnimate({
470
+ speed: PSV_ANIM_DURATION,
471
+ yaw: 0,
472
+ pitch: 0,
473
+ zoom: PSV_DEFAULT_ZOOM
474
+ });
475
+ }
476
+ }
477
+
478
+ /**
479
+ * Moves the view of main component slightly to the left.
480
+ * @memberof Panoramax.components.core.Viewer#
481
+ */
482
+ moveLeft() {
483
+ this._moveToDirection("left");
484
+ }
485
+
486
+ /**
487
+ * Moves the view of main component slightly to the right.
488
+ * @memberof Panoramax.components.core.Viewer#
489
+ */
490
+ moveRight() {
491
+ this._moveToDirection("right");
492
+ }
493
+
494
+ /**
495
+ * Moves the view of main component slightly to the top.
496
+ * @memberof Panoramax.components.core.Viewer#
497
+ */
498
+ moveUp() {
499
+ this._moveToDirection("up");
500
+ }
501
+
502
+ /**
503
+ * Moves the view of main component slightly to the bottom.
504
+ * @memberof Panoramax.components.core.Viewer#
505
+ */
506
+ moveDown() {
507
+ this._moveToDirection("down");
508
+ }
509
+
510
+ /**
511
+ * Moves map or picture viewer to given direction.
512
+ * @param {string} dir Direction to move to (up, left, down, right)
513
+ * @private
514
+ */
515
+ _moveToDirection(dir) {
516
+ if(this.map && this.isMapWide()) {
517
+ let pan;
518
+ switch(dir) {
519
+ case "up":
520
+ pan = [0, -MAP_MOVE_DELTA];
521
+ break;
522
+ case "left":
523
+ pan = [-MAP_MOVE_DELTA, 0];
524
+ break;
525
+ case "down":
526
+ pan = [0, MAP_MOVE_DELTA];
527
+ break;
528
+ case "right":
529
+ pan = [MAP_MOVE_DELTA, 0];
530
+ break;
531
+ }
532
+ this.map.panBy(pan);
533
+ }
534
+ else {
535
+ let pos = this.psv.getPosition();
536
+ switch(dir) {
537
+ case "up":
538
+ pos.pitch += PSV_MOVE_DELTA;
539
+ break;
540
+ case "left":
541
+ pos.yaw -= PSV_MOVE_DELTA;
542
+ break;
543
+ case "down":
544
+ pos.pitch -= PSV_MOVE_DELTA;
545
+ break;
546
+ case "right":
547
+ pos.yaw += PSV_MOVE_DELTA;
548
+ break;
549
+ }
550
+ this._psvAnimate({ speed: PSV_ANIM_DURATION, ...pos });
551
+ }
552
+ }
553
+
554
+ /**
555
+ * Overrided PSV animate function to ensure a single animation plays at once.
556
+ * @param {object} options PSV animate options
557
+ * @private
558
+ */
559
+ _psvAnimate(options) {
560
+ if(this._lastPsvAnim) { this._lastPsvAnim.cancel(); }
561
+ this._lastPsvAnim = this.psv.animate(options);
562
+ }
563
+
564
+ /**
565
+ * Is the map shown as main element instead of viewer (wide map mode) ?
566
+ * @memberof Panoramax.components.core.Viewer#
567
+ * @returns {boolean} True if map is wider than viewer
568
+ * @throws {Error} If map is not enabled
569
+ */
570
+ isMapWide() {
571
+ if(!this.map) { throw new Error("Map is not enabled"); }
572
+ return this.mapContainer.parentNode == this.grid;
573
+ }
574
+
575
+ /**
576
+ * Change the viewer focus (either on picture or map)
577
+ * @memberof Panoramax.components.core.Viewer#
578
+ * @param {string} focus The object to focus on (map, pic)
579
+ * @param {boolean} [skipEvent=false] True to not send focus-changed event
580
+ * @param {boolean} [skipDupCheck=false] True to avoid duplicate calls check
581
+ * @private
582
+ */
583
+ _setFocus(focus, skipEvent = false, skipDupCheck = false) {
584
+ if(focus === "map" && !this.map) { throw new Error("Map is not enabled"); }
585
+ if(!["map", "pic"].includes(focus)) { throw new Error("Invalid focus value (should be pic or map)"); }
586
+ this.focus = focus;
587
+
588
+ if(!skipDupCheck && (
589
+ (focus === "map" && this.map && this.isMapWide())
590
+ || (focus === "pic" && (!this.map || !this.isMapWide()))
591
+ )) { return; }
592
+
593
+ if(focus === "map") {
594
+ // Remove PSV from grid
595
+ if(this.psvContainer.parentNode == this.grid) {
596
+ this.grid.removeChild(this.psvContainer);
597
+ this.psvContainer.removeAttribute("slot");
598
+ }
599
+
600
+ // Remove map from mini
601
+ if(this.mapContainer.parentNode == this.mini) {
602
+ this.mini.removeChild(this.mapContainer);
603
+ }
604
+
605
+ // Add map to grid
606
+ this.mapContainer.setAttribute("slot", "bg");
607
+ this.grid.appendChild(this.mapContainer);
608
+
609
+ // Add PSV to mini
610
+ this.mini.appendChild(this.psvContainer);
611
+ this.mini.icon = fa(faPanorama);
612
+
613
+ this.map.getCanvas().focus();
614
+ }
615
+ else {
616
+ // Remove map from grid
617
+ if(this.mapContainer.parentNode == this.grid) {
618
+ this.grid.removeChild(this.mapContainer);
619
+ this.mapContainer.removeAttribute("slot");
620
+ }
621
+
622
+ // Remove PSV from mini
623
+ if(this.psvContainer.parentNode == this.mini) {
624
+ this.mini.removeChild(this.psvContainer);
625
+ }
626
+
627
+ // Add PSV to grid
628
+ this.psvContainer.setAttribute("slot", "bg");
629
+ this.grid.appendChild(this.psvContainer);
630
+
631
+ // Add map to mini
632
+ this.mini.appendChild(this.mapContainer);
633
+ this.mini.icon = fa(faMap);
634
+
635
+ this.psvContainer.focus();
636
+ }
637
+
638
+ this?.map?.resize?.();
639
+ this.psv.autoSize();
640
+ this.psv.forceRefresh();
641
+ this.legend?.setAttribute?.("focus", this.focus);
642
+
643
+ if(!skipEvent) {
644
+ /**
645
+ * Event for focus change (either map or picture is shown wide)
646
+ * @event Panoramax.components.core.Viewer#focus-changed
647
+ * @type {CustomEvent}
648
+ * @property {string} detail.focus Component now focused on (map, pic)
649
+ */
650
+ const event = new CustomEvent("focus-changed", { detail: { focus } });
651
+ this.dispatchEvent(event);
652
+ }
653
+ }
654
+
655
+ /**
656
+ * Toggle the viewer focus (either on picture or map)
657
+ * @memberof Panoramax.components.core.Viewer#
658
+ * @throws {Error} If map is not enabled
659
+ * @private
660
+ */
661
+ _toggleFocus() {
662
+ if(!this.map) { throw new Error("Map is not enabled"); }
663
+ this._setFocus(this.isMapWide() ? "pic" : "map");
664
+ }
665
+
666
+ /** @private */
667
+ _onMiniExpand() {
668
+ this.map.resize();
669
+ this.psv.autoSize();
670
+ }
671
+
672
+ /**
673
+ * Send viewer new map filters values.
674
+ * @private
675
+ */
676
+ _onMapFiltersChange() {
677
+ const mapFiltersMenu = querySelectorDeep("#pnx-map-filters-menu");
678
+ const fMinDate = mapFiltersMenu?.shadowRoot.getElementById("pnx-filter-date-from");
679
+ const fMaxDate = mapFiltersMenu?.shadowRoot.getElementById("pnx-filter-date-end");
680
+ const fTypeFlat = mapFiltersMenu?.shadowRoot.getElementById("pnx-filter-type-flat");
681
+ const fType360 = mapFiltersMenu?.shadowRoot.getElementById("pnx-filter-type-360");
682
+ const fMapTheme = querySelectorDeep("#pnx-map-theme");
683
+
684
+ let type = "";
685
+ if(fType360?.checked && !fTypeFlat?.checked) { type = "equirectangular"; }
686
+ if(!fType360?.checked && fTypeFlat?.checked) { type = "flat"; }
687
+
688
+ let qualityscore = [];
689
+ if(this.map?._hasQualityScore()) {
690
+ const fScore = mapFiltersMenu?.shadowRoot.getElementById("pnx-filter-qualityscore");
691
+ qualityscore = (fScore?.grade || "").split(",").map(v => parseInt(v)).filter(v => !isNaN(v));
692
+ if(qualityscore.length == 5) { qualityscore = []; }
693
+ }
694
+
695
+ const values = {
696
+ minDate: fMinDate?.value,
697
+ maxDate: fMaxDate?.value,
698
+ pic_type: type,
699
+ theme: fMapTheme?.value,
700
+ qualityscore,
701
+ };
702
+
703
+ this.map.setFilters(values);
704
+ }
705
+ }
706
+
707
+ customElements.define("pnx-viewer", Viewer);
@@ -0,0 +1,11 @@
1
+ /* eslint-disable import/no-unused-modules */
2
+
3
+ /**
4
+ * Core graphical components
5
+ * @module Panoramax:components:core
6
+ */
7
+
8
+ export {default as Basic} from "./Basic";
9
+ export {default as CoverageMap} from "./CoverageMap";
10
+ export {default as Editor} from "./Editor";
11
+ export {default as Viewer} from "./Viewer";