@panoramax/web-viewer 3.2.3 → 4.0.0
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/.gitlab-ci.yml +13 -6
- package/CHANGELOG.md +49 -1
- package/CODE_OF_CONDUCT.md +1 -1
- package/README.md +1 -1
- package/build/editor.html +10 -1
- package/build/index.css +12 -12
- package/build/index.css.map +1 -1
- package/build/index.html +1 -1
- package/build/index.js +2126 -14
- package/build/index.js.map +1 -1
- package/build/map.html +1 -1
- package/build/photo.html +1 -0
- package/build/static/media/atkinson-hyperlegible-next-latin-400-normal..woff +0 -0
- package/build/static/media/atkinson-hyperlegible-next-latin-400-normal..woff2 +0 -0
- package/build/static/media/atkinson-hyperlegible-next-latin-ext-400-normal..woff +0 -0
- package/build/static/media/atkinson-hyperlegible-next-latin-ext-400-normal..woff2 +0 -0
- package/build/viewer.html +12 -1
- package/build/widgets.html +1 -0
- package/config/jest/mocks.js +201 -0
- package/config/paths.js +2 -0
- package/config/webpack.config.js +52 -0
- package/docs/03_URL_settings.md +14 -16
- package/docs/05_Compatibility.md +59 -76
- package/docs/09_Develop.md +46 -11
- package/docs/90_Releases.md +2 -2
- package/docs/images/class_diagram.drawio +60 -45
- package/docs/images/class_diagram.jpg +0 -0
- package/docs/images/screenshot.jpg +0 -0
- package/docs/index.md +135 -0
- package/docs/reference/components/core/Basic.md +196 -0
- package/docs/reference/components/core/CoverageMap.md +210 -0
- package/docs/reference/components/core/Editor.md +224 -0
- package/docs/reference/components/core/PhotoViewer.md +307 -0
- package/docs/reference/components/core/Viewer.md +350 -0
- package/docs/reference/components/layout/BottomDrawer.md +35 -0
- package/docs/reference/components/layout/CorneredGrid.md +29 -0
- package/docs/reference/components/layout/Mini.md +45 -0
- package/docs/reference/components/layout/Tabs.md +45 -0
- package/docs/reference/components/menus/MapBackground.md +32 -0
- package/docs/reference/components/menus/MapFilters.md +15 -0
- package/docs/reference/components/menus/MapLayers.md +15 -0
- package/docs/reference/components/menus/MapLegend.md +15 -0
- package/docs/reference/components/menus/PictureLegend.md +16 -0
- package/docs/reference/components/menus/PictureMetadata.md +15 -0
- package/docs/reference/components/menus/PlayerOptions.md +15 -0
- package/docs/reference/components/menus/QualityScoreDoc.md +15 -0
- package/docs/reference/components/menus/ReportForm.md +15 -0
- package/docs/reference/components/menus/ShareMenu.md +15 -0
- package/docs/reference/components/ui/Button.md +40 -0
- package/docs/reference/components/ui/ButtonGroup.md +36 -0
- package/docs/reference/components/ui/CopyButton.md +38 -0
- package/docs/reference/components/ui/Grade.md +32 -0
- package/docs/reference/components/ui/LinkButton.md +45 -0
- package/docs/reference/components/ui/ListGroup.md +22 -0
- package/docs/reference/components/ui/Loader.md +56 -0
- package/docs/reference/components/ui/Map.md +239 -0
- package/docs/reference/components/ui/MapMore.md +256 -0
- package/docs/reference/components/ui/Photo.md +385 -0
- package/docs/reference/components/ui/Popup.md +56 -0
- package/docs/reference/components/ui/ProgressBar.md +32 -0
- package/docs/reference/components/ui/QualityScore.md +45 -0
- package/docs/reference/components/ui/SearchBar.md +63 -0
- package/docs/reference/components/ui/TogglableGroup.md +39 -0
- package/docs/reference/components/ui/widgets/GeoSearch.md +32 -0
- package/docs/reference/components/ui/widgets/Legend.md +49 -0
- package/docs/reference/components/ui/widgets/MapFiltersButton.md +33 -0
- package/docs/reference/components/ui/widgets/MapLayersButton.md +15 -0
- package/docs/reference/components/ui/widgets/OSMEditors.md +15 -0
- package/docs/reference/components/ui/widgets/PictureLegendActions.md +32 -0
- package/docs/reference/components/ui/widgets/Player.md +33 -0
- package/docs/reference/components/ui/widgets/Zoom.md +15 -0
- package/docs/reference/utils/API.md +334 -0
- package/docs/reference/utils/InitParameters.md +68 -0
- package/docs/reference/utils/URLHandler.md +107 -0
- package/docs/reference.md +79 -0
- package/docs/shortcuts.md +11 -0
- package/docs/tutorials/aerial_imagery.md +19 -0
- package/docs/tutorials/authentication.md +10 -0
- package/docs/tutorials/custom_widgets.md +59 -0
- package/docs/tutorials/map_style.md +39 -0
- package/docs/tutorials/migrate_v4.md +153 -0
- package/docs/tutorials/synced_coverage.md +43 -0
- package/mkdocs.yml +66 -5
- package/package.json +22 -17
- package/public/editor.html +21 -29
- package/public/index.html +17 -12
- package/public/map.html +19 -18
- package/public/photo.html +55 -0
- package/public/viewer.html +22 -26
- package/public/widgets.html +306 -0
- package/scripts/doc.js +79 -0
- package/src/components/core/Basic.css +48 -0
- package/src/components/core/Basic.js +349 -0
- package/src/components/core/CoverageMap.css +9 -0
- package/src/components/core/CoverageMap.js +139 -0
- package/src/components/core/Editor.css +23 -0
- package/src/components/core/Editor.js +390 -0
- package/src/components/core/PhotoViewer.css +48 -0
- package/src/components/core/PhotoViewer.js +499 -0
- package/src/components/core/Viewer.css +98 -0
- package/src/components/core/Viewer.js +564 -0
- package/src/components/core/index.js +12 -0
- package/src/components/index.js +13 -0
- package/src/components/layout/BottomDrawer.js +257 -0
- package/src/components/layout/CorneredGrid.js +112 -0
- package/src/components/layout/Mini.js +117 -0
- package/src/components/layout/Tabs.js +133 -0
- package/src/components/layout/index.js +9 -0
- package/src/components/menus/MapBackground.js +106 -0
- package/src/components/menus/MapFilters.js +400 -0
- package/src/components/menus/MapLayers.js +143 -0
- package/src/components/menus/MapLegend.js +34 -0
- package/src/components/menus/PictureLegend.js +253 -0
- package/src/components/menus/PictureMetadata.js +317 -0
- package/src/components/menus/PlayerOptions.js +95 -0
- package/src/components/menus/QualityScoreDoc.js +36 -0
- package/src/components/menus/ReportForm.js +133 -0
- package/src/components/menus/Share.js +100 -0
- package/src/components/menus/index.js +15 -0
- package/src/components/styles.js +383 -0
- package/src/components/ui/Button.js +77 -0
- package/src/components/ui/ButtonGroup.css +57 -0
- package/src/components/ui/ButtonGroup.js +68 -0
- package/src/components/ui/CopyButton.js +106 -0
- package/src/components/ui/Grade.js +54 -0
- package/src/components/ui/LinkButton.js +67 -0
- package/src/components/ui/ListGroup.js +66 -0
- package/src/components/ui/Loader.js +203 -0
- package/src/components/{Map.css → ui/Map.css} +5 -17
- package/src/components/{Map.js → ui/Map.js} +148 -156
- package/src/components/ui/MapMore.js +324 -0
- package/src/components/{Photo.css → ui/Photo.css} +6 -6
- package/src/components/{Photo.js → ui/Photo.js} +313 -101
- package/src/components/ui/Popup.js +145 -0
- package/src/components/ui/ProgressBar.js +104 -0
- package/src/components/ui/QualityScore.js +147 -0
- package/src/components/ui/SearchBar.js +367 -0
- package/src/components/ui/TogglableGroup.js +157 -0
- package/src/components/ui/index.js +22 -0
- package/src/components/ui/widgets/GeoSearch.css +21 -0
- package/src/components/ui/widgets/GeoSearch.js +139 -0
- package/src/components/ui/widgets/Legend.js +113 -0
- package/src/components/ui/widgets/MapFiltersButton.js +104 -0
- package/src/components/ui/widgets/MapLayersButton.js +79 -0
- package/src/components/ui/widgets/OSMEditors.js +155 -0
- package/src/components/ui/widgets/PictureLegendActions.js +117 -0
- package/src/components/ui/widgets/Player.css +7 -0
- package/src/components/ui/widgets/Player.js +151 -0
- package/src/components/ui/widgets/Zoom.js +82 -0
- package/src/components/ui/widgets/index.js +13 -0
- package/src/img/loader_base.jpg +0 -0
- package/src/img/panoramax.svg +13 -0
- package/src/img/switch_big.svg +20 -10
- package/src/index.js +7 -9
- package/src/translations/br.json +1 -0
- package/src/translations/da.json +38 -15
- package/src/translations/de.json +5 -3
- package/src/translations/en.json +35 -15
- package/src/translations/eo.json +38 -15
- package/src/translations/es.json +1 -1
- package/src/translations/fr.json +36 -16
- package/src/translations/hu.json +1 -1
- package/src/translations/it.json +39 -16
- package/src/translations/ja.json +182 -1
- package/src/translations/nl.json +106 -6
- package/src/translations/pl.json +1 -1
- package/src/translations/sv.json +182 -0
- package/src/translations/zh_Hant.json +35 -14
- package/src/utils/API.js +109 -49
- package/src/utils/InitParameters.js +388 -0
- package/src/utils/PhotoAdapter.js +1 -0
- package/src/utils/URLHandler.js +362 -0
- package/src/utils/geocoder.js +152 -0
- package/src/utils/{I18n.js → i18n.js} +7 -3
- package/src/utils/index.js +11 -0
- package/src/utils/{Map.js → map.js} +256 -77
- package/src/utils/picture.js +442 -0
- package/src/utils/utils.js +324 -0
- package/src/utils/widgets.js +55 -0
- package/tests/components/core/Basic.test.js +121 -0
- package/tests/components/core/BasicMock.js +25 -0
- package/tests/components/core/CoverageMap.test.js +20 -0
- package/tests/components/core/Editor.test.js +20 -0
- package/tests/components/core/PhotoViewer.test.js +57 -0
- package/tests/components/core/Viewer.test.js +84 -0
- package/tests/components/core/__snapshots__/PhotoViewer.test.js.snap +73 -0
- package/tests/components/core/__snapshots__/Viewer.test.js.snap +145 -0
- package/tests/components/ui/CopyButton.test.js +52 -0
- package/tests/components/ui/Loader.test.js +55 -0
- package/tests/components/{Map.test.js → ui/Map.test.js} +73 -61
- package/tests/components/{Photo.test.js → ui/Photo.test.js} +97 -63
- package/tests/components/ui/Popup.test.js +26 -0
- package/tests/components/ui/QualityScore.test.js +18 -0
- package/tests/components/ui/SearchBar.test.js +110 -0
- package/tests/components/ui/__snapshots__/CopyButton.test.js.snap +33 -0
- package/tests/components/ui/__snapshots__/Loader.test.js.snap +56 -0
- package/tests/components/{__snapshots__ → ui/__snapshots__}/Map.test.js.snap +11 -38
- package/tests/components/{__snapshots__ → ui/__snapshots__}/Photo.test.js.snap +70 -6
- package/tests/components/ui/__snapshots__/Popup.test.js.snap +29 -0
- package/tests/components/ui/__snapshots__/QualityScore.test.js.snap +11 -0
- package/tests/components/ui/__snapshots__/SearchBar.test.js.snap +65 -0
- package/tests/utils/API.test.js +83 -83
- package/tests/utils/InitParameters.test.js +499 -0
- package/tests/utils/URLHandler.test.js +401 -0
- package/tests/utils/__snapshots__/API.test.js.snap +10 -0
- package/tests/utils/__snapshots__/URLHandler.test.js.snap +21 -0
- package/tests/utils/__snapshots__/{Map.test.js.snap → geocoder.test.js.snap} +1 -1
- package/tests/utils/__snapshots__/map.test.js.snap +11 -0
- package/tests/utils/__snapshots__/picture.test.js.snap +327 -0
- package/tests/utils/__snapshots__/widgets.test.js.snap +19 -0
- package/tests/utils/geocoder.test.js +37 -0
- package/tests/utils/{I18n.test.js → i18n.test.js} +8 -8
- package/tests/utils/map.test.js +126 -0
- package/tests/utils/picture.test.js +745 -0
- package/tests/utils/utils.test.js +288 -0
- package/tests/utils/widgets.test.js +31 -0
- package/docs/01_Start.md +0 -149
- package/docs/02_Usage.md +0 -831
- package/docs/04_Advanced_examples.md +0 -216
- package/src/Editor.css +0 -37
- package/src/Editor.js +0 -361
- package/src/StandaloneMap.js +0 -114
- package/src/Viewer.css +0 -203
- package/src/Viewer.js +0 -1246
- package/src/components/CoreView.css +0 -70
- package/src/components/CoreView.js +0 -175
- package/src/components/Loader.css +0 -74
- package/src/components/Loader.js +0 -120
- package/src/img/loader_hd.jpg +0 -0
- package/src/utils/Exif.js +0 -193
- package/src/utils/Utils.js +0 -631
- package/src/utils/Widgets.js +0 -562
- package/src/viewer/URLHash.js +0 -469
- package/src/viewer/Widgets.css +0 -880
- package/src/viewer/Widgets.js +0 -1470
- package/tests/Editor.test.js +0 -126
- package/tests/StandaloneMap.test.js +0 -45
- package/tests/Viewer.test.js +0 -366
- package/tests/__snapshots__/Editor.test.js.snap +0 -298
- package/tests/__snapshots__/StandaloneMap.test.js.snap +0 -30
- package/tests/__snapshots__/Viewer.test.js.snap +0 -195
- package/tests/components/CoreView.test.js +0 -92
- package/tests/components/Loader.test.js +0 -38
- package/tests/components/__snapshots__/Loader.test.js.snap +0 -15
- package/tests/utils/Exif.test.js +0 -124
- package/tests/utils/Map.test.js +0 -113
- package/tests/utils/Utils.test.js +0 -300
- package/tests/utils/Widgets.test.js +0 -107
- package/tests/utils/__snapshots__/Exif.test.js.snap +0 -43
- package/tests/utils/__snapshots__/Utils.test.js.snap +0 -41
- package/tests/utils/__snapshots__/Widgets.test.js.snap +0 -44
- package/tests/viewer/URLHash.test.js +0 -559
- package/tests/viewer/Widgets.test.js +0 -127
- package/tests/viewer/__snapshots__/URLHash.test.js.snap +0 -108
- package/tests/viewer/__snapshots__/Widgets.test.js.snap +0 -403
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
export const BASE_PANORAMA_ID = "geovisio-fake-id-0";
|
|
2
|
+
|
|
3
|
+
export const COLORS = {
|
|
4
|
+
BASE: "#FF6F00",
|
|
5
|
+
SELECTED: "#1E88E5",
|
|
6
|
+
HIDDEN: "#34495E",
|
|
7
|
+
NEXT: "#ffab40",
|
|
8
|
+
|
|
9
|
+
QUALI_1: "#00695C", // 360
|
|
10
|
+
QUALI_2: "#fd8d3c", // flat
|
|
11
|
+
|
|
12
|
+
PALETTE_1: "#fecc5c", // Oldest
|
|
13
|
+
PALETTE_2: "#fd8d3c",
|
|
14
|
+
PALETTE_3: "#f03b20",
|
|
15
|
+
PALETTE_4: "#bd0026" // Newest
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const COLORS_HEX = Object.fromEntries(Object.entries(COLORS).map(e => {
|
|
19
|
+
e[1] = parseInt(e[1].slice(1), 16);
|
|
20
|
+
return e;
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
export const QUALITYSCORE_VALUES = [
|
|
24
|
+
{ color: "#007f4e", label: "A" },
|
|
25
|
+
{ color: "#72b043", label: "B" },
|
|
26
|
+
{ color: "#b5be2f", label: "C" },
|
|
27
|
+
{ color: "#f8cc1b", label: "D" },
|
|
28
|
+
{ color: "#f6a020", label: "E" },
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
export const QUALITYSCORE_RES_FLAT_VALUES = [1, 10, 2, 15, 3, 30, 4]; // Grade, < Px/FOV value
|
|
32
|
+
export const QUALITYSCORE_RES_360_VALUES = [3, 15, 4, 30, 5]; // Grade, < Px/FOV value
|
|
33
|
+
export const QUALITYSCORE_GPS_VALUES = [5, 1.01, 4, 2.01, 3, 5.01, 2, 10.01, 1]; // Grade, < Meters value
|
|
34
|
+
export const QUALITYSCORE_POND_RES = 4/5;
|
|
35
|
+
export const QUALITYSCORE_POND_GPS = 1/5;
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Checks if a picture or sequence ID is kinda-null.
|
|
40
|
+
* @param {string|null|undefined} id The ID to check
|
|
41
|
+
* @returns True if null-like
|
|
42
|
+
*/
|
|
43
|
+
export function isNullId(id) {
|
|
44
|
+
return [null, undefined, "", BASE_PANORAMA_ID].includes(id);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Find the grade associated to an input Quality Score definition.
|
|
49
|
+
* @param {number[]} ranges The QUALITYSCORE_*_VALUES definition
|
|
50
|
+
* @param {number} value The picture value
|
|
51
|
+
* @return {number} The corresponding grade (1 to 5, or null if missing)
|
|
52
|
+
* @private
|
|
53
|
+
*/
|
|
54
|
+
export function getGrade(ranges, value) {
|
|
55
|
+
if(value === null || value === undefined || value === "") { return null; }
|
|
56
|
+
|
|
57
|
+
// Read each pair from table (grade, reference value)
|
|
58
|
+
for(let i = 0; i < ranges.length; i += 2) {
|
|
59
|
+
const grade = ranges[i];
|
|
60
|
+
const limit = ranges[i+1];
|
|
61
|
+
|
|
62
|
+
// Send grade if value is under limit
|
|
63
|
+
if (value < limit) { return grade;}
|
|
64
|
+
}
|
|
65
|
+
// Otherwise, send last grade
|
|
66
|
+
return ranges[ranges.length - 1];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get cartesian distance between two points
|
|
71
|
+
* @param {number[]} from Start [x,y] coordinates
|
|
72
|
+
* @param {number[]} to End [x,y] coordinates
|
|
73
|
+
* @returns {number} The distance
|
|
74
|
+
* @private
|
|
75
|
+
*/
|
|
76
|
+
export function getDistance(from, to) {
|
|
77
|
+
const dx = from[0] - to[0];
|
|
78
|
+
const dy = from[1] - to[1];
|
|
79
|
+
return Math.sqrt(dx*dx + dy*dy);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Transforms a Base64 SVG string into a DOM img element.
|
|
84
|
+
* @param {string} svg The SVG as Base64 string
|
|
85
|
+
* @returns {Element} The DOM image element
|
|
86
|
+
* @private
|
|
87
|
+
*/
|
|
88
|
+
export function svgToPSVLink(svg, fillColor) {
|
|
89
|
+
try {
|
|
90
|
+
const svgStr = atob(svg.replace(/^data:image\/svg\+xml;base64,/, ""));
|
|
91
|
+
const svgXml = (new DOMParser()).parseFromString(svgStr, "image/svg+xml").childNodes[0];
|
|
92
|
+
const btn = document.createElement("button");
|
|
93
|
+
btn.appendChild(svgXml);
|
|
94
|
+
btn.classList.add("pnx-psv-tour-arrows", "pnx-print-hidden");
|
|
95
|
+
btn.style.color = fillColor;
|
|
96
|
+
return btn;
|
|
97
|
+
}
|
|
98
|
+
catch(e) {
|
|
99
|
+
const img = document.createElement("img");
|
|
100
|
+
img.src = svg;
|
|
101
|
+
img.alt = "";
|
|
102
|
+
return img;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Clones a model PSV link
|
|
108
|
+
* @private
|
|
109
|
+
*/
|
|
110
|
+
export function getArrow(a) {
|
|
111
|
+
const d = a.cloneNode(true);
|
|
112
|
+
d.addEventListener("pointerup", () => d.classList.add("pnx-clicked"));
|
|
113
|
+
return d;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Get direction based on angle
|
|
118
|
+
* @param {number[]} from Start [x,y] coordinates
|
|
119
|
+
* @param {number[]} to End [x,y] coordinates
|
|
120
|
+
* @returns {number} The azimuth, from 0 to 360°
|
|
121
|
+
* @private
|
|
122
|
+
*/
|
|
123
|
+
export function getAzimuth(from, to) {
|
|
124
|
+
return (Math.atan2(to[0] - from[0], to[1] - from[1]) * (180 / Math.PI) + 360) % 360;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Computes relative heading for a single picture, based on its metadata
|
|
129
|
+
* @param {*} m The picture metadata
|
|
130
|
+
* @returns {number} The relative heading
|
|
131
|
+
* @private
|
|
132
|
+
*/
|
|
133
|
+
export function getRelativeHeading(m) {
|
|
134
|
+
if(!m) { throw new Error("No picture selected"); }
|
|
135
|
+
|
|
136
|
+
let prevSegDir, nextSegDir;
|
|
137
|
+
const currHeading = m.properties["view:azimuth"];
|
|
138
|
+
|
|
139
|
+
// Previous picture GPS coordinates
|
|
140
|
+
if(m?.sequence?.prevPic) {
|
|
141
|
+
const prevLink = m?.links?.find(l => l.nodeId === m.sequence.prevPic);
|
|
142
|
+
if(prevLink) {
|
|
143
|
+
prevSegDir = (((currHeading - getAzimuth(prevLink.gps, m.gps)) + 180) % 360) - 180;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Next picture GPS coordinates
|
|
148
|
+
if(m?.sequence?.nextPic) {
|
|
149
|
+
const nextLink = m?.links?.find(l => l.nodeId === m.sequence.nextPic);
|
|
150
|
+
if(nextLink) {
|
|
151
|
+
nextSegDir = (((currHeading - getAzimuth(m.gps, nextLink.gps)) + 180) % 360) - 180;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return prevSegDir !== undefined ? prevSegDir : (nextSegDir !== undefined ? nextSegDir : 0);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get direction based on angle
|
|
160
|
+
* @param {number[]} from Start [x,y] coordinates
|
|
161
|
+
* @param {number[]} to End [x,y] coordinates
|
|
162
|
+
* @returns {string} Direction (N/ENE/ESE/S/WSW/WNW)
|
|
163
|
+
* @private
|
|
164
|
+
*/
|
|
165
|
+
export function getSimplifiedAngle(from, to) {
|
|
166
|
+
const angle = Math.atan2(to[0] - from[0], to[1] - from[1]) * (180 / Math.PI); // -180 to 180°
|
|
167
|
+
|
|
168
|
+
// 6 directions version
|
|
169
|
+
if (Math.abs(angle) < 30) { return "N"; }
|
|
170
|
+
else if (angle >= 30 && angle < 90) { return "ENE"; }
|
|
171
|
+
else if (angle >= 90 && angle < 150) { return "ESE"; }
|
|
172
|
+
else if (Math.abs(angle) >= 150) { return "S"; }
|
|
173
|
+
else if (angle <= -30 && angle > -90) { return "WNW"; }
|
|
174
|
+
else if (angle <= -90 && angle > -150) { return "WSW"; }
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Converts result from getPosition or position-updated event into x/y/z coordinates
|
|
179
|
+
*
|
|
180
|
+
* @param {object} pos pitch/yaw as given by PSV
|
|
181
|
+
* @param {number} zoom zoom as given by PSV
|
|
182
|
+
* @returns {object} Coordinates as x/y in degrees and zoom as given by PSV
|
|
183
|
+
* @private
|
|
184
|
+
*/
|
|
185
|
+
export function positionToXYZ(pos, zoom = undefined) {
|
|
186
|
+
const res = {
|
|
187
|
+
x: pos.yaw * (180/Math.PI),
|
|
188
|
+
y: pos.pitch * (180/Math.PI)
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
if(zoom !== undefined) { res.z = zoom; }
|
|
192
|
+
return res;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Converts x/y/z coordinates into PSV position (lat/lon/zoom)
|
|
197
|
+
*
|
|
198
|
+
* @param {number} x The X coordinate (in degrees)
|
|
199
|
+
* @param {number} y The Y coordinate (in degrees)
|
|
200
|
+
* @param {number} z The zoom level (0-100)
|
|
201
|
+
* @returns {object} Position coordinates as yaw/pitch/zoom
|
|
202
|
+
* @private
|
|
203
|
+
*/
|
|
204
|
+
export function xyzToPosition(x, y, z) {
|
|
205
|
+
return {
|
|
206
|
+
yaw: x / (180/Math.PI),
|
|
207
|
+
pitch: y / (180/Math.PI),
|
|
208
|
+
zoom: z
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Get the query string for JOSM to load current picture area
|
|
214
|
+
* @returns {string} The query string, or null if not available
|
|
215
|
+
* @private
|
|
216
|
+
*/
|
|
217
|
+
export function josmBboxParameters(meta) {
|
|
218
|
+
if(meta) {
|
|
219
|
+
const coords = meta.gps;
|
|
220
|
+
const heading = meta?.properties?.["view:azimuth"];
|
|
221
|
+
const delta = 0.0002;
|
|
222
|
+
const values = {
|
|
223
|
+
left: coords[0] - (heading === null || heading >= 180 ? delta : 0),
|
|
224
|
+
right: coords[0] + (heading === null || heading <= 180 ? delta : 0),
|
|
225
|
+
top: coords[1] + (heading === null || heading <= 90 || heading >= 270 ? delta : 0),
|
|
226
|
+
bottom: coords[1] - (heading === null || (heading >= 90 && heading <= 270) ? delta : 0),
|
|
227
|
+
changeset_source: "Panoramax"
|
|
228
|
+
};
|
|
229
|
+
return Object.entries(values).map(e => e.join("=")).join("&");
|
|
230
|
+
}
|
|
231
|
+
else { return null; }
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Check if code runs in an iframe or in a classic page.
|
|
236
|
+
* @returns {boolean} True if running in iframe
|
|
237
|
+
* @private
|
|
238
|
+
*/
|
|
239
|
+
export function isInIframe() {
|
|
240
|
+
try {
|
|
241
|
+
return window.self !== window.top;
|
|
242
|
+
} catch(e) {
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
const INTERNET_FAST_THRESHOLD = 10; // MBit/s
|
|
249
|
+
const INTERNET_FAST_STORAGE = "pnx-internet-fast";
|
|
250
|
+
const INTERNET_FAST_TESTFILE = "https://panoramax.openstreetmap.fr/images/05/ca/2c/98/0111-4baf-b6f3-587bb8847d2e.jpg";
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Check if Internet connection is high-speed or not.
|
|
254
|
+
* @returns {Promise} Resolves on true if high-speed.
|
|
255
|
+
* @private
|
|
256
|
+
*/
|
|
257
|
+
export function isInternetFast() {
|
|
258
|
+
// Check if downlink property is available
|
|
259
|
+
try {
|
|
260
|
+
const speed = navigator.connection.downlink; // MBit/s
|
|
261
|
+
return Promise.resolve(speed >= INTERNET_FAST_THRESHOLD);
|
|
262
|
+
}
|
|
263
|
+
// Fallback for other browsers
|
|
264
|
+
catch(e) {
|
|
265
|
+
try {
|
|
266
|
+
// Check if test has been done before and stored
|
|
267
|
+
const isFast = sessionStorage.getItem(INTERNET_FAST_STORAGE);
|
|
268
|
+
if(["true", "false"].includes(isFast)) {
|
|
269
|
+
return Promise.resolve(isFast === "true");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Run download testing
|
|
273
|
+
const startTime = (new Date()).getTime();
|
|
274
|
+
return fetch(INTERNET_FAST_TESTFILE+"?nocache="+startTime)
|
|
275
|
+
.then(async res => [res, await res.blob()])
|
|
276
|
+
.then(([res, blob]) => {
|
|
277
|
+
const size = parseInt(res.headers.get("Content-Length") || blob.size); // Bytes
|
|
278
|
+
const endTime = (new Date()).getTime();
|
|
279
|
+
const duration = (endTime - startTime) / 1000; // Transfer time in seconds
|
|
280
|
+
const speed = (size * 8 / 1024 / 1024) / duration; // MBits/s
|
|
281
|
+
const isFast = speed >= INTERNET_FAST_THRESHOLD;
|
|
282
|
+
sessionStorage.setItem(INTERNET_FAST_STORAGE, isFast ? "true" : "false");
|
|
283
|
+
return isFast;
|
|
284
|
+
})
|
|
285
|
+
.catch(e => {
|
|
286
|
+
console.warn("Failed to run speedtest", e);
|
|
287
|
+
return false;
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
// Fallback for browser blocking third-party downloads or sessionStorage
|
|
291
|
+
catch(e) {
|
|
292
|
+
return Promise.resolve(false);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Get a cookie value
|
|
299
|
+
* @param {str} name The cookie name
|
|
300
|
+
* @returns {str} The cookie value, or null if not found
|
|
301
|
+
* @private
|
|
302
|
+
*/
|
|
303
|
+
export function getCookie(name) {
|
|
304
|
+
const parts = document.cookie
|
|
305
|
+
?.split(";")
|
|
306
|
+
?.find((row) => row.trimStart().startsWith(`${name}=`))
|
|
307
|
+
?.split("=");
|
|
308
|
+
if(!parts) { return undefined; }
|
|
309
|
+
parts.shift();
|
|
310
|
+
return parts.join("=");
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Checks if an user account exists
|
|
315
|
+
* @returns {object} Object like {"id", "name"} or null if no authenticated account
|
|
316
|
+
* @private
|
|
317
|
+
*/
|
|
318
|
+
export function getUserAccount() {
|
|
319
|
+
const session = getCookie("session");
|
|
320
|
+
const user_id = getCookie("user_id");
|
|
321
|
+
const user_name = getCookie("user_name");
|
|
322
|
+
|
|
323
|
+
return (session && user_id && user_name) ? { id: user_id, name: user_name } : null;
|
|
324
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Every single icon imported separately to reduce bundle size
|
|
2
|
+
import { icon } from "@fortawesome/fontawesome-svg-core";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Transform Font Awesome icon definition into HTML element
|
|
7
|
+
* @param {IconDefinition} i The icon to use
|
|
8
|
+
* @param {object} [o] [FontAwesome icon parameters](https://origin.fontawesome.com/docs/apis/javascript/methods#icon-icondefinition-params)
|
|
9
|
+
* @returns {Element} HTML element
|
|
10
|
+
* @private
|
|
11
|
+
*/
|
|
12
|
+
export function fa(i, o) {
|
|
13
|
+
return icon(i, o).node[0];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create a web component with its initial properties
|
|
18
|
+
* @private
|
|
19
|
+
*/
|
|
20
|
+
export function createWebComp(tag, props = {}) {
|
|
21
|
+
const wc = document.createElement(tag);
|
|
22
|
+
Object.entries(props).forEach(([k,v]) => {
|
|
23
|
+
if(k.startsWith("_")) { wc[k] = v; }
|
|
24
|
+
else if(k.startsWith("fn")) { wc[k.substring(2)] = v; }
|
|
25
|
+
else if(k.startsWith("on")) { wc.addEventListener(k.substring(2), v); }
|
|
26
|
+
else if(v) { wc.setAttribute(k, v); }
|
|
27
|
+
});
|
|
28
|
+
return wc;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Listen to parent events that may lead to a menu closure
|
|
33
|
+
* @private
|
|
34
|
+
*/
|
|
35
|
+
export function listenForMenuClosure(me, callback) {
|
|
36
|
+
// Other menu opened
|
|
37
|
+
me._parent?.addEventListener("menu-opened", e => {
|
|
38
|
+
if(e.detail.menu != me) { callback(); }
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Map click
|
|
42
|
+
me._parent?.onceMapReady?.().then(() => {
|
|
43
|
+
me._parent.map?.on?.("click", () => callback());
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Photo click
|
|
47
|
+
me._parent?.oncePSVReady?.().then(() => {
|
|
48
|
+
me._parent.psv.addEventListener("click", () => callback());
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Legend click
|
|
52
|
+
me._parent?.onceReady?.().then(() => {
|
|
53
|
+
me._parent.legend?.addEventListener("click", () => callback());
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import Basic from "../../../src/components/core/Basic";
|
|
2
|
+
import API from "../../../src/utils/API";
|
|
3
|
+
|
|
4
|
+
jest.mock("../../../src/utils/API");
|
|
5
|
+
jest.mock("../../../src/utils/i18n");
|
|
6
|
+
jest.mock("../../../src/utils/widgets");
|
|
7
|
+
jest.mock("../../../src/utils/utils");
|
|
8
|
+
jest.mock("../../../package.json", () => ({ version: "1.0.0", repository: { url: "https://example.com" } }));
|
|
9
|
+
|
|
10
|
+
let basic;
|
|
11
|
+
global.console = { info: jest.fn(), error: jest.fn(), warn: jest.fn(), log: global.console.log };
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
basic = new Basic(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
jest.clearAllMocks();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe("constructor", () => {
|
|
22
|
+
it("should initialize with default values", () => {
|
|
23
|
+
expect(basic.users).toEqual(["geovisio"]);
|
|
24
|
+
expect(basic.mapstyle).toBeDefined();
|
|
25
|
+
expect(basic.lang).toBeNull();
|
|
26
|
+
expect(basic.endpoint).toBeNull();
|
|
27
|
+
expect(basic.picture).toBeNull();
|
|
28
|
+
expect(basic.sequence).toBeNull();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should log version info on initialization", () => {
|
|
32
|
+
new Basic(true);
|
|
33
|
+
expect(console.info).toHaveBeenCalledWith(expect.stringContaining("Panoramax Basic - Version 1.0.0"));
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should set up API when endpoint is provided", async () => {
|
|
37
|
+
basic.endpoint = "https://api.example.com";
|
|
38
|
+
basic.attributeChangedCallback("endpoint", null, "https://api.example.com");
|
|
39
|
+
expect(API).toHaveBeenCalledWith("https://api.example.com", expect.any(Object));
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("attributeChangedCallback", () => {
|
|
44
|
+
it("should dispatch select event when picture or sequence changes", () => {
|
|
45
|
+
const eventSpy = jest.spyOn(basic, "dispatchEvent");
|
|
46
|
+
basic.attributeChangedCallback("picture", null, "new-pic-id");
|
|
47
|
+
expect(eventSpy).toHaveBeenCalledWith(expect.any(CustomEvent));
|
|
48
|
+
eventSpy.mockRestore();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe("getClassName", () => {
|
|
53
|
+
it("works", () => {
|
|
54
|
+
expect(basic.getClassName()).toBe("Basic");
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("select", () => {
|
|
59
|
+
it("works", () => {
|
|
60
|
+
basic.select("new-seq-id", "new-pic-id");
|
|
61
|
+
expect(basic.picture).toBe("new-pic-id");
|
|
62
|
+
expect(basic.sequence).toBe("new-seq-id");
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe("offsetWidth", () => {
|
|
67
|
+
it("works", () => {
|
|
68
|
+
basic.offsetWidth = 500;
|
|
69
|
+
expect(basic.isWidthSmall()).toBe(true);
|
|
70
|
+
basic.offsetWidth = 600;
|
|
71
|
+
expect(basic.isWidthSmall()).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("offsetHeight", () => {
|
|
76
|
+
it("works", () => {
|
|
77
|
+
basic.offsetHeight = 300;
|
|
78
|
+
expect(basic.isHeightSmall()).toBe(true);
|
|
79
|
+
basic.offsetHeight = 500;
|
|
80
|
+
expect(basic.isHeightSmall()).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("getSubComponentsNames", () => {
|
|
85
|
+
it("works", () => {
|
|
86
|
+
expect(basic.getSubComponentsNames()).toEqual(["loader", "api"]);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe("addEventListener", () => {
|
|
91
|
+
it("should add event listener for standard events", () => {
|
|
92
|
+
const listener = jest.fn();
|
|
93
|
+
basic.addEventListener("ready", listener);
|
|
94
|
+
basic.dispatchEvent(new CustomEvent("ready"));
|
|
95
|
+
expect(listener).toHaveBeenCalled();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should add event listener with options", () => {
|
|
99
|
+
const listener = jest.fn();
|
|
100
|
+
basic.addEventListener("once-event", listener, { once: true });
|
|
101
|
+
basic.dispatchEvent(new CustomEvent("once-event"));
|
|
102
|
+
basic.dispatchEvent(new CustomEvent("once-event"));
|
|
103
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("should wait for sub-component to be available", () => {
|
|
107
|
+
const listener = jest.fn();
|
|
108
|
+
global.setTimeout = jest.fn();
|
|
109
|
+
basic.getSubComponentsNames = () => ["loader", "api", "map"];
|
|
110
|
+
basic.map = null;
|
|
111
|
+
basic.addEventListener("map:move", listener);
|
|
112
|
+
expect(global.setTimeout).toHaveBeenCalled();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("adds listener to basic if no sub-component matches prefix", () => {
|
|
116
|
+
const listener = jest.fn();
|
|
117
|
+
basic.addEventListener("unknown:event", listener);
|
|
118
|
+
basic.dispatchEvent(new CustomEvent("unknown:event"));
|
|
119
|
+
expect(listener).toHaveBeenCalled();
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
jest.mock("../../../src/components/core/Basic", () => (
|
|
2
|
+
class Basic extends EventTarget {
|
|
3
|
+
constructor() {
|
|
4
|
+
super();
|
|
5
|
+
this.loader = { setAttribute: jest.fn() };
|
|
6
|
+
this.api = {
|
|
7
|
+
getMapStyle: () => ({}),
|
|
8
|
+
_getMapRequestTransform: () => ({}),
|
|
9
|
+
_endpoints: {},
|
|
10
|
+
};
|
|
11
|
+
this._t = { maplibre: {}, psv: {} };
|
|
12
|
+
}
|
|
13
|
+
isWidthSmall() { return false; }
|
|
14
|
+
isHeightSmall() { return false; }
|
|
15
|
+
getSubComponentsNames() {
|
|
16
|
+
return ["loader", "api"];
|
|
17
|
+
}
|
|
18
|
+
onceAPIReady() {
|
|
19
|
+
return Promise.resolve();
|
|
20
|
+
}
|
|
21
|
+
static GetJSONConverter() {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
));
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import CoverageMap from "../../../src/components/core/CoverageMap";
|
|
2
|
+
import "./BasicMock";
|
|
3
|
+
|
|
4
|
+
let cm;
|
|
5
|
+
global.console = { info: jest.fn(), error: jest.fn(), warn: jest.fn(), log: global.console.log };
|
|
6
|
+
|
|
7
|
+
beforeEach(() => cm = new CoverageMap());
|
|
8
|
+
afterEach(() => jest.clearAllMocks());
|
|
9
|
+
|
|
10
|
+
describe("getClassName", () => {
|
|
11
|
+
it("works", () => {
|
|
12
|
+
expect(cm.getClassName()).toBe("CoverageMap");
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe("getSubComponentsNames", () => {
|
|
17
|
+
it("works", () => {
|
|
18
|
+
expect(cm.getSubComponentsNames()).toEqual(["loader", "api", "map"]);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import Editor from "../../../src/components/core/Editor";
|
|
2
|
+
import "./BasicMock";
|
|
3
|
+
|
|
4
|
+
let comp;
|
|
5
|
+
global.console = { info: jest.fn(), error: jest.fn(), warn: jest.fn(), log: global.console.log };
|
|
6
|
+
|
|
7
|
+
beforeEach(() => comp = new Editor());
|
|
8
|
+
afterEach(() => jest.clearAllMocks());
|
|
9
|
+
|
|
10
|
+
describe("getClassName", () => {
|
|
11
|
+
it("works", () => {
|
|
12
|
+
expect(comp.getClassName()).toBe("Editor");
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe("getSubComponentsNames", () => {
|
|
17
|
+
it("works", () => {
|
|
18
|
+
expect(comp.getSubComponentsNames()).toEqual(["loader", "api", "map", "psv"]);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import PhotoViewer from "../../../src/components/core/PhotoViewer";
|
|
2
|
+
import "./BasicMock";
|
|
3
|
+
|
|
4
|
+
let comp;
|
|
5
|
+
global.console = { info: jest.fn(), error: jest.fn(), warn: jest.fn(), log: global.console.log };
|
|
6
|
+
|
|
7
|
+
beforeEach(() => comp = new PhotoViewer());
|
|
8
|
+
afterEach(() => jest.clearAllMocks());
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
describe("_initWidgets", () => {
|
|
12
|
+
let initParamsMock;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
comp.grid.appendChild = jest.fn();
|
|
16
|
+
|
|
17
|
+
// Mock the return value of getParentPostInit
|
|
18
|
+
initParamsMock = {
|
|
19
|
+
widgets: "true",
|
|
20
|
+
focus: "pic",
|
|
21
|
+
picture: "somePicture",
|
|
22
|
+
};
|
|
23
|
+
comp._createInitParamsHandler();
|
|
24
|
+
comp._initParams.getParentPostInit = jest.fn();
|
|
25
|
+
comp._initParams.getParentPostInit.mockReturnValue(initParamsMock);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should not add widgets if widgets is false", () => {
|
|
29
|
+
initParamsMock.widgets = "false";
|
|
30
|
+
comp._initWidgets();
|
|
31
|
+
expect(comp.grid.appendChild).not.toHaveBeenCalled();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should handle widgets if width is not small", () => {
|
|
35
|
+
comp.isWidthSmall = () => false;
|
|
36
|
+
comp._initWidgets();
|
|
37
|
+
expect(comp.grid.appendChild).toMatchSnapshot();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should handle widgets if width is small", () => {
|
|
41
|
+
comp.isWidthSmall = () => true;
|
|
42
|
+
comp._initWidgets();
|
|
43
|
+
expect(comp.grid.appendChild).toMatchSnapshot();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("getClassName", () => {
|
|
48
|
+
it("works", () => {
|
|
49
|
+
expect(comp.getClassName()).toBe("PhotoViewer");
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("getSubComponentsNames", () => {
|
|
54
|
+
it("works", () => {
|
|
55
|
+
expect(comp.getSubComponentsNames()).toEqual(["loader", "api", "psv", "grid", "popup", "urlHandler"]);
|
|
56
|
+
});
|
|
57
|
+
});
|