@panoramax/web-viewer 3.0.2-develop-a8ea8e60

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 (125) hide show
  1. package/.dockerignore +6 -0
  2. package/.gitlab-ci.yml +71 -0
  3. package/CHANGELOG.md +428 -0
  4. package/CODE_OF_CONDUCT.md +134 -0
  5. package/Dockerfile +14 -0
  6. package/LICENSE +21 -0
  7. package/README.md +39 -0
  8. package/build/editor.html +1 -0
  9. package/build/index.css +36 -0
  10. package/build/index.css.map +1 -0
  11. package/build/index.html +1 -0
  12. package/build/index.js +25 -0
  13. package/build/index.js.map +1 -0
  14. package/build/map.html +1 -0
  15. package/build/viewer.html +1 -0
  16. package/config/env.js +104 -0
  17. package/config/getHttpsConfig.js +66 -0
  18. package/config/getPackageJson.js +25 -0
  19. package/config/jest/babelTransform.js +29 -0
  20. package/config/jest/cssTransform.js +14 -0
  21. package/config/jest/fileTransform.js +40 -0
  22. package/config/modules.js +134 -0
  23. package/config/paths.js +72 -0
  24. package/config/pnpTs.js +35 -0
  25. package/config/webpack/persistentCache/createEnvironmentHash.js +9 -0
  26. package/config/webpack.config.js +885 -0
  27. package/config/webpackDevServer.config.js +127 -0
  28. package/docs/01_Start.md +149 -0
  29. package/docs/02_Usage.md +828 -0
  30. package/docs/03_URL_settings.md +140 -0
  31. package/docs/04_Advanced_examples.md +214 -0
  32. package/docs/05_Compatibility.md +85 -0
  33. package/docs/09_Develop.md +62 -0
  34. package/docs/90_Releases.md +27 -0
  35. package/docs/images/class_diagram.drawio +129 -0
  36. package/docs/images/class_diagram.jpg +0 -0
  37. package/docs/images/screenshot.jpg +0 -0
  38. package/mkdocs.yml +45 -0
  39. package/package.json +254 -0
  40. package/public/editor.html +54 -0
  41. package/public/favicon.ico +0 -0
  42. package/public/index.html +59 -0
  43. package/public/map.html +53 -0
  44. package/public/viewer.html +67 -0
  45. package/scripts/build.js +217 -0
  46. package/scripts/start.js +176 -0
  47. package/scripts/test.js +52 -0
  48. package/src/Editor.css +37 -0
  49. package/src/Editor.js +359 -0
  50. package/src/StandaloneMap.js +114 -0
  51. package/src/Viewer.css +203 -0
  52. package/src/Viewer.js +1186 -0
  53. package/src/components/CoreView.css +64 -0
  54. package/src/components/CoreView.js +159 -0
  55. package/src/components/Loader.css +56 -0
  56. package/src/components/Loader.js +111 -0
  57. package/src/components/Map.css +65 -0
  58. package/src/components/Map.js +841 -0
  59. package/src/components/Photo.css +36 -0
  60. package/src/components/Photo.js +687 -0
  61. package/src/img/arrow_360.svg +14 -0
  62. package/src/img/arrow_flat.svg +11 -0
  63. package/src/img/arrow_triangle.svg +10 -0
  64. package/src/img/arrow_turn.svg +9 -0
  65. package/src/img/bg_aerial.jpg +0 -0
  66. package/src/img/bg_streets.jpg +0 -0
  67. package/src/img/loader_base.jpg +0 -0
  68. package/src/img/loader_hd.jpg +0 -0
  69. package/src/img/logo_dead.svg +91 -0
  70. package/src/img/marker.svg +17 -0
  71. package/src/img/marker_blue.svg +20 -0
  72. package/src/img/switch_big.svg +44 -0
  73. package/src/img/switch_mini.svg +48 -0
  74. package/src/index.js +10 -0
  75. package/src/translations/de.json +163 -0
  76. package/src/translations/en.json +164 -0
  77. package/src/translations/eo.json +6 -0
  78. package/src/translations/es.json +164 -0
  79. package/src/translations/fi.json +1 -0
  80. package/src/translations/fr.json +164 -0
  81. package/src/translations/hu.json +133 -0
  82. package/src/translations/nl.json +1 -0
  83. package/src/translations/zh_Hant.json +136 -0
  84. package/src/utils/API.js +709 -0
  85. package/src/utils/Exif.js +198 -0
  86. package/src/utils/I18n.js +75 -0
  87. package/src/utils/Map.js +382 -0
  88. package/src/utils/PhotoAdapter.js +45 -0
  89. package/src/utils/Utils.js +568 -0
  90. package/src/utils/Widgets.js +477 -0
  91. package/src/viewer/URLHash.js +334 -0
  92. package/src/viewer/Widgets.css +711 -0
  93. package/src/viewer/Widgets.js +1196 -0
  94. package/tests/Editor.test.js +125 -0
  95. package/tests/StandaloneMap.test.js +44 -0
  96. package/tests/Viewer.test.js +363 -0
  97. package/tests/__snapshots__/Editor.test.js.snap +300 -0
  98. package/tests/__snapshots__/StandaloneMap.test.js.snap +30 -0
  99. package/tests/__snapshots__/Viewer.test.js.snap +195 -0
  100. package/tests/components/CoreView.test.js +91 -0
  101. package/tests/components/Loader.test.js +38 -0
  102. package/tests/components/Map.test.js +230 -0
  103. package/tests/components/Photo.test.js +335 -0
  104. package/tests/components/__snapshots__/Loader.test.js.snap +15 -0
  105. package/tests/components/__snapshots__/Map.test.js.snap +767 -0
  106. package/tests/components/__snapshots__/Photo.test.js.snap +205 -0
  107. package/tests/data/Map_geocoder_ban.json +36 -0
  108. package/tests/data/Map_geocoder_nominatim.json +56 -0
  109. package/tests/data/Viewer_pictures_1.json +148 -0
  110. package/tests/setupTests.js +5 -0
  111. package/tests/utils/API.test.js +906 -0
  112. package/tests/utils/Exif.test.js +124 -0
  113. package/tests/utils/I18n.test.js +28 -0
  114. package/tests/utils/Map.test.js +105 -0
  115. package/tests/utils/Utils.test.js +300 -0
  116. package/tests/utils/Widgets.test.js +107 -0
  117. package/tests/utils/__snapshots__/API.test.js.snap +132 -0
  118. package/tests/utils/__snapshots__/Exif.test.js.snap +43 -0
  119. package/tests/utils/__snapshots__/Map.test.js.snap +48 -0
  120. package/tests/utils/__snapshots__/Utils.test.js.snap +41 -0
  121. package/tests/utils/__snapshots__/Widgets.test.js.snap +44 -0
  122. package/tests/viewer/URLHash.test.js +537 -0
  123. package/tests/viewer/Widgets.test.js +127 -0
  124. package/tests/viewer/__snapshots__/URLHash.test.js.snap +98 -0
  125. package/tests/viewer/__snapshots__/Widgets.test.js.snap +393 -0
@@ -0,0 +1,334 @@
1
+ const MAP_FILTERS_JS2URL = {
2
+ "minDate": "date_from",
3
+ "maxDate": "date_to",
4
+ "type": "pic_type",
5
+ "camera": "camera",
6
+ "theme": "theme",
7
+ };
8
+ const MAP_FILTERS_URL2JS = Object.fromEntries(Object.entries(MAP_FILTERS_JS2URL).map(v => [v[1], v[0]]));
9
+ const UPDATE_HASH_EVENTS = [
10
+ "psv:view-rotated", "psv:picture-loaded", "focus-changed",
11
+ "filters-changed", "psv:transition-duration-changed",
12
+ "map:background-changed", "map:users-changed", "pictures-navigation-changed",
13
+ ];
14
+
15
+ /**
16
+ * Updates the URL hash with various viewer information.
17
+ * This doesn't handle the "map" parameter, which is managed by MapLibre GL JS
18
+ *
19
+ * Based on https://github.com/maplibre/maplibre-gl-js/blob/main/src/ui/hash.ts
20
+ *
21
+ * @returns {URLHash} `this`
22
+ *
23
+ * @private
24
+ */
25
+ export default class URLHash extends EventTarget {
26
+ constructor(viewer) {
27
+ super();
28
+ this._viewer = viewer;
29
+ this._delay = null;
30
+ this._hashChangeHandler = this._onHashChange.bind(this);
31
+
32
+ // Only start changing after first initial load
33
+ // to avoid generating broken URL during load
34
+ viewer.addEventListener("ready", () => {
35
+ window.addEventListener("hashchange", this._hashChangeHandler, false);
36
+ UPDATE_HASH_EVENTS.forEach(e => this._viewer.addEventListener(e, this._updateHash.bind(this)));
37
+ }, { once: true });
38
+ }
39
+
40
+ /**
41
+ * Ends all form of life in this object.
42
+ */
43
+ destroy() {
44
+ window.removeEventListener("hashchange", this._hashChangeHandler);
45
+ delete this._hashChangeHandler;
46
+ this._viewer?.map?.off("moveend", this._updateHash);
47
+ UPDATE_HASH_EVENTS.forEach(e => this._viewer.removeEventListener(e, this._updateHash));
48
+ delete this._viewer;
49
+ this._updateHash();
50
+ }
51
+
52
+ /**
53
+ * Start listening to map movend event
54
+ */
55
+ bindMapEvents() {
56
+ this._viewer.map.on("moveend", this._updateHash.bind(this));
57
+ }
58
+
59
+ /**
60
+ * Get the hash string with current map/psv parameters
61
+ * @return {string} The hash, starting with #
62
+ */
63
+ getHashString() {
64
+ let hash = "";
65
+ let hashParts = {};
66
+
67
+ if(typeof this._viewer.psv.getTransitionDuration() == "number") {
68
+ hashParts.speed = this._viewer.psv.getTransitionDuration();
69
+ }
70
+
71
+ if(![null, "any"].includes(this._viewer.getPicturesNavigation())) {
72
+ hashParts.nav = this._viewer.getPicturesNavigation();
73
+ }
74
+
75
+ const picMeta = this._viewer.psv.getPictureMetadata();
76
+ if (picMeta) {
77
+ hashParts.pic = picMeta.id;
78
+ hashParts.xyz = this._getXyzHashString();
79
+ }
80
+
81
+ if(this._viewer.map) {
82
+ hashParts.map = this._getMapHashString();
83
+ hashParts.focus = "pic";
84
+ if(this._viewer.isMapWide()) { hashParts.focus = "map"; }
85
+ if(!this._viewer.popupContainer.classList.contains("gvs-hidden")) { hashParts.focus = "meta"; }
86
+ if(this._viewer.map.hasTwoBackgrounds() && this._viewer.map.getBackground()) {
87
+ hashParts.background = this._viewer.map.getBackground();
88
+ }
89
+
90
+ const vu = this._viewer.map.getVisibleUsers();
91
+ if(vu.length > 1 || !vu.includes("geovisio")) {
92
+ hashParts.users = vu.join(",");
93
+ }
94
+
95
+ if(this._viewer._mapFilters) {
96
+ for(let k in MAP_FILTERS_JS2URL) {
97
+ if(this._viewer._mapFilters[k]) {
98
+ hashParts[MAP_FILTERS_JS2URL[k]] = this._viewer._mapFilters[k];
99
+ }
100
+ }
101
+ }
102
+ }
103
+ else {
104
+ hashParts.map = "none";
105
+ }
106
+
107
+ Object.entries(hashParts)
108
+ .sort((a,b) => a[0].localeCompare(b[0]))
109
+ .forEach(entry => {
110
+ let [ hashName, value ] = entry;
111
+ let found = false;
112
+ const parts = hash.split("&").map(part => {
113
+ const key = part.split("=")[0];
114
+ if (key === hashName) {
115
+ found = true;
116
+ return `${key}=${value}`;
117
+ }
118
+ return part;
119
+ }).filter(a => a);
120
+ if (!found) {
121
+ parts.push(`${hashName}=${value}`);
122
+ }
123
+ hash = `${parts.join("&")}`;
124
+ });
125
+
126
+ return `#${hash}`.replace(/^#+/, "#");
127
+ }
128
+
129
+ /**
130
+ * Transforms window.location.hash into key->value object
131
+ * @return {object} Key-value read from hash
132
+ * @private
133
+ */
134
+ _getCurrentHash() {
135
+ // Get the current hash from location, stripped from its number sign
136
+ const hash = window.location.hash.replace("#", "");
137
+
138
+ // Split the parameter-styled hash into parts and find the value we need
139
+ let keyvals = {};
140
+ hash.split("&").map(
141
+ part => part.split("=")
142
+ )
143
+ .filter(part => part[0] !== undefined && part[0].length > 0)
144
+ .forEach(part => {
145
+ keyvals[part[0]] = part[1];
146
+ });
147
+
148
+ return keyvals;
149
+ }
150
+
151
+ /**
152
+ * Get string representation of map position
153
+ * @returns {string} zoom/lat/lon or zoom/lat/lon/bearing/pitch
154
+ * @private
155
+ */
156
+ _getMapHashString() {
157
+ const center = this._viewer.map.getCenter(),
158
+ zoom = Math.round(this._viewer.map.getZoom() * 100) / 100,
159
+ // derived from equation: 512px * 2^z / 360 / 10^d < 0.5px
160
+ precision = Math.ceil((zoom * Math.LN2 + Math.log(512 / 360 / 0.5)) / Math.LN10),
161
+ m = Math.pow(10, precision),
162
+ lng = Math.round(center.lng * m) / m,
163
+ lat = Math.round(center.lat * m) / m,
164
+ bearing = this._viewer.map.getBearing(),
165
+ pitch = this._viewer.map.getPitch();
166
+ let hash = `${zoom}/${lat}/${lng}`;
167
+
168
+ if (bearing || pitch) hash += (`/${Math.round(bearing * 10) / 10}`);
169
+ if (pitch) hash += (`/${Math.round(pitch)}`);
170
+
171
+ return hash;
172
+ }
173
+
174
+ /**
175
+ * Get PSV view position as string
176
+ * @returns {string} x/y/z
177
+ * @private
178
+ */
179
+ _getXyzHashString() {
180
+ const xyz = this._viewer.psv.getXYZ();
181
+ const x = xyz.x.toFixed(2),
182
+ y = xyz.y.toFixed(2),
183
+ z = Math.round(xyz.z || 0);
184
+ return `${x}/${y}/${z}`;
185
+ }
186
+
187
+ /**
188
+ * Updates map and PSV according to current hash values
189
+ * @private
190
+ */
191
+ _onHashChange() {
192
+ const vals = this._getCurrentHash();
193
+
194
+ // Restore selected picture
195
+ if(vals.pic) {
196
+ const picIds = vals.pic.split(";"); // Handle multiple IDs coming from OSM
197
+ if(picIds.length > 1) {
198
+ console.warn("Multiple picture IDs passed in URL, only first one kept");
199
+ }
200
+ this._viewer.select(null, picIds[0]);
201
+ }
202
+
203
+ // Change focus
204
+ if(vals.focus && ["map", "pic"].includes(vals.focus)) {
205
+ this._viewer.setFocus(vals.focus);
206
+ }
207
+ if(vals.focus && vals.focus == "meta") {
208
+ this._viewer._widgets._showPictureMetadataPopup();
209
+ }
210
+
211
+ // Change speed
212
+ if(vals.speed !== undefined) {
213
+ this._viewer.psv.setTransitionDuration(vals.speed);
214
+ }
215
+
216
+ // Change map position & users
217
+ if(vals.map && this._viewer.map) {
218
+ const mapOpts = this.getMapOptionsFromHashString(vals.map);
219
+ if(mapOpts) {
220
+ this._viewer.map.jumpTo(mapOpts);
221
+ }
222
+
223
+ let vu = (vals.users || "").split(",");
224
+ if(vu.length === 0 || (vu.length === 1 && vu[0].trim() === "")) { vu = ["geovisio"]; }
225
+ this._viewer.map.setVisibleUsers(vu);
226
+ }
227
+
228
+ // Change xyz position
229
+ if(vals.xyz) {
230
+ const coords = this.getXyzOptionsFromHashString(vals.xyz);
231
+ this._viewer.psv.setXYZ(coords.x, coords.y, coords.z);
232
+ }
233
+
234
+ // Change map filters
235
+ this._viewer.setFilters(this.getMapFiltersFromHashVals(vals));
236
+
237
+ // Change map background
238
+ if(["aerial", "streets"].includes(vals.background)) {
239
+ this._viewer.map.setBackground(vals.background);
240
+ }
241
+
242
+ // Change pictures navigation mode
243
+ if(["pic", "any", "seq"].includes(vals.nav)) {
244
+ this._viewer.setPicturesNavigation(vals.nav);
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Extracts from hash parsed keys all map filters values
250
+ * @param {*} vals Hash keys
251
+ * @returns {object} Map filters
252
+ */
253
+ getMapFiltersFromHashVals(vals) {
254
+ const newMapFilters = {};
255
+ for(let k in MAP_FILTERS_URL2JS) {
256
+ if(vals[k]) {
257
+ newMapFilters[MAP_FILTERS_URL2JS[k]] = vals[k];
258
+ }
259
+ }
260
+ return newMapFilters;
261
+ }
262
+
263
+ /**
264
+ * Extracts from string map position
265
+ * @param {string} str The map position as hash string
266
+ * @returns {object} { center, zoom, pitch, bearing }
267
+ */
268
+ getMapOptionsFromHashString(str) {
269
+ const loc = str.split("/");
270
+ if (loc.length >= 3 && !loc.some(v => isNaN(v))) {
271
+ const res = {
272
+ center: [+loc[2], +loc[1]],
273
+ zoom: +loc[0],
274
+ pitch: +(loc[4] || 0)
275
+ };
276
+
277
+ if(this._viewer.map) {
278
+ res.bearing = this._viewer.map.dragRotate.isEnabled() && this._viewer.map.touchZoomRotate.isEnabled() ? +(loc[3] || 0) : this._viewer.map.getBearing();
279
+ }
280
+
281
+ return res;
282
+ }
283
+ else { return null; }
284
+ }
285
+
286
+ /**
287
+ * Extracts from string xyz position
288
+ * @param {string} str The xyz position as hash string
289
+ * @returns {object} { x, y, z }
290
+ */
291
+ getXyzOptionsFromHashString(str) {
292
+ const loc = str.split("/");
293
+ if (loc.length === 3 && !loc.some(v => isNaN(v))) {
294
+ const res = {
295
+ x: +loc[0],
296
+ y: +loc[1],
297
+ z: +loc[2]
298
+ };
299
+
300
+ return res;
301
+ }
302
+ else { return null; }
303
+ }
304
+
305
+ /**
306
+ * Changes the URL hash using current viewer parameters
307
+ * @private
308
+ */
309
+ _updateHash() {
310
+ if(this._delay) {
311
+ clearTimeout(this._delay);
312
+ this._delay = null;
313
+ }
314
+
315
+ this._delay = setTimeout(() => {
316
+ // Replace if already present, else append the updated hash string
317
+ const location = window.location.href.replace(
318
+ /(#.+)?$/,
319
+ this._viewer ? this.getHashString() : ""
320
+ );
321
+ try {
322
+ window.history.replaceState(window.history.state, null, location);
323
+ if(this._viewer) {
324
+ const event = new CustomEvent("url-changed", { detail: {url: location}});
325
+ this.dispatchEvent(event);
326
+ }
327
+ } catch (SecurityError) {
328
+ // IE11 does not allow this if the page is within an iframe created
329
+ // with iframe.contentWindow.document.write(...).
330
+ // https://github.com/mapbox/mapbox-gl-js/issues/7410
331
+ }
332
+ }, 500);
333
+ }
334
+ }