@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.
- package/.dockerignore +6 -0
- package/.gitlab-ci.yml +71 -0
- package/CHANGELOG.md +428 -0
- package/CODE_OF_CONDUCT.md +134 -0
- package/Dockerfile +14 -0
- package/LICENSE +21 -0
- package/README.md +39 -0
- package/build/editor.html +1 -0
- package/build/index.css +36 -0
- package/build/index.css.map +1 -0
- package/build/index.html +1 -0
- package/build/index.js +25 -0
- package/build/index.js.map +1 -0
- package/build/map.html +1 -0
- package/build/viewer.html +1 -0
- package/config/env.js +104 -0
- package/config/getHttpsConfig.js +66 -0
- package/config/getPackageJson.js +25 -0
- package/config/jest/babelTransform.js +29 -0
- package/config/jest/cssTransform.js +14 -0
- package/config/jest/fileTransform.js +40 -0
- package/config/modules.js +134 -0
- package/config/paths.js +72 -0
- package/config/pnpTs.js +35 -0
- package/config/webpack/persistentCache/createEnvironmentHash.js +9 -0
- package/config/webpack.config.js +885 -0
- package/config/webpackDevServer.config.js +127 -0
- package/docs/01_Start.md +149 -0
- package/docs/02_Usage.md +828 -0
- package/docs/03_URL_settings.md +140 -0
- package/docs/04_Advanced_examples.md +214 -0
- package/docs/05_Compatibility.md +85 -0
- package/docs/09_Develop.md +62 -0
- package/docs/90_Releases.md +27 -0
- package/docs/images/class_diagram.drawio +129 -0
- package/docs/images/class_diagram.jpg +0 -0
- package/docs/images/screenshot.jpg +0 -0
- package/mkdocs.yml +45 -0
- package/package.json +254 -0
- package/public/editor.html +54 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +59 -0
- package/public/map.html +53 -0
- package/public/viewer.html +67 -0
- package/scripts/build.js +217 -0
- package/scripts/start.js +176 -0
- package/scripts/test.js +52 -0
- package/src/Editor.css +37 -0
- package/src/Editor.js +359 -0
- package/src/StandaloneMap.js +114 -0
- package/src/Viewer.css +203 -0
- package/src/Viewer.js +1186 -0
- package/src/components/CoreView.css +64 -0
- package/src/components/CoreView.js +159 -0
- package/src/components/Loader.css +56 -0
- package/src/components/Loader.js +111 -0
- package/src/components/Map.css +65 -0
- package/src/components/Map.js +841 -0
- package/src/components/Photo.css +36 -0
- package/src/components/Photo.js +687 -0
- package/src/img/arrow_360.svg +14 -0
- package/src/img/arrow_flat.svg +11 -0
- package/src/img/arrow_triangle.svg +10 -0
- package/src/img/arrow_turn.svg +9 -0
- package/src/img/bg_aerial.jpg +0 -0
- package/src/img/bg_streets.jpg +0 -0
- package/src/img/loader_base.jpg +0 -0
- package/src/img/loader_hd.jpg +0 -0
- package/src/img/logo_dead.svg +91 -0
- package/src/img/marker.svg +17 -0
- package/src/img/marker_blue.svg +20 -0
- package/src/img/switch_big.svg +44 -0
- package/src/img/switch_mini.svg +48 -0
- package/src/index.js +10 -0
- package/src/translations/de.json +163 -0
- package/src/translations/en.json +164 -0
- package/src/translations/eo.json +6 -0
- package/src/translations/es.json +164 -0
- package/src/translations/fi.json +1 -0
- package/src/translations/fr.json +164 -0
- package/src/translations/hu.json +133 -0
- package/src/translations/nl.json +1 -0
- package/src/translations/zh_Hant.json +136 -0
- package/src/utils/API.js +709 -0
- package/src/utils/Exif.js +198 -0
- package/src/utils/I18n.js +75 -0
- package/src/utils/Map.js +382 -0
- package/src/utils/PhotoAdapter.js +45 -0
- package/src/utils/Utils.js +568 -0
- package/src/utils/Widgets.js +477 -0
- package/src/viewer/URLHash.js +334 -0
- package/src/viewer/Widgets.css +711 -0
- package/src/viewer/Widgets.js +1196 -0
- package/tests/Editor.test.js +125 -0
- package/tests/StandaloneMap.test.js +44 -0
- package/tests/Viewer.test.js +363 -0
- package/tests/__snapshots__/Editor.test.js.snap +300 -0
- package/tests/__snapshots__/StandaloneMap.test.js.snap +30 -0
- package/tests/__snapshots__/Viewer.test.js.snap +195 -0
- package/tests/components/CoreView.test.js +91 -0
- package/tests/components/Loader.test.js +38 -0
- package/tests/components/Map.test.js +230 -0
- package/tests/components/Photo.test.js +335 -0
- package/tests/components/__snapshots__/Loader.test.js.snap +15 -0
- package/tests/components/__snapshots__/Map.test.js.snap +767 -0
- package/tests/components/__snapshots__/Photo.test.js.snap +205 -0
- package/tests/data/Map_geocoder_ban.json +36 -0
- package/tests/data/Map_geocoder_nominatim.json +56 -0
- package/tests/data/Viewer_pictures_1.json +148 -0
- package/tests/setupTests.js +5 -0
- package/tests/utils/API.test.js +906 -0
- package/tests/utils/Exif.test.js +124 -0
- package/tests/utils/I18n.test.js +28 -0
- package/tests/utils/Map.test.js +105 -0
- package/tests/utils/Utils.test.js +300 -0
- package/tests/utils/Widgets.test.js +107 -0
- package/tests/utils/__snapshots__/API.test.js.snap +132 -0
- package/tests/utils/__snapshots__/Exif.test.js.snap +43 -0
- package/tests/utils/__snapshots__/Map.test.js.snap +48 -0
- package/tests/utils/__snapshots__/Utils.test.js.snap +41 -0
- package/tests/utils/__snapshots__/Widgets.test.js.snap +44 -0
- package/tests/viewer/URLHash.test.js +537 -0
- package/tests/viewer/Widgets.test.js +127 -0
- package/tests/viewer/__snapshots__/URLHash.test.js.snap +98 -0
- 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
|
+
}
|