@panoramax/web-viewer 3.1.1 → 3.2.0-develop-8f79d734
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/CHANGELOG.md +26 -1
- package/build/index.css +2 -2
- package/build/index.css.map +1 -1
- package/build/index.js +6 -6
- package/build/index.js.map +1 -1
- package/docs/02_Usage.md +267 -224
- package/docs/03_URL_settings.md +10 -1
- package/docs/05_Compatibility.md +1 -0
- package/package.json +4 -3
- package/src/Viewer.js +51 -3
- package/src/components/CoreView.css +6 -0
- package/src/components/CoreView.js +25 -10
- package/src/components/Map.js +41 -2
- package/src/components/Photo.css +5 -0
- package/src/translations/de.json +28 -11
- package/src/translations/en.json +22 -11
- package/src/translations/es.json +23 -11
- package/src/translations/fr.json +22 -11
- package/src/translations/hu.json +117 -73
- package/src/translations/nl.json +73 -1
- package/src/translations/pl.json +1 -0
- package/src/translations/zh_Hant.json +53 -9
- package/src/utils/API.js +20 -3
- package/src/utils/Exif.js +5 -10
- package/src/utils/Map.js +56 -10
- package/src/utils/Utils.js +68 -24
- package/src/utils/Widgets.js +64 -2
- package/src/viewer/URLHash.js +18 -3
- package/src/viewer/Widgets.css +139 -0
- package/src/viewer/Widgets.js +207 -42
- package/tests/Viewer.test.js +1 -0
- package/tests/__snapshots__/Editor.test.js.snap +1 -5
- package/tests/components/__snapshots__/Photo.test.js.snap +2 -10
- package/tests/utils/Exif.test.js +10 -10
- package/tests/utils/Map.test.js +8 -0
- package/tests/utils/__snapshots__/API.test.js.snap +5 -0
- package/tests/utils/__snapshots__/Widgets.test.js.snap +1 -1
- package/tests/viewer/__snapshots__/Widgets.test.js.snap +39 -29
package/src/utils/Map.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// DO NOT REMOVE THE "!": bundled builds breaks otherwise !!!
|
|
2
2
|
import maplibregl from "!maplibre-gl";
|
|
3
3
|
import LoaderImg from "../img/marker.svg";
|
|
4
|
-
import { COLORS } from "./Utils";
|
|
4
|
+
import { COLORS, QUALITYSCORE_RES_FLAT_VALUES, QUALITYSCORE_RES_360_VALUES, QUALITYSCORE_GPS_VALUES, QUALITYSCORE_POND_RES, QUALITYSCORE_POND_GPS } from "./Utils";
|
|
5
5
|
import { autoDetectLocale } from "./I18n";
|
|
6
6
|
|
|
7
7
|
export const DEFAULT_TILES = "https://panoramax.openstreetmap.fr/pmtiles/basic.json";
|
|
@@ -63,6 +63,24 @@ export const VECTOR_STYLES = {
|
|
|
63
63
|
}
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
+
|
|
67
|
+
// See MapLibre docs for explanation of expressions magic: https://maplibre.org/maplibre-style-spec/expressions/
|
|
68
|
+
const MAP_EXPR_QUALITYSCORE_RES_360 = ["case", ["has", "h_pixel_density"], ["step", ["get", "h_pixel_density"], ...QUALITYSCORE_RES_360_VALUES], 1];
|
|
69
|
+
const MAP_EXPR_QUALITYSCORE_RES_FLAT = ["case", ["has", "h_pixel_density"], ["step", ["get", "h_pixel_density"], ...QUALITYSCORE_RES_FLAT_VALUES], 1];
|
|
70
|
+
const MAP_EXPR_QUALITYSCORE_RES = [
|
|
71
|
+
"case", ["==", ["get", "type"], "equirectangular"],
|
|
72
|
+
MAP_EXPR_QUALITYSCORE_RES_360, MAP_EXPR_QUALITYSCORE_RES_FLAT
|
|
73
|
+
];
|
|
74
|
+
const MAP_EXPR_QUALITYSCORE_GPS = ["case", ["has", "gps_accuracy"], ["step", ["get", "gps_accuracy"], ...QUALITYSCORE_GPS_VALUES], 1];
|
|
75
|
+
// Note: score is also calculated in widgets/popup code
|
|
76
|
+
export const MAP_EXPR_QUALITYSCORE = [
|
|
77
|
+
"round",
|
|
78
|
+
["+",
|
|
79
|
+
["*", MAP_EXPR_QUALITYSCORE_RES, QUALITYSCORE_POND_RES],
|
|
80
|
+
["*", MAP_EXPR_QUALITYSCORE_GPS, QUALITYSCORE_POND_GPS]]
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
|
|
66
84
|
/**
|
|
67
85
|
* Get the GIF shown while thumbnail loads
|
|
68
86
|
* @param {object} lang Translations
|
|
@@ -109,10 +127,12 @@ export function combineStyles(parent, options) {
|
|
|
109
127
|
|
|
110
128
|
// Complete styles
|
|
111
129
|
style.layers = style.layers.concat(getMissingLayerStyles(style.sources, style.layers));
|
|
130
|
+
if(!style.metadata) { style.metadata = {}; }
|
|
112
131
|
|
|
113
132
|
// Complementary style
|
|
114
133
|
if(options.supplementaryStyle) {
|
|
115
134
|
Object.assign(style.sources, options.supplementaryStyle.sources || {});
|
|
135
|
+
Object.assign(style.metadata, options.supplementaryStyle.metadata || {});
|
|
116
136
|
style.layers = style.layers.concat(options.supplementaryStyle.layers || []);
|
|
117
137
|
}
|
|
118
138
|
|
|
@@ -155,13 +175,15 @@ export function combineStyles(parent, options) {
|
|
|
155
175
|
});
|
|
156
176
|
|
|
157
177
|
// TODO : remove override once available in default Panoramax style
|
|
158
|
-
if(!style.metadata["panoramax:locales"]) {
|
|
178
|
+
if(!style.metadata?.["panoramax:locales"]) {
|
|
159
179
|
style.metadata["panoramax:locales"] = ["fr", "en", "de", "es", "ru", "pt", "zh", "hi", "latin"];
|
|
160
180
|
}
|
|
161
181
|
|
|
162
182
|
// Override labels to use appropriate language
|
|
163
183
|
if(style.metadata["panoramax:locales"]) {
|
|
164
|
-
|
|
184
|
+
let prefLang = parent._options.lang || autoDetectLocale(style.metadata["panoramax:locales"], "latin");
|
|
185
|
+
if(prefLang.includes("-")) { prefLang = prefLang.split("-")[0]; }
|
|
186
|
+
if(prefLang.includes("_")) { prefLang = prefLang.split("_")[0]; }
|
|
165
187
|
style.layers.forEach(l => {
|
|
166
188
|
if(isLabelLayer(l) && l.layout["text-field"].includes("name:latin")) {
|
|
167
189
|
l.layout["text-field"] = [
|
|
@@ -179,18 +201,19 @@ export function combineStyles(parent, options) {
|
|
|
179
201
|
let capitalLayer = style.layers.find(l => l.id == "place_label_capital");
|
|
180
202
|
if(citiesLayer && !capitalLayer) {
|
|
181
203
|
// Create capital layer from original city style
|
|
204
|
+
citiesLayer.paint = {
|
|
205
|
+
"text-color": "hsl(0, 0%, 0%)",
|
|
206
|
+
"text-halo-blur": 0,
|
|
207
|
+
"text-halo-color": "hsla(0, 0%, 100%, 1)",
|
|
208
|
+
"text-halo-width": 3,
|
|
209
|
+
};
|
|
210
|
+
citiesLayer.layout["text-letter-spacing"] = 0.1;
|
|
182
211
|
capitalLayer = JSON.parse(JSON.stringify(citiesLayer));
|
|
183
212
|
capitalLayer.id = "place_label_capital";
|
|
184
213
|
capitalLayer.filter.push(["<=", "capital", 2]);
|
|
185
214
|
|
|
186
215
|
// Edit original city to make it less import
|
|
187
216
|
citiesLayer.filter.push([">", "capital", 2]);
|
|
188
|
-
citiesLayer.paint = {
|
|
189
|
-
"text-color": "hsl(0,0%,15%)",
|
|
190
|
-
"text-halo-blur": 0.5,
|
|
191
|
-
"text-halo-color": "hsl(0,0%,100%)",
|
|
192
|
-
"text-halo-width": 0.8,
|
|
193
|
-
};
|
|
194
217
|
style.layers.push(capitalLayer);
|
|
195
218
|
}
|
|
196
219
|
|
|
@@ -317,7 +340,6 @@ export function getMissingLayerStyles(sources, layers) {
|
|
|
317
340
|
l.layout = Object.assign(l.layout || {}, VECTOR_STYLES.PICTURES.layout);
|
|
318
341
|
});
|
|
319
342
|
|
|
320
|
-
|
|
321
343
|
return newLayers;
|
|
322
344
|
}
|
|
323
345
|
|
|
@@ -342,6 +364,30 @@ export function getUserSourceId(userId) {
|
|
|
342
364
|
return userId === "geovisio" ? "geovisio" : "geovisio_"+userId;
|
|
343
365
|
}
|
|
344
366
|
|
|
367
|
+
/**
|
|
368
|
+
* Switches used coef value in MapLibre style JSON expression
|
|
369
|
+
* @param {*} expr The MapLibre style expression
|
|
370
|
+
* @param {string} newCoefVal The new coef value to use
|
|
371
|
+
* @returns {*} The switched expression
|
|
372
|
+
* @private
|
|
373
|
+
*/
|
|
374
|
+
export function switchCoefValue(expr, newCoefVal) {
|
|
375
|
+
if(Array.isArray(expr)) {
|
|
376
|
+
return expr.map(v => switchCoefValue(v, newCoefVal));
|
|
377
|
+
}
|
|
378
|
+
else if(typeof expr === "object" && expr !== null) {
|
|
379
|
+
const newExpr = {};
|
|
380
|
+
for (const key in expr) {
|
|
381
|
+
newExpr[key] = switchCoefValue(expr[key], newCoefVal);
|
|
382
|
+
}
|
|
383
|
+
return newExpr;
|
|
384
|
+
}
|
|
385
|
+
else if(typeof expr === "string" && expr.startsWith("coef")) {
|
|
386
|
+
return newCoefVal;
|
|
387
|
+
}
|
|
388
|
+
return expr;
|
|
389
|
+
}
|
|
390
|
+
|
|
345
391
|
/**
|
|
346
392
|
* Transforms a set of parameters into an URL-ready string
|
|
347
393
|
* It also removes null/undefined values
|
package/src/utils/Utils.js
CHANGED
|
@@ -23,9 +23,45 @@ export const COLORS_HEX = Object.fromEntries(Object.entries(COLORS).map(e => {
|
|
|
23
23
|
return e;
|
|
24
24
|
}));
|
|
25
25
|
|
|
26
|
+
export const QUALITYSCORE_VALUES = [
|
|
27
|
+
{ color: "#007f4e", label: "A" },
|
|
28
|
+
{ color: "#72b043", label: "B" },
|
|
29
|
+
{ color: "#b5be2f", label: "C" },
|
|
30
|
+
{ color: "#f8cc1b", label: "D" },
|
|
31
|
+
{ color: "#f6a020", label: "E" },
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
export const QUALITYSCORE_RES_FLAT_VALUES = [1, 15, 2, 38, 3, 60, 4]; // Grade, < Px/FOV value
|
|
35
|
+
export const QUALITYSCORE_RES_360_VALUES = [2, 15, 3, 20, 4, 38, 5]; // Grade, < Px/FOV value
|
|
36
|
+
export const QUALITYSCORE_GPS_VALUES = [5, 1.01, 4, 2.01, 3, 5.01, 2, 10.01, 1]; // Grade, < Meters value
|
|
37
|
+
export const QUALITYSCORE_POND_RES = 4/5;
|
|
38
|
+
export const QUALITYSCORE_POND_GPS = 1/5;
|
|
39
|
+
|
|
26
40
|
const ArrowTriangle = svgToPSVLink(ArrowTriangleSVG, "white");
|
|
27
41
|
const ArrowTurn = svgToPSVLink(ArrowTurnSVG, COLORS.NEXT);
|
|
28
42
|
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Find the grade associated to an input Quality Score definition.
|
|
46
|
+
* @param {number[]} ranges The QUALITYSCORE_*_VALUES definition
|
|
47
|
+
* @param {number} value The picture value
|
|
48
|
+
* @return {number} The corresponding grade (1 to 5, or null if missing)
|
|
49
|
+
*/
|
|
50
|
+
export function getGrade(ranges, value) {
|
|
51
|
+
if(value === null || value === undefined || value === "") { return null; }
|
|
52
|
+
|
|
53
|
+
// Read each pair from table (grade, reference value)
|
|
54
|
+
for(let i = 0; i < ranges.length; i += 2) {
|
|
55
|
+
const grade = ranges[i];
|
|
56
|
+
const limit = ranges[i+1];
|
|
57
|
+
|
|
58
|
+
// Send grade if value is under limit
|
|
59
|
+
if (value < limit) { return grade;}
|
|
60
|
+
}
|
|
61
|
+
// Otherwise, send last grade
|
|
62
|
+
return ranges[ranges.length - 1];
|
|
63
|
+
}
|
|
64
|
+
|
|
29
65
|
/**
|
|
30
66
|
* Get cartesian distance between two points
|
|
31
67
|
* @param {number[]} from Start [x,y] coordinates
|
|
@@ -350,7 +386,7 @@ export function apiFeatureToPSVNode(f, t, fastInternet=false, customLinkFilter=n
|
|
|
350
386
|
};
|
|
351
387
|
}
|
|
352
388
|
// 360°
|
|
353
|
-
else if(is360) {
|
|
389
|
+
else if(is360 && matrix) {
|
|
354
390
|
panorama = {
|
|
355
391
|
baseUrl,
|
|
356
392
|
origBaseUrl: baseUrl,
|
|
@@ -395,7 +431,7 @@ export function apiFeatureToPSVNode(f, t, fastInternet=false, customLinkFilter=n
|
|
|
395
431
|
};
|
|
396
432
|
}
|
|
397
433
|
|
|
398
|
-
|
|
434
|
+
const node = {
|
|
399
435
|
id: f.id,
|
|
400
436
|
caption: getNodeCaption(f, t),
|
|
401
437
|
panorama,
|
|
@@ -410,6 +446,8 @@ export function apiFeatureToPSVNode(f, t, fastInternet=false, customLinkFilter=n
|
|
|
410
446
|
horizontalFov,
|
|
411
447
|
properties: f.properties,
|
|
412
448
|
};
|
|
449
|
+
|
|
450
|
+
return node;
|
|
413
451
|
}
|
|
414
452
|
|
|
415
453
|
/**
|
|
@@ -514,29 +552,35 @@ export function isInternetFast() {
|
|
|
514
552
|
}
|
|
515
553
|
// Fallback for other browsers
|
|
516
554
|
catch(e) {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
555
|
+
try {
|
|
556
|
+
// Check if test has been done before and stored
|
|
557
|
+
const isFast = sessionStorage.getItem(INTERNET_FAST_STORAGE);
|
|
558
|
+
if(["true", "false"].includes(isFast)) {
|
|
559
|
+
return Promise.resolve(isFast === "true");
|
|
560
|
+
}
|
|
522
561
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
562
|
+
// Run download testing
|
|
563
|
+
const startTime = (new Date()).getTime();
|
|
564
|
+
return fetch(INTERNET_FAST_TESTFILE+"?nocache="+startTime)
|
|
565
|
+
.then(async res => [res, await res.blob()])
|
|
566
|
+
.then(([res, blob]) => {
|
|
567
|
+
const size = parseInt(res.headers.get("Content-Length") || blob.size); // Bytes
|
|
568
|
+
const endTime = (new Date()).getTime();
|
|
569
|
+
const duration = (endTime - startTime) / 1000; // Transfer time in seconds
|
|
570
|
+
const speed = (size * 8 / 1024 / 1024) / duration; // MBits/s
|
|
571
|
+
const isFast = speed >= INTERNET_FAST_THRESHOLD;
|
|
572
|
+
sessionStorage.setItem(INTERNET_FAST_STORAGE, isFast ? "true" : "false");
|
|
573
|
+
return isFast;
|
|
574
|
+
})
|
|
575
|
+
.catch(e => {
|
|
576
|
+
console.warn("Failed to run speedtest", e);
|
|
577
|
+
return false;
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
// Fallback for browser blocking third-party downloads or sessionStorage
|
|
581
|
+
catch(e) {
|
|
582
|
+
return Promise.resolve(false);
|
|
583
|
+
}
|
|
540
584
|
}
|
|
541
585
|
}
|
|
542
586
|
|
package/src/utils/Widgets.js
CHANGED
|
@@ -7,6 +7,9 @@ import { faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons/faMagnifyin
|
|
|
7
7
|
import { faCircleNotch } from "@fortawesome/free-solid-svg-icons/faCircleNotch";
|
|
8
8
|
import { faCheck } from "@fortawesome/free-solid-svg-icons/faCheck";
|
|
9
9
|
import { faCopy } from "@fortawesome/free-solid-svg-icons/faCopy";
|
|
10
|
+
import { faStar } from "@fortawesome/free-solid-svg-icons/faStar";
|
|
11
|
+
import { faStar as farStar } from "@fortawesome/free-regular-svg-icons/faStar";
|
|
12
|
+
import { QUALITYSCORE_VALUES } from "./Utils";
|
|
10
13
|
|
|
11
14
|
|
|
12
15
|
/**
|
|
@@ -59,6 +62,19 @@ export function createExpandableButton(id, icon, label, container, classes = [])
|
|
|
59
62
|
btn.title = label;
|
|
60
63
|
}
|
|
61
64
|
btn.classList.add("gvs-btn", "gvs-widget-bg", "gvs-btn-expandable", ...classes);
|
|
65
|
+
btn.setActive = val => {
|
|
66
|
+
let span = btn.querySelector(".gvs-filters-active");
|
|
67
|
+
if(val && !span) {
|
|
68
|
+
span = document.createElement("span");
|
|
69
|
+
span.classList.add("gvs-filters-active");
|
|
70
|
+
const svg = btn.querySelector("svg");
|
|
71
|
+
if(svg.nextSibling) { btn.insertBefore(span, svg.nextSibling); }
|
|
72
|
+
else { btn.appendChild(span); }
|
|
73
|
+
}
|
|
74
|
+
else if(!val && span) {
|
|
75
|
+
span.remove();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
62
78
|
return btn;
|
|
63
79
|
}
|
|
64
80
|
|
|
@@ -95,6 +111,7 @@ export function createSearchBar(
|
|
|
95
111
|
const input = document.createElement("input");
|
|
96
112
|
input.type = "text";
|
|
97
113
|
input.placeholder = placeholder;
|
|
114
|
+
input.id = `${id}-input`;
|
|
98
115
|
bar.appendChild(input);
|
|
99
116
|
const extendInput = () => {
|
|
100
117
|
bar.classList.remove("gvs-search-bar-reduced");
|
|
@@ -138,7 +155,7 @@ export function createSearchBar(
|
|
|
138
155
|
bar.resetSearch = resetSearch;
|
|
139
156
|
|
|
140
157
|
// Handle result item click
|
|
141
|
-
|
|
158
|
+
input.goItem = (entry) => {
|
|
142
159
|
if(reduced) {
|
|
143
160
|
onResultClick(entry);
|
|
144
161
|
resetSearch();
|
|
@@ -148,6 +165,7 @@ export function createSearchBar(
|
|
|
148
165
|
input.value = entry.title;
|
|
149
166
|
list.innerHTML = "";
|
|
150
167
|
list._toggle(false);
|
|
168
|
+
switchIcon(iconEmpty);
|
|
151
169
|
onResultClick(entry);
|
|
152
170
|
}
|
|
153
171
|
};
|
|
@@ -195,7 +213,7 @@ export function createSearchBar(
|
|
|
195
213
|
listEntry.classList.add("gvs-search-bar-result");
|
|
196
214
|
listEntry.innerHTML = `${entry.title}<br /><small>${entry?.subtitle || ""}</small>`;
|
|
197
215
|
list.appendChild(listEntry);
|
|
198
|
-
listEntry.addEventListener("click", () => goItem(entry));
|
|
216
|
+
listEntry.addEventListener("click", () => input.goItem(entry));
|
|
199
217
|
});
|
|
200
218
|
}).catch(e => {
|
|
201
219
|
console.error(e);
|
|
@@ -479,3 +497,47 @@ export function createLabel(forAttr, text, faIcon = null) {
|
|
|
479
497
|
label.appendChild(document.createTextNode(text));
|
|
480
498
|
return label;
|
|
481
499
|
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Show a grade in a nice, user-friendly way
|
|
503
|
+
* @param {number} grade The obtained grade
|
|
504
|
+
* @returns {string} Nice to display grade display
|
|
505
|
+
*/
|
|
506
|
+
export function showGrade(grade, t) {
|
|
507
|
+
let label = "<span class=\"gvs-grade\">";
|
|
508
|
+
|
|
509
|
+
for(let i=1; i <= grade; i++) {
|
|
510
|
+
label += fat(faStar);
|
|
511
|
+
}
|
|
512
|
+
for(let i=grade+1; i <= 5; i++) {
|
|
513
|
+
label += fat(farStar);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
label += "</span> (";
|
|
517
|
+
if(grade === null) { label += t.gvs.metadata_quality_missing+")"; }
|
|
518
|
+
else { label += grade + "/5)"; }
|
|
519
|
+
return label;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Displays a nice QualityScore
|
|
524
|
+
* @param {number} grade The 1 to 5 grade
|
|
525
|
+
* @returns {Element} The HTML code for showing the grade
|
|
526
|
+
*/
|
|
527
|
+
export function showQualityScore(grade) {
|
|
528
|
+
const span = document.createElement("span");
|
|
529
|
+
|
|
530
|
+
for(let i=1; i <= QUALITYSCORE_VALUES.length; i++) {
|
|
531
|
+
const pv = QUALITYSCORE_VALUES[i-1];
|
|
532
|
+
const sub = document.createElement("span");
|
|
533
|
+
sub.appendChild(document.createTextNode(pv.label));
|
|
534
|
+
sub.classList.add("gvs-qualityscore");
|
|
535
|
+
sub.style.backgroundColor = pv.color;
|
|
536
|
+
if(i === (6-grade)) {
|
|
537
|
+
sub.classList.add("gvs-qualityscore-selected");
|
|
538
|
+
}
|
|
539
|
+
span.appendChild(sub);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
return span;
|
|
543
|
+
}
|
package/src/viewer/URLHash.js
CHANGED
|
@@ -4,6 +4,7 @@ const MAP_FILTERS_JS2URL = {
|
|
|
4
4
|
"type": "pic_type",
|
|
5
5
|
"camera": "camera",
|
|
6
6
|
"theme": "theme",
|
|
7
|
+
"qualityscore": "pic_score",
|
|
7
8
|
};
|
|
8
9
|
const MAP_FILTERS_URL2JS = Object.fromEntries(Object.entries(MAP_FILTERS_JS2URL).map(v => [v[1], v[0]]));
|
|
9
10
|
const UPDATE_HASH_EVENTS = [
|
|
@@ -98,6 +99,10 @@ export default class URLHash extends EventTarget {
|
|
|
98
99
|
hashParts[MAP_FILTERS_JS2URL[k]] = this._viewer._mapFilters[k];
|
|
99
100
|
}
|
|
100
101
|
}
|
|
102
|
+
if(hashParts.pic_score) {
|
|
103
|
+
const mapping = [null, "E", "D", "C", "B", "A"];
|
|
104
|
+
hashParts.pic_score = hashParts.pic_score.map(v => mapping[v]).join("");
|
|
105
|
+
}
|
|
101
106
|
}
|
|
102
107
|
}
|
|
103
108
|
else {
|
|
@@ -158,13 +163,13 @@ export default class URLHash extends EventTarget {
|
|
|
158
163
|
if(keyvals.s) {
|
|
159
164
|
const shortVals = Object.fromEntries(
|
|
160
165
|
keyvals.s
|
|
161
|
-
.split("
|
|
166
|
+
.split(";")
|
|
162
167
|
.map(kv => [kv[0], kv.substring(1)])
|
|
163
168
|
);
|
|
164
169
|
|
|
165
170
|
keyvals = {};
|
|
166
171
|
|
|
167
|
-
// Used letters: b c d e f k m n p s t u v
|
|
172
|
+
// Used letters: b c d e f k m n p q s t u v
|
|
168
173
|
// Focus
|
|
169
174
|
if(shortVals.f === "m") { keyvals.focus = "map"; }
|
|
170
175
|
else if(shortVals.f === "p") { keyvals.focus = "pic"; }
|
|
@@ -202,6 +207,7 @@ export default class URLHash extends EventTarget {
|
|
|
202
207
|
if(shortVals.v === "d") { keyvals.theme = "default"; }
|
|
203
208
|
else if(shortVals.v === "a") { keyvals.theme = "age"; }
|
|
204
209
|
else if(shortVals.v === "t") { keyvals.theme = "type"; }
|
|
210
|
+
else if(shortVals.v === "s") { keyvals.theme = "score"; }
|
|
205
211
|
|
|
206
212
|
// Background
|
|
207
213
|
if(shortVals.b === "s") { keyvals.background = "streets"; }
|
|
@@ -209,6 +215,9 @@ export default class URLHash extends EventTarget {
|
|
|
209
215
|
|
|
210
216
|
// Users
|
|
211
217
|
if(shortVals.u !== "") { keyvals.users = shortVals.u; }
|
|
218
|
+
|
|
219
|
+
// Photoscore
|
|
220
|
+
if(shortVals.q !== "") { keyvals.pic_score = shortVals.q; }
|
|
212
221
|
}
|
|
213
222
|
|
|
214
223
|
return keyvals;
|
|
@@ -332,11 +341,12 @@ export default class URLHash extends EventTarget {
|
|
|
332
341
|
v: (hashParts.theme || "").substring(0, 1),
|
|
333
342
|
b: (hashParts.background || "").substring(0, 1),
|
|
334
343
|
u: hashParts.users,
|
|
344
|
+
q: hashParts.pic_score,
|
|
335
345
|
};
|
|
336
346
|
const short = Object.entries(shortVals)
|
|
337
347
|
.filter(([,v]) => v != undefined && v != "")
|
|
338
348
|
.map(([k,v]) => `${k}${v}`)
|
|
339
|
-
.join("
|
|
349
|
+
.join(";");
|
|
340
350
|
url.hash = `s=${short}`;
|
|
341
351
|
return url;
|
|
342
352
|
}
|
|
@@ -353,6 +363,11 @@ export default class URLHash extends EventTarget {
|
|
|
353
363
|
newMapFilters[MAP_FILTERS_URL2JS[k]] = vals[k];
|
|
354
364
|
}
|
|
355
365
|
}
|
|
366
|
+
if(newMapFilters.qualityscore) {
|
|
367
|
+
let values = newMapFilters.qualityscore.split("");
|
|
368
|
+
const mapping = {"A": 5, "B": 4, "C": 3, "D": 2, "E": 1};
|
|
369
|
+
newMapFilters.qualityscore = values.map(v => mapping[v]);
|
|
370
|
+
}
|
|
356
371
|
return newMapFilters;
|
|
357
372
|
}
|
|
358
373
|
|
package/src/viewer/Widgets.css
CHANGED
|
@@ -207,6 +207,63 @@ span.gvs-input-btn {
|
|
|
207
207
|
width: 100%;
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
+
/* Checkbox looking like buttons */
|
|
211
|
+
.gvs-input-group.gvs-checkbox-btns {
|
|
212
|
+
gap: 0;
|
|
213
|
+
}
|
|
214
|
+
.gvs-checkbox-btns label {
|
|
215
|
+
display: inline-block;
|
|
216
|
+
padding: 2px 7px;
|
|
217
|
+
background: none;
|
|
218
|
+
border: 1px solid var(--widget-border-btn);
|
|
219
|
+
color: var(--widget-font-btn-direct);
|
|
220
|
+
cursor: pointer;
|
|
221
|
+
font-size: 16px;
|
|
222
|
+
text-decoration: none;
|
|
223
|
+
border-left-width: 0px;
|
|
224
|
+
}
|
|
225
|
+
.gvs-checkbox-btns label:hover {
|
|
226
|
+
background-color: var(--widget-bg-hover);
|
|
227
|
+
}
|
|
228
|
+
.gvs-checkbox-btns label:first-of-type {
|
|
229
|
+
border-top-left-radius: 8px;
|
|
230
|
+
border-bottom-left-radius: 8px;
|
|
231
|
+
border-left-width: 1px;
|
|
232
|
+
}
|
|
233
|
+
.gvs-checkbox-btns label:last-of-type {
|
|
234
|
+
border-top-right-radius: 8px;
|
|
235
|
+
border-bottom-right-radius: 8px;
|
|
236
|
+
}
|
|
237
|
+
.gvs-checkbox-btns input[type="checkbox"] { display: none; }
|
|
238
|
+
.gvs-checkbox-btns input[type="checkbox"]:checked + label {
|
|
239
|
+
background-color: var(--widget-bg-active);
|
|
240
|
+
color: var(--widget-font-active);
|
|
241
|
+
}
|
|
242
|
+
.gvs-checkbox-btns input[type="checkbox"]:checked + label:first-of-type {
|
|
243
|
+
border-right-color: white;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/* Input shortcuts */
|
|
247
|
+
.gvs-input-shortcuts {
|
|
248
|
+
margin-top: -10px;
|
|
249
|
+
margin-bottom: 5px;
|
|
250
|
+
}
|
|
251
|
+
.gvs-input-shortcuts button {
|
|
252
|
+
border: none;
|
|
253
|
+
height: 20px;
|
|
254
|
+
line-height: 20px;
|
|
255
|
+
font-size: 11px;
|
|
256
|
+
padding: 0 8px;
|
|
257
|
+
vertical-align: middle;
|
|
258
|
+
background-color: var(--grey-pale);
|
|
259
|
+
color: var(--black);
|
|
260
|
+
border-radius: 10px;
|
|
261
|
+
cursor: pointer;
|
|
262
|
+
}
|
|
263
|
+
.gvs-input-shortcuts button:hover {
|
|
264
|
+
background-color: #d9dcd9;
|
|
265
|
+
}
|
|
266
|
+
|
|
210
267
|
|
|
211
268
|
/* Group */
|
|
212
269
|
.gvs-group {
|
|
@@ -455,6 +512,11 @@ a.gvs-btn { text-decoration: none; }
|
|
|
455
512
|
margin-right: 5px;
|
|
456
513
|
}
|
|
457
514
|
|
|
515
|
+
/* Grades */
|
|
516
|
+
.gvs-grade {
|
|
517
|
+
color: var(--orange);
|
|
518
|
+
}
|
|
519
|
+
|
|
458
520
|
|
|
459
521
|
/***********************************************
|
|
460
522
|
* Per-component styles
|
|
@@ -586,10 +648,25 @@ a.gvs-btn { text-decoration: none; }
|
|
|
586
648
|
|
|
587
649
|
/* Filters */
|
|
588
650
|
#gvs-filter { margin-bottom: 5px; }
|
|
651
|
+
.gvs-filters-active {
|
|
652
|
+
width: 15px;
|
|
653
|
+
height: 15px;
|
|
654
|
+
border-radius: 8px;
|
|
655
|
+
border: 3px solid white;
|
|
656
|
+
position: absolute;
|
|
657
|
+
left: 20px;
|
|
658
|
+
top: 5px;
|
|
659
|
+
background-color: var(--orange);
|
|
660
|
+
}
|
|
589
661
|
#gvs-filter-panel {
|
|
590
662
|
width: 350px;
|
|
591
663
|
max-width: 350px;
|
|
592
664
|
}
|
|
665
|
+
#gvs-filter-panel .gvs-filter-active {
|
|
666
|
+
background-color: var(--widget-bg-active);
|
|
667
|
+
border-color: var(--widget-bg-active);
|
|
668
|
+
color: var(--widget-font-active);
|
|
669
|
+
}
|
|
593
670
|
#gvs-filter-panel input[type=date] {
|
|
594
671
|
min-width: 0;
|
|
595
672
|
flex-grow: 2;
|
|
@@ -597,10 +674,67 @@ a.gvs-btn { text-decoration: none; }
|
|
|
597
674
|
text-align: center;
|
|
598
675
|
}
|
|
599
676
|
#gvs-filter-camera-model, #gvs-filter-search-user { width: 100%; }
|
|
677
|
+
#gvs-filter-search-user.gvs-filter-active input {
|
|
678
|
+
color: var(--widget-font-active);
|
|
679
|
+
background: none;
|
|
680
|
+
}
|
|
600
681
|
#gvs-filter-zoomin {
|
|
601
682
|
text-align: center;
|
|
602
683
|
font-weight: bold;
|
|
603
684
|
margin-bottom: 15px;
|
|
685
|
+
color: var(--orange);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
#gvs-filter-qualityscore {
|
|
689
|
+
gap: 0px;
|
|
690
|
+
justify-content: center;
|
|
691
|
+
height: 42px;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
.gvs-qualityscore, #gvs-filter-qualityscore label {
|
|
695
|
+
font-size: 18px;
|
|
696
|
+
width: 25px;
|
|
697
|
+
height: 30px;
|
|
698
|
+
line-height: 26px;
|
|
699
|
+
display: inline-block;
|
|
700
|
+
border: 1px solid white;
|
|
701
|
+
text-align: center;
|
|
702
|
+
background-color: gray;
|
|
703
|
+
color: rgba(255,255,255,0.9);
|
|
704
|
+
font-family: sans-serif;
|
|
705
|
+
font-weight: bold;
|
|
706
|
+
vertical-align: middle;
|
|
707
|
+
}
|
|
708
|
+
#gvs-filter-qualityscore label { cursor: pointer; }
|
|
709
|
+
#gvs-filter-qualityscore label:hover {
|
|
710
|
+
width: 28px;
|
|
711
|
+
height: 35px;
|
|
712
|
+
line-height: 30px;
|
|
713
|
+
border-radius: 3px;
|
|
714
|
+
font-size: 22px;
|
|
715
|
+
color: white;
|
|
716
|
+
border: 2px solid white;
|
|
717
|
+
}
|
|
718
|
+
.gvs-qualityscore:first-of-type,
|
|
719
|
+
#gvs-filter-qualityscore label:first-of-type {
|
|
720
|
+
border-top-left-radius: 8px;
|
|
721
|
+
border-bottom-left-radius: 8px;
|
|
722
|
+
}
|
|
723
|
+
.gvs-qualityscore:last-of-type,
|
|
724
|
+
#gvs-filter-qualityscore label:last-of-type {
|
|
725
|
+
border-top-right-radius: 8px;
|
|
726
|
+
border-bottom-right-radius: 8px;
|
|
727
|
+
}
|
|
728
|
+
#gvs-filter-qualityscore input[type="checkbox"] { display: none; }
|
|
729
|
+
.gvs-qualityscore-selected,
|
|
730
|
+
#gvs-filter-qualityscore input[type="checkbox"]:checked + label {
|
|
731
|
+
width: 30px;
|
|
732
|
+
height: 42px;
|
|
733
|
+
line-height: 37px;
|
|
734
|
+
border-radius: 8px;
|
|
735
|
+
font-size: 27px;
|
|
736
|
+
color: white;
|
|
737
|
+
border: 2px solid white;
|
|
604
738
|
}
|
|
605
739
|
|
|
606
740
|
|
|
@@ -650,6 +784,11 @@ a.gvs-btn { text-decoration: none; }
|
|
|
650
784
|
margin-right: 5px;
|
|
651
785
|
}
|
|
652
786
|
|
|
787
|
+
#gvs-map-theme-legend-score {
|
|
788
|
+
justify-content: center;
|
|
789
|
+
gap: 0;
|
|
790
|
+
}
|
|
791
|
+
|
|
653
792
|
|
|
654
793
|
/* Player */
|
|
655
794
|
#gvs-widget-player { justify-content: center; }
|