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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/.gitlab-ci.yml +3 -0
  2. package/CHANGELOG.md +19 -0
  3. package/CODE_OF_CONDUCT.md +1 -1
  4. package/README.md +1 -1
  5. package/build/editor.html +10 -1
  6. package/build/index.css +2 -2
  7. package/build/index.css.map +1 -1
  8. package/build/index.html +1 -1
  9. package/build/index.js +1682 -5
  10. package/build/index.js.map +1 -1
  11. package/build/map.html +1 -1
  12. package/build/viewer.html +10 -1
  13. package/build/widgets.html +1 -0
  14. package/config/jest/mocks.js +172 -0
  15. package/config/paths.js +1 -0
  16. package/config/webpack.config.js +26 -0
  17. package/docs/03_URL_settings.md +3 -11
  18. package/docs/05_Compatibility.md +59 -76
  19. package/docs/09_Develop.md +30 -11
  20. package/docs/90_Releases.md +2 -2
  21. package/docs/images/class_diagram.drawio +28 -28
  22. package/docs/images/class_diagram.jpg +0 -0
  23. package/docs/index.md +112 -0
  24. package/docs/reference/components/core/Basic.md +153 -0
  25. package/docs/reference/components/core/CoverageMap.md +160 -0
  26. package/docs/reference/components/core/Editor.md +172 -0
  27. package/docs/reference/components/core/Viewer.md +288 -0
  28. package/docs/reference/components/layout/CorneredGrid.md +29 -0
  29. package/docs/reference/components/layout/Mini.md +45 -0
  30. package/docs/reference/components/menus/MapBackground.md +32 -0
  31. package/docs/reference/components/menus/MapFilters.md +15 -0
  32. package/docs/reference/components/menus/MapLayers.md +15 -0
  33. package/docs/reference/components/menus/MapLegend.md +15 -0
  34. package/docs/reference/components/menus/PictureLegend.md +15 -0
  35. package/docs/reference/components/menus/PictureMetadata.md +15 -0
  36. package/docs/reference/components/menus/PlayerOptions.md +15 -0
  37. package/docs/reference/components/menus/QualityScoreDoc.md +15 -0
  38. package/docs/reference/components/menus/ReportForm.md +15 -0
  39. package/docs/reference/components/menus/ShareMenu.md +15 -0
  40. package/docs/reference/components/ui/Button.md +39 -0
  41. package/docs/reference/components/ui/ButtonGroup.md +36 -0
  42. package/docs/reference/components/ui/CopyButton.md +35 -0
  43. package/docs/reference/components/ui/Grade.md +32 -0
  44. package/docs/reference/components/ui/LinkButton.md +44 -0
  45. package/docs/reference/components/ui/Loader.md +54 -0
  46. package/docs/reference/components/ui/Map.md +214 -0
  47. package/docs/reference/components/ui/MapMore.md +233 -0
  48. package/docs/reference/components/ui/Photo.md +369 -0
  49. package/docs/reference/components/ui/Popup.md +56 -0
  50. package/docs/reference/components/ui/QualityScore.md +45 -0
  51. package/docs/reference/components/ui/SearchBar.md +63 -0
  52. package/docs/reference/components/ui/TogglableGroup.md +39 -0
  53. package/docs/reference/components/ui/widgets/GeoSearch.md +32 -0
  54. package/docs/reference/components/ui/widgets/Legend.md +32 -0
  55. package/docs/reference/components/ui/widgets/MapFiltersButton.md +33 -0
  56. package/docs/reference/components/ui/widgets/MapLayersButton.md +15 -0
  57. package/docs/reference/components/ui/widgets/Player.md +32 -0
  58. package/docs/reference/components/ui/widgets/Share.md +15 -0
  59. package/docs/reference/components/ui/widgets/Zoom.md +15 -0
  60. package/docs/reference/utils/API.md +311 -0
  61. package/docs/reference/utils/InitParameters.md +67 -0
  62. package/docs/reference/utils/URLHandler.md +102 -0
  63. package/docs/reference.md +73 -0
  64. package/docs/shortcuts.md +11 -0
  65. package/docs/tutorials/aerial_imagery.md +19 -0
  66. package/docs/tutorials/authentication.md +10 -0
  67. package/docs/tutorials/custom_widgets.md +64 -0
  68. package/docs/tutorials/map_style.md +27 -0
  69. package/docs/tutorials/migrate_v4.md +122 -0
  70. package/docs/tutorials/synced_coverage.md +42 -0
  71. package/mkdocs.yml +60 -5
  72. package/package.json +10 -7
  73. package/public/editor.html +21 -29
  74. package/public/index.html +3 -3
  75. package/public/map.html +19 -18
  76. package/public/viewer.html +18 -24
  77. package/public/widgets.html +265 -0
  78. package/scripts/doc.js +77 -0
  79. package/src/components/core/Basic.css +44 -0
  80. package/src/components/core/Basic.js +258 -0
  81. package/src/components/core/CoverageMap.css +9 -0
  82. package/src/components/core/CoverageMap.js +105 -0
  83. package/src/components/core/Editor.css +23 -0
  84. package/src/components/core/Editor.js +354 -0
  85. package/src/components/core/Viewer.css +109 -0
  86. package/src/components/core/Viewer.js +707 -0
  87. package/src/components/core/index.js +11 -0
  88. package/src/components/index.js +13 -0
  89. package/src/components/layout/CorneredGrid.js +109 -0
  90. package/src/components/layout/Mini.js +117 -0
  91. package/src/components/layout/index.js +7 -0
  92. package/src/components/menus/MapBackground.js +106 -0
  93. package/src/components/menus/MapFilters.js +386 -0
  94. package/src/components/menus/MapLayers.js +143 -0
  95. package/src/components/menus/MapLegend.js +54 -0
  96. package/src/components/menus/PictureLegend.js +103 -0
  97. package/src/components/menus/PictureMetadata.js +188 -0
  98. package/src/components/menus/PlayerOptions.js +96 -0
  99. package/src/components/menus/QualityScoreDoc.js +36 -0
  100. package/src/components/menus/ReportForm.js +133 -0
  101. package/src/components/menus/Share.js +228 -0
  102. package/src/components/menus/index.js +15 -0
  103. package/src/components/styles.js +365 -0
  104. package/src/components/ui/Button.js +75 -0
  105. package/src/components/ui/ButtonGroup.css +49 -0
  106. package/src/components/ui/ButtonGroup.js +68 -0
  107. package/src/components/ui/CopyButton.js +71 -0
  108. package/src/components/ui/Grade.js +54 -0
  109. package/src/components/ui/LinkButton.js +68 -0
  110. package/src/components/ui/Loader.js +188 -0
  111. package/src/components/{Map.css → ui/Map.css} +5 -17
  112. package/src/components/{Map.js → ui/Map.js} +114 -138
  113. package/src/components/ui/MapMore.js +324 -0
  114. package/src/components/{Photo.css → ui/Photo.css} +6 -6
  115. package/src/components/{Photo.js → ui/Photo.js} +279 -90
  116. package/src/components/ui/Popup.js +145 -0
  117. package/src/components/ui/QualityScore.js +152 -0
  118. package/src/components/ui/SearchBar.js +363 -0
  119. package/src/components/ui/TogglableGroup.js +162 -0
  120. package/src/components/ui/index.js +20 -0
  121. package/src/components/ui/widgets/GeoSearch.css +21 -0
  122. package/src/components/ui/widgets/GeoSearch.js +139 -0
  123. package/src/components/ui/widgets/Legend.js +51 -0
  124. package/src/components/ui/widgets/MapFiltersButton.js +104 -0
  125. package/src/components/ui/widgets/MapLayersButton.js +79 -0
  126. package/src/components/ui/widgets/Player.css +7 -0
  127. package/src/components/ui/widgets/Player.js +148 -0
  128. package/src/components/ui/widgets/Share.js +30 -0
  129. package/src/components/ui/widgets/Zoom.js +82 -0
  130. package/src/components/ui/widgets/index.js +12 -0
  131. package/src/img/panoramax.svg +13 -0
  132. package/src/img/switch_big.svg +20 -10
  133. package/src/index.js +6 -9
  134. package/src/translations/da.json +1 -1
  135. package/src/translations/de.json +1 -1
  136. package/src/translations/en.json +5 -3
  137. package/src/translations/eo.json +1 -1
  138. package/src/translations/es.json +1 -1
  139. package/src/translations/fr.json +5 -3
  140. package/src/translations/hu.json +1 -1
  141. package/src/translations/it.json +1 -1
  142. package/src/translations/ja.json +1 -1
  143. package/src/translations/nl.json +1 -1
  144. package/src/translations/pl.json +1 -1
  145. package/src/translations/sv.json +1 -1
  146. package/src/translations/zh_Hant.json +1 -1
  147. package/src/utils/API.js +74 -42
  148. package/src/utils/InitParameters.js +354 -0
  149. package/src/utils/URLHandler.js +364 -0
  150. package/src/utils/geocoder.js +116 -0
  151. package/src/utils/{I18n.js → i18n.js} +3 -1
  152. package/src/utils/index.js +11 -0
  153. package/src/utils/{Map.js → map.js} +216 -80
  154. package/src/utils/picture.js +433 -0
  155. package/src/utils/utils.js +315 -0
  156. package/src/utils/widgets.js +93 -0
  157. package/tests/components/ui/CopyButton.test.js +52 -0
  158. package/tests/components/ui/Loader.test.js +54 -0
  159. package/tests/components/{Map.test.js → ui/Map.test.js} +19 -61
  160. package/tests/components/{Photo.test.js → ui/Photo.test.js} +89 -57
  161. package/tests/components/ui/Popup.test.js +24 -0
  162. package/tests/components/ui/QualityScore.test.js +17 -0
  163. package/tests/components/ui/SearchBar.test.js +107 -0
  164. package/tests/components/ui/__snapshots__/CopyButton.test.js.snap +34 -0
  165. package/tests/components/ui/__snapshots__/Loader.test.js.snap +56 -0
  166. package/tests/components/{__snapshots__ → ui/__snapshots__}/Map.test.js.snap +11 -38
  167. package/tests/components/{__snapshots__ → ui/__snapshots__}/Photo.test.js.snap +57 -4
  168. package/tests/components/ui/__snapshots__/Popup.test.js.snap +29 -0
  169. package/tests/components/ui/__snapshots__/QualityScore.test.js.snap +11 -0
  170. package/tests/components/ui/__snapshots__/SearchBar.test.js.snap +65 -0
  171. package/tests/utils/API.test.js +1 -14
  172. package/tests/utils/InitParameters.test.js +485 -0
  173. package/tests/utils/URLHandler.test.js +350 -0
  174. package/tests/utils/__snapshots__/URLHandler.test.js.snap +21 -0
  175. package/tests/utils/__snapshots__/picture.test.js.snap +315 -0
  176. package/tests/utils/__snapshots__/widgets.test.js.snap +19 -0
  177. package/tests/utils/geocoder.test.js +37 -0
  178. package/tests/utils/{I18n.test.js → i18n.test.js} +1 -1
  179. package/tests/utils/map.test.js +67 -0
  180. package/tests/utils/picture.test.js +745 -0
  181. package/tests/utils/utils.test.js +288 -0
  182. package/tests/utils/widgets.test.js +90 -0
  183. package/docs/01_Start.md +0 -149
  184. package/docs/02_Usage.md +0 -831
  185. package/docs/04_Advanced_examples.md +0 -216
  186. package/src/Editor.css +0 -37
  187. package/src/Editor.js +0 -361
  188. package/src/StandaloneMap.js +0 -114
  189. package/src/Viewer.css +0 -203
  190. package/src/Viewer.js +0 -1246
  191. package/src/components/CoreView.css +0 -70
  192. package/src/components/CoreView.js +0 -175
  193. package/src/components/Loader.css +0 -74
  194. package/src/components/Loader.js +0 -120
  195. package/src/utils/Exif.js +0 -193
  196. package/src/utils/Utils.js +0 -631
  197. package/src/utils/Widgets.js +0 -562
  198. package/src/viewer/URLHash.js +0 -469
  199. package/src/viewer/Widgets.css +0 -880
  200. package/src/viewer/Widgets.js +0 -1470
  201. package/tests/Editor.test.js +0 -126
  202. package/tests/StandaloneMap.test.js +0 -45
  203. package/tests/Viewer.test.js +0 -366
  204. package/tests/__snapshots__/Editor.test.js.snap +0 -298
  205. package/tests/__snapshots__/StandaloneMap.test.js.snap +0 -30
  206. package/tests/__snapshots__/Viewer.test.js.snap +0 -195
  207. package/tests/components/CoreView.test.js +0 -92
  208. package/tests/components/Loader.test.js +0 -38
  209. package/tests/components/__snapshots__/Loader.test.js.snap +0 -15
  210. package/tests/utils/Exif.test.js +0 -124
  211. package/tests/utils/Map.test.js +0 -113
  212. package/tests/utils/Utils.test.js +0 -300
  213. package/tests/utils/Widgets.test.js +0 -107
  214. package/tests/utils/__snapshots__/Exif.test.js.snap +0 -43
  215. package/tests/utils/__snapshots__/Utils.test.js.snap +0 -41
  216. package/tests/utils/__snapshots__/Widgets.test.js.snap +0 -44
  217. package/tests/viewer/URLHash.test.js +0 -559
  218. package/tests/viewer/Widgets.test.js +0 -127
  219. package/tests/viewer/__snapshots__/URLHash.test.js.snap +0 -108
  220. package/tests/viewer/__snapshots__/Widgets.test.js.snap +0 -403
  221. /package/tests/utils/__snapshots__/{Map.test.js.snap → geocoder.test.js.snap} +0 -0
@@ -1,1470 +0,0 @@
1
- import "./Widgets.css";
2
- import { PSV_ANIM_DURATION, PSV_ZOOM_DELTA, PIC_MAX_STAY_DURATION } from "../Viewer";
3
- import {
4
- createPanel, createGroup, fa, fat, createButton, disableButton,
5
- createSearchBar, createExpandableButton, enableButton, enableCopyButton, closeOtherPanels,
6
- createLinkCell, createTable, createHeader, createButtonSpan, createLabel, showGrade,
7
- showQualityScore,
8
- } from "../utils/Widgets";
9
- import { COLORS, isInIframe, getUserAccount, QUALITYSCORE_VALUES, getGrade, QUALITYSCORE_GPS_VALUES, QUALITYSCORE_RES_360_VALUES, QUALITYSCORE_RES_FLAT_VALUES, QUALITYSCORE_POND_RES, QUALITYSCORE_POND_GPS } from "../utils/Utils";
10
- import SwitchBig from "../img/switch_big.svg";
11
- import SwitchMini from "../img/switch_mini.svg";
12
- import BackgroundAerial from "../img/bg_aerial.jpg";
13
- import BackgroundStreets from "../img/bg_streets.jpg";
14
- import { getGPSPrecision } from "../utils/Exif";
15
-
16
- // Every single icon imported separately to reduce bundle size
17
- import { faPlus } from "@fortawesome/free-solid-svg-icons/faPlus";
18
- import { faMinus } from "@fortawesome/free-solid-svg-icons/faMinus";
19
- import { faShareNodes } from "@fortawesome/free-solid-svg-icons/faShareNodes";
20
- import { faLink } from "@fortawesome/free-solid-svg-icons/faLink";
21
- import { faMap } from "@fortawesome/free-solid-svg-icons/faMap";
22
- import { faImage } from "@fortawesome/free-solid-svg-icons/faImage";
23
- import { faPanorama } from "@fortawesome/free-solid-svg-icons/faPanorama";
24
- import { faPlay } from "@fortawesome/free-solid-svg-icons/faPlay";
25
- import { faBackward } from "@fortawesome/free-solid-svg-icons/faBackward";
26
- import { faForward } from "@fortawesome/free-solid-svg-icons/faForward";
27
- import { faPause } from "@fortawesome/free-solid-svg-icons/faPause";
28
- import { faCalendar } from "@fortawesome/free-solid-svg-icons/faCalendar";
29
- import { faArrowRight } from "@fortawesome/free-solid-svg-icons/faArrowRight";
30
- import { faCamera } from "@fortawesome/free-solid-svg-icons/faCamera";
31
- import { faPen } from "@fortawesome/free-solid-svg-icons/faPen";
32
- import { faPrint } from "@fortawesome/free-solid-svg-icons/faPrint";
33
- import { faSatelliteDish } from "@fortawesome/free-solid-svg-icons/faSatelliteDish";
34
- import { faEllipsisVertical } from "@fortawesome/free-solid-svg-icons/faEllipsisVertical";
35
- import { faRocket } from "@fortawesome/free-solid-svg-icons/faRocket";
36
- import { faPalette } from "@fortawesome/free-solid-svg-icons/faPalette";
37
- import { faLightbulb } from "@fortawesome/free-solid-svg-icons/faLightbulb";
38
- import { faPersonBiking } from "@fortawesome/free-solid-svg-icons/faPersonBiking";
39
- import { faSliders } from "@fortawesome/free-solid-svg-icons/faSliders";
40
- import { faLayerGroup } from "@fortawesome/free-solid-svg-icons/faLayerGroup";
41
- import { faEarthEurope } from "@fortawesome/free-solid-svg-icons/faEarthEurope";
42
- import { faUser } from "@fortawesome/free-solid-svg-icons/faUser";
43
- import { faCircleInfo } from "@fortawesome/free-solid-svg-icons/faCircleInfo";
44
- import { faGear } from "@fortawesome/free-solid-svg-icons/faGear";
45
- import { faLocationDot } from "@fortawesome/free-solid-svg-icons/faLocationDot";
46
- import { faSquareRss } from "@fortawesome/free-solid-svg-icons/faSquareRss";
47
- import { faCloudArrowDown } from "@fortawesome/free-solid-svg-icons/faCloudArrowDown";
48
- import { faCopy } from "@fortawesome/free-solid-svg-icons/faCopy";
49
- import { faTriangleExclamation } from "@fortawesome/free-solid-svg-icons/faTriangleExclamation";
50
- import { faCircleQuestion } from "@fortawesome/free-solid-svg-icons/faCircleQuestion";
51
- import { faCommentDots } from "@fortawesome/free-solid-svg-icons/faCommentDots";
52
- import { faAt } from "@fortawesome/free-solid-svg-icons/faAt";
53
- import { faPaperPlane } from "@fortawesome/free-solid-svg-icons/faPaperPlane";
54
- import { faMedal } from "@fortawesome/free-solid-svg-icons/faMedal";
55
- import { faInfoCircle } from "@fortawesome/free-solid-svg-icons/faInfoCircle";
56
-
57
-
58
- /**
59
- * Handles all map/viewer buttons visible on UI.
60
- * Also handles switch between map and viewer, and responsiveness.
61
- *
62
- * @private
63
- */
64
- export default class Widgets {
65
- /**
66
- * @param {Viewer} viewer The viewer
67
- * @param {object} [options] Widgets options
68
- * @param {string} [options.editIdUrl] Edit with iD URL
69
- * @param {string} [options.mapAttribution] Override default map attribution
70
- * @param {string|Element} [options.customWidget] A user-defined widget to add
71
- * @param {string} [options.iframeBaseURL] Set a custom base URL for the "Share as iframe" menu (defaults to current page)
72
- */
73
- constructor(viewer, options = {}) {
74
- // Set default options
75
- if(options == null) { options = {}; }
76
- if(options.editIdUrl == null) { options.editIdUrl = "https://www.openstreetmap.org/edit"; }
77
-
78
- this._viewer = viewer;
79
- this._t = this._viewer._t;
80
- this._options = options;
81
- const hasMap = this._viewer.map !== undefined;
82
-
83
- // Create widgets "corners"
84
- this._corners = {};
85
- const components = hasMap ? ["main", "mini"] : ["main"];
86
- const cornerSpace = ["top", "bottom"];
87
- const corners = ["left", "middle", "right"];
88
- for(let cp of components) {
89
- for(let cs of cornerSpace) {
90
- const csDom = document.createElement("div");
91
- csDom.id = `gvs-corner-${cp}-${cs}`;
92
- csDom.classList.add("gvs-corner-space");
93
-
94
- for(let cn of corners) {
95
- const corner = document.createElement("div");
96
- corner.id = `${csDom.id}-${cn}`;
97
- corner.classList.add("gvs-corner");
98
- this._corners[`${cp}-${cs}-${cn}`] = corner;
99
- csDom.appendChild(corner);
100
- }
101
-
102
- if(cp == "main") { this._viewer.mainContainer.appendChild(csDom); }
103
- else if(cp == "mini") { this._viewer.miniContainer.appendChild(csDom); }
104
- }
105
- }
106
-
107
- if(!isInIframe()) {
108
- this._initWidgetPlayer(hasMap);
109
- }
110
- this._initWidgetLegend(hasMap, options?.mapAttribution);
111
-
112
- if(hasMap) {
113
- this._initWidgetMiniActions();
114
- if(!isInIframe()) {
115
- this._initWidgetSearch();
116
- this._initWidgetFilters(
117
- this._viewer._api._endpoints.user_search !== null
118
- && this._viewer._api._endpoints.user_tiles !== null,
119
- this._viewer.map && this._viewer.map._hasQualityScore()
120
- );
121
- this._initWidgetMapLayers();
122
- this._listenMapFiltersChanges();
123
- }
124
- }
125
-
126
- if(!this._viewer.isWidthSmall()) {
127
- this._initWidgetShare();
128
- }
129
-
130
- // Custom widget provided by user
131
- if(options.customWidget) {
132
- const corner = this._corners["main-bottom-right"];
133
-
134
- switch(typeof options.customWidget) {
135
- case "string":
136
- for(let e of new DOMParser().parseFromString(options.customWidget, "text/html").body.children) {
137
- corner.appendChild(e);
138
- }
139
- break;
140
-
141
- case "object":
142
- if(Array.isArray(options.customWidget)) {
143
- options.customWidget.forEach(e => corner.appendChild(e));
144
- }
145
- else {
146
- corner.appendChild(options.customWidget);
147
- }
148
- break;
149
- }
150
- }
151
-
152
- this._initWidgetZoom(hasMap);
153
-
154
- // Click outside of an open panel -> closes panels
155
- this._viewer.container.addEventListener("click", e => closeOtherPanels(e.target, this._viewer.container));
156
- }
157
-
158
- /**
159
- * Ends all form of life in this object.
160
- */
161
- destroy() {
162
- Object.values(this._corners).forEach(e => e.remove());
163
- delete this._corners;
164
- delete this._t;
165
- delete this._viewer;
166
- }
167
-
168
- /**
169
- * Creates the zoom buttons group
170
- * @param {boolean} hasMap True if map is enabled
171
- * @private
172
- */
173
- _initWidgetZoom(hasMap) {
174
- this._lastWantedZoom = this._viewer.psv.getZoomLevel();
175
-
176
- // Presentation
177
- const btnZoomIn = createButton("gvs-zoom-in", fa(faPlus), this._t.gvs.zoomIn);
178
- const btnZoomOut = createButton("gvs-zoom-out", fa(faMinus), this._t.gvs.zoomOut);
179
- createGroup("gvs-widget-zoom", "main-bottom-right", this, [btnZoomIn, btnZoomOut], ["gvs-group-vertical", "gvs-mobile-hidden", "gvs-print-hidden"]);
180
-
181
- // Events
182
- const zoomFct = (e, zoomIn) => {
183
- if(hasMap && this._viewer.isMapWide()) {
184
- if(zoomIn) { this._viewer.map.zoomIn({}, {originalEvent: e}); }
185
- else { this._viewer.map.zoomOut({}, {originalEvent: e}); }
186
- }
187
- else {
188
- if(this._viewer.lastPsvAnim) { this._viewer.lastPsvAnim.cancel(); }
189
- const goToZoom = zoomIn ?
190
- Math.min(100, this._lastWantedZoom + PSV_ZOOM_DELTA)
191
- : Math.max(0, this._lastWantedZoom - PSV_ZOOM_DELTA);
192
- this._viewer.lastPsvAnim = this._viewer.psv.animate({
193
- speed: PSV_ANIM_DURATION,
194
- zoom: goToZoom
195
- });
196
- this._lastWantedZoom = goToZoom;
197
- }
198
- };
199
-
200
- btnZoomIn.addEventListener("click", e => zoomFct(e, true));
201
- btnZoomOut.addEventListener("click", e => zoomFct(e, false));
202
- }
203
-
204
- /**
205
- * Creates play/pause/next/prev picture buttons
206
- * @param {boolean} hasMap True if map is enabled
207
- * @private
208
- */
209
- _initWidgetPlayer(hasMap) {
210
- // Presentation
211
- const btnPlayerPrev = createButton("gvs-player-prev", fa(faBackward), this._t.gvs.sequence_prev);
212
- const btnPlayerPlay = createButton("gvs-player-play");
213
- const btnPlayerNext = createButton("gvs-player-next", fa(faForward), this._t.gvs.sequence_next);
214
- const btnPlayerMore = createButton("gvs-player-more", fa(faEllipsisVertical), this._t.gvs.sequence_more, ["gvs-xs-hidden"]);
215
-
216
- // Panel for more options
217
- const pnlOpts = createPanel(this, btnPlayerMore, [], ["gvs-player-options"]);
218
- pnlOpts.innerHTML = `
219
- <div class="gvs-input-range" title="${this._t.gvs.sequence_speed}">
220
- ${fat(faPersonBiking)}
221
- <input
222
- id="gvs-player-speed"
223
- type="range" name="speed"
224
- min="0" max="${PIC_MAX_STAY_DURATION - 100}"
225
- value="${PIC_MAX_STAY_DURATION - this._viewer.psv.getTransitionDuration()}"
226
- title="${this._t.gvs.sequence_speed}"
227
- style="width: 100%;" />
228
- ${fat(faRocket)}
229
- </div>
230
- <button title="${this._t.gvs.contrast}" id="gvs-player-contrast">
231
- ${fat(faLightbulb)}
232
- </button>
233
- `;
234
-
235
- // Group widget
236
- const grpPlayer = createGroup(
237
- "gvs-widget-player",
238
- !hasMap ? "main-top-left" : "main-top-middle",
239
- this,
240
- [btnPlayerPrev, btnPlayerPlay, btnPlayerNext].concat(this._viewer.isWidthSmall() ? [] : [pnlOpts, btnPlayerMore]),
241
- ["gvs-group-horizontal", "gvs-only-psv", "gvs-print-hidden", this._viewer.psv.getPictureMetadata() ? "" : "gvs-hidden"]
242
- );
243
-
244
- // Toggle state of play button
245
- const toggleBtnPlay = (isPlaying) => {
246
- btnPlayerPlay.innerHTML = isPlaying ? fat(faPause) : fat(faPlay);
247
- btnPlayerPlay.title = isPlaying ? this._t.gvs.sequence_pause : this._t.gvs.sequence_play;
248
- };
249
- toggleBtnPlay(false);
250
-
251
- // Update state of play button on picture load
252
- const updatePlayBtn = () => {
253
- if(this._viewer.getPicturesNavigation() === "pic") {
254
- disableButton(btnPlayerNext);
255
- disableButton(btnPlayerPlay);
256
- disableButton(btnPlayerPrev);
257
- }
258
- else {
259
- if(this._viewer.psv.getPictureMetadata()?.sequence?.prevPic != null) { enableButton(btnPlayerPrev); }
260
- else { disableButton(btnPlayerPrev); }
261
-
262
- if(this._viewer.psv.getPictureMetadata()?.sequence?.nextPic != null) {
263
- enableButton(btnPlayerNext);
264
- enableButton(btnPlayerPlay);
265
- }
266
- else {
267
- disableButton(btnPlayerNext);
268
- disableButton(btnPlayerPlay);
269
- }
270
- }
271
- };
272
- updatePlayBtn();
273
-
274
- // Listening to viewer events
275
- this._viewer.addEventListener("sequence-playing", () => toggleBtnPlay(true));
276
- this._viewer.addEventListener("sequence-stopped", () => toggleBtnPlay(false));
277
- this._viewer.addEventListener("psv:picture-loaded", () => grpPlayer.classList.remove("gvs-hidden"), { once: true });
278
- this._viewer.addEventListener("psv:picture-loaded", updatePlayBtn);
279
- this._viewer.addEventListener("pictures-navigation-changed", updatePlayBtn);
280
-
281
- if(!this._viewer.isWidthSmall()) {
282
- const btnPlayerSpeed = pnlOpts.children[0].children[1];
283
-
284
- this._viewer.addEventListener("psv:transition-duration-changed", e => {
285
- btnPlayerSpeed.value = PIC_MAX_STAY_DURATION - e.detail.value;
286
- });
287
-
288
- btnPlayerSpeed.addEventListener("change", e => {
289
- const newSpeed = PIC_MAX_STAY_DURATION - e.target.value;
290
- this._viewer.psv.setTransitionDuration(newSpeed);
291
- });
292
- }
293
-
294
- // Buttons events
295
- btnPlayerPrev.addEventListener("click", () => this._viewer.psv.goToPrevPicture());
296
- btnPlayerNext.addEventListener("click", () => this._viewer.psv.goToNextPicture());
297
-
298
- btnPlayerPlay.addEventListener("click", () => {
299
- if(this._viewer.isSequencePlaying()) {
300
- toggleBtnPlay(false);
301
- this._viewer.stopSequence();
302
- }
303
- else {
304
- toggleBtnPlay(true);
305
- this._viewer.playSequence();
306
- }
307
- });
308
-
309
- const btnPlayerContrast = document.getElementById("gvs-player-contrast");
310
- if(btnPlayerContrast) {
311
- btnPlayerContrast.addEventListener("click", () => {
312
- if(btnPlayerContrast.classList.contains("gvs-btn-active")) {
313
- btnPlayerContrast.classList.remove("gvs-btn-active");
314
- this._viewer.psv.setHigherContrast(false);
315
- }
316
- else {
317
- btnPlayerContrast.classList.add("gvs-btn-active");
318
- this._viewer.psv.setHigherContrast(true);
319
- }
320
- });
321
- }
322
- }
323
-
324
- /**
325
- * Creates legend block
326
- * @param {boolean} hasMap True if map is enabled
327
- * @param {string} [mapAttribution] Override map attribution
328
- * @private
329
- */
330
- _initWidgetLegend(hasMap, mapAttribution) {
331
- // Presentation (main widget)
332
- const mainLegend = createGroup(
333
- "gvs-widget-legend",
334
- hasMap ? "main-bottom-right" : "main-bottom-left",
335
- this,
336
- [],
337
- ["gvs-widget-bg"]
338
- );
339
-
340
- // Presentation (mini widget)
341
- let miniLegend;
342
- if(hasMap) {
343
- miniLegend = createGroup(
344
- "gvs-widget-mini-legend",
345
- "mini-bottom-right",
346
- this,
347
- [],
348
- ["gvs-widget-bg", "gvs-only-mini", "gvs-mobile-hidden"]
349
- );
350
- }
351
-
352
- // Show/hide legend button (for small devices)
353
- let btnVisibLegend, toggleVisibLegend;
354
- if(this._viewer.isWidthSmall()) {
355
- btnVisibLegend = document.createElement("button");
356
- btnVisibLegend.id = "gvs-legend-toggle";
357
- btnVisibLegend.classList.add("gvs-btn", "gvs-widget-bg", "gvs-print-hidden");
358
- btnVisibLegend.appendChild(fa(faCircleInfo));
359
- toggleVisibLegend = () => {
360
- if(mainLegend.style.visibility === "hidden") {
361
- mainLegend.style.visibility = "visible";
362
- if(!hasMap) { toggleLegend(false); }
363
- else { toggleLegend(this._viewer.isMapWide()); }
364
- }
365
- else {
366
- mainLegend.innerHTML = "";
367
- mainLegend.style.visibility = "hidden";
368
- mainLegend.appendChild(btnVisibLegend);
369
- }
370
- };
371
- btnVisibLegend.addEventListener("click", e => {
372
- e.stopPropagation();
373
- toggleVisibLegend();
374
- });
375
- }
376
-
377
- const toggleLegend = (focusOnMap) => {
378
- let mapLegend = mapAttribution || this._viewer.map?._attribution?._attribHTML || "";
379
- let picLegend = "<a href='https://panoramax.fr/' target='_blank'>Panoramax</a>";
380
-
381
- // Picture legend based on current picture metadata
382
- const picMeta = this._viewer.psv.getPictureMetadata()?.caption;
383
- let picMetaBtn;
384
- if(!isInIframe() && picMeta) {
385
- picLegend = "";
386
- if(picMeta.producer) {
387
- picLegend += `<span style="font-weight: bold">&copy; ${picMeta.producer}</span>`;
388
- }
389
- if(picMeta.date) {
390
- if(picMeta.producer) { picLegend += "&nbsp;-&nbsp;"; }
391
- picLegend += picMeta.date.toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric" });
392
- }
393
-
394
- // Button for metadata popup
395
- picMetaBtn = fa(faCircleQuestion);
396
- picMetaBtn.style.marginLeft = "5px";
397
- }
398
-
399
- // Put appropriate legend according to view focus
400
- mainLegend.title = "";
401
- if(focusOnMap) {
402
- mainLegend.innerHTML = mapLegend;
403
- if(isInIframe()) {
404
- mainLegend.innerHTML = "<a href='https://panoramax.fr/' target='_blank'>Panoramax</a><br />" + mainLegend.innerHTML;
405
- }
406
- mainLegend.style.cursor = null;
407
- mainLegend.onclick = null;
408
- miniLegend.innerHTML = picLegend;
409
- }
410
- else {
411
- mainLegend.innerHTML = picLegend;
412
-
413
- if(picMetaBtn) {
414
- mainLegend.appendChild(picMetaBtn);
415
- mainLegend.style.cursor = "pointer";
416
- mainLegend.title = this._t.gvs.legend_title;
417
- mainLegend.onclick = isInIframe() ?
418
- () => window.open(window.location.href, "_blank")
419
- : this._showPictureMetadataPopup.bind(this);
420
- }
421
- else {
422
- mainLegend.style.cursor = null;
423
- mainLegend.onclick = null;
424
- }
425
-
426
- if(hasMap) { miniLegend.innerHTML = mapLegend; }
427
- }
428
-
429
- if(btnVisibLegend) { mainLegend.appendChild(btnVisibLegend); }
430
- };
431
-
432
- if(!btnVisibLegend) {
433
- if(!hasMap) { toggleLegend(false); }
434
- else { toggleLegend(this._viewer.isMapWide()); }
435
- }
436
- else {
437
- mainLegend.appendChild(btnVisibLegend);
438
- mainLegend.style.visibility = "hidden";
439
- }
440
-
441
- // Listening to viewer events
442
- this._viewer.addEventListener("focus-changed", e => toggleLegend(e.detail.focus == "map"));
443
- this._viewer.addEventListener("psv:picture-loaded", () => toggleLegend(hasMap && this._viewer.isMapWide()));
444
- }
445
-
446
- /**
447
- * Displays current picture metadata in popup
448
- * @private
449
- */
450
- _showPictureMetadataPopup() {
451
- const picMeta = this._viewer.psv.getPictureMetadata();
452
- if (!picMeta) { throw new Error("No picture currently selected"); }
453
-
454
- const popupContent = [];
455
- popupContent.push(createHeader("h4", `${fat(faCircleInfo)} ${this._t.gvs.metadata}`));
456
-
457
- // Rapid actions (report)
458
- if (this._viewer._api._endpoints.report) {
459
- const popupMetaActions = createButtonSpan(`${fat(faTriangleExclamation)} ${this._t.gvs.report}`);
460
- popupMetaActions.firstChild.addEventListener("click", this._showReportForm.bind(this));
461
- popupContent.push(popupMetaActions);
462
- }
463
-
464
- // General metadata
465
- const rowsData = [
466
- {
467
- section: this._t.gvs.metadata_general_picid,
468
- classes: ["gvs-td-with-id"],
469
- values: createLinkCell(
470
- picMeta.id,
471
- this._viewer._api.getPictureMetadataUrl(picMeta.id, picMeta?.sequence?.id),
472
- this._t.gvs.metadata_general_picid_link,
473
- this._t.gvs.copy
474
- )
475
- },
476
- {
477
- section: this._t.gvs.metadata_general_seqid,
478
- classes: ["gvs-td-with-id"],
479
- values: createLinkCell(
480
- picMeta?.sequence?.id,
481
- this._viewer._api.getSequenceMetadataUrl(picMeta?.sequence?.id),
482
- this._t.gvs.metadata_general_seqid_link,
483
- this._t.gvs.copy
484
- )
485
- },
486
- { section: this._t.gvs.metadata_general_author, value: picMeta?.caption?.producer },
487
- { section: this._t.gvs.metadata_general_license, value: picMeta?.caption?.license },
488
- {
489
- section: this._t.gvs.metadata_general_date,
490
- value: picMeta?.caption?.date?.toLocaleDateString(undefined, {
491
- year: "numeric", month: "long", day: "numeric",
492
- hour: "numeric", minute: "numeric", second: "numeric",
493
- fractionalSecondDigits: 3, timeZoneName: "short"
494
- })
495
- },
496
- ];
497
- popupContent.push(createTable("gvs-table-light", rowsData));
498
-
499
- // Camera details
500
- popupContent.push(createHeader("h4", `${fat(faCamera)} ${this._t.gvs.metadata_camera}`));
501
- const focal = picMeta?.properties?.["pers:interior_orientation"]?.focal_length ? `${picMeta?.properties?.["pers:interior_orientation"]?.focal_length} mm` : "❓";
502
- let resmp = picMeta?.properties?.["pers:interior_orientation"]?.["sensor_array_dimensions"];
503
- if(resmp) {
504
- resmp = `${resmp[0]} x ${resmp[1]} px (${Math.floor(resmp[0] * resmp[1] / 1000000)} Mpx)`;
505
- }
506
- let pictype = this._t.gvs.picture_flat;
507
- let picFov = picMeta?.properties?.["pers:interior_orientation"]?.["field_of_view"]; // Use raw value instead of horizontalFov to avoid default showing up
508
- if(picFov !== null && picFov !== undefined) {
509
- if(picFov === 360) { pictype = this._t.gvs.picture_360; }
510
- else { pictype += ` (${picFov}°)`; }
511
- }
512
-
513
- const cameraData = [
514
- { section: this._t.gvs.metadata_camera_make, value: picMeta?.properties?.["pers:interior_orientation"]?.camera_manufacturer || "❓" },
515
- { section: this._t.gvs.metadata_camera_model, value: picMeta?.properties?.["pers:interior_orientation"]?.camera_model || "❓" },
516
- { section: this._t.gvs.metadata_camera_type, value: pictype },
517
- { section: this._t.gvs.metadata_camera_resolution, value: resmp || "❓" },
518
- { section: this._t.gvs.metadata_camera_focal_length, value: focal },
519
- ];
520
- popupContent.push(createTable("gvs-table-light", cameraData));
521
-
522
- // Location details
523
- popupContent.push(createHeader("h4", `${fat(faLocationDot)} ${this._t.gvs.metadata_location}`));
524
- const orientation = picMeta?.properties?.["view:azimuth"] !== undefined ? `${picMeta.properties["view:azimuth"]}°` : "❓";
525
- const gpsPrecisionLabel = getGPSPrecision(picMeta);
526
- const locationData = [
527
- { section: this._t.gvs.metadata_location_longitude, value: picMeta.gps[0] },
528
- { section: this._t.gvs.metadata_location_latitude, value: picMeta.gps[1] },
529
- { section: this._t.gvs.metadata_location_orientation, value: orientation },
530
- { section: this._t.gvs.metadata_location_precision, value: gpsPrecisionLabel },
531
- ];
532
- popupContent.push(createTable("gvs-table-light", locationData));
533
-
534
- // Picture quality level
535
- if(this._viewer?.map?._hasQualityScore()) {
536
- const qsHeader = createHeader(
537
- "h4",
538
- `${fat(faMedal)} ${this._t.gvs.metadata_quality} <button class="gvs-btn-link" title="${this._t.gvs.metadata_quality_help}">${fat(faInfoCircle)}</button>`
539
- );
540
- qsHeader.lastChild.addEventListener("click", () => this._showQualityScoreDoc());
541
- popupContent.push(qsHeader);
542
- const gpsGrade = getGrade(QUALITYSCORE_GPS_VALUES, picMeta?.properties?.["quality:horizontal_accuracy"]);
543
- const resGrade = getGrade(
544
- picMeta?.horizontalFov === 360 ? QUALITYSCORE_RES_360_VALUES : QUALITYSCORE_RES_FLAT_VALUES,
545
- picMeta?.properties?.["panoramax:horizontal_pixel_density"]
546
- );
547
- // Note: score is also calculated in utils/map code
548
- const generalGrade = Math.round((resGrade || 1) * QUALITYSCORE_POND_RES + (gpsGrade || 1) * QUALITYSCORE_POND_GPS);
549
-
550
- const qualityData = [
551
- { section: this._t.gvs.metadata_quality_score, value: showQualityScore(generalGrade) },
552
- { section: this._t.gvs.metadata_quality_gps_score, value: showGrade(gpsGrade, this._t) },
553
- { section: this._t.gvs.metadata_quality_resolution_score, value: showGrade(resGrade, this._t) },
554
- ];
555
- popupContent.push(createTable("gvs-table-light", qualityData));
556
- }
557
-
558
- // EXIF
559
- if (picMeta.properties?.exif) {
560
- const exifDetails = document.createElement("details");
561
- exifDetails.appendChild(createHeader("summary", `${fat(faGear)} ${this._t.gvs.metadata_exif}`));
562
-
563
- const exifData = Object.entries(picMeta.properties.exif).sort().map(([key, value]) => ({ section: key, value: value }));
564
- exifDetails.appendChild(createTable("", exifData));
565
- popupContent.push(exifDetails);
566
- }
567
-
568
- this._viewer.setPopup(true, popupContent);
569
- this._viewer.dispatchEvent(new CustomEvent("focus-changed", { detail: { focus: "meta" } }));
570
- }
571
-
572
- _showQualityScoreDoc() {
573
- const popupContent = [];
574
- popupContent.push(createHeader("h4", `${fat(faMedal)} ${this._t.gvs.qualityscore_title}`));
575
-
576
- const link = document.createElement("a");
577
- link.setAttribute("href", "https://docs.panoramax.fr/pictures-metadata/quality_score/");
578
- link.setAttribute("target", "_blank");
579
- link.appendChild(document.createTextNode(this._t.gvs.qualityscore_doc_link));
580
-
581
- [
582
- document.createTextNode(this._t.gvs.qualityscore_doc_1),
583
- document.createTextNode(this._t.gvs.qualityscore_doc_2),
584
- showQualityScore(5),
585
- document.createTextNode(this._t.gvs.qualityscore_doc_3),
586
- link
587
- ].forEach(elem => {
588
- const p = document.createElement("p");
589
- p.appendChild(elem);
590
- popupContent.push(p);
591
- });
592
-
593
- this._viewer.setPopup(true, popupContent);
594
- }
595
-
596
- _showReportForm() {
597
- const picMeta = this._viewer.psv.getPictureMetadata();
598
- if (!picMeta) { throw new Error("No picture currently selected"); }
599
-
600
- const popupContent = [];
601
- popupContent.push(createHeader("h4", `${fat(faTriangleExclamation)} ${this._t.gvs.report}`));
602
-
603
- const userAccount = getUserAccount();
604
- if(userAccount) {
605
- const accountInfo = document.createElement("p");
606
- accountInfo.appendChild(document.createTextNode(this._t.gvs.report_auth.replace("{a}", userAccount.name)));
607
- popupContent.push(accountInfo);
608
- }
609
-
610
- const form = document.createElement("form");
611
- popupContent.push(form);
612
-
613
- // Nature of the issue
614
- const issueGrp = document.createElement("div");
615
- issueGrp.classList.add("gvs-input-group");
616
- const issueLabel = createLabel("gvs-report-issue", this._t.gvs.report_nature_label, faCircleInfo);
617
- const issueSelect = document.createElement("select");
618
- issueSelect.name = "gvs-report-issue";
619
- issueSelect.required = true;
620
-
621
- const issueOptions = [
622
- "", "blur_missing", "blur_excess", "inappropriate", "privacy",
623
- "picture_low_quality", "mislocated", "copyright", "other"
624
- ];
625
-
626
- issueOptions.forEach(optionValue => {
627
- const option = document.createElement("option");
628
- option.value = optionValue;
629
- option.textContent = this._t.gvs.report_nature[optionValue];
630
- if(optionValue === "") {
631
- option.setAttribute("disabled", "");
632
- option.setAttribute("selected", "");
633
- option.setAttribute("hidden", "");
634
- }
635
- issueSelect.appendChild(option);
636
- });
637
-
638
- issueGrp.appendChild(issueLabel);
639
- issueGrp.appendChild(issueSelect);
640
- form.appendChild(issueGrp);
641
-
642
- // Picture or sequence ?
643
- const wholeSeqGrp = document.createElement("div");
644
- wholeSeqGrp.classList.add("gvs-input-group", "gvs-input-group-inline");
645
- const picSeqInput = document.createElement("input");
646
- picSeqInput.id = "gvs-report-whole-sequence";
647
- picSeqInput.name = "gvs-report-whole-sequence";
648
- picSeqInput.type = "checkbox";
649
- const picSeqLabel = createLabel("gvs-report-whole-sequence", this._t.gvs.report_whole_sequence);
650
- wholeSeqGrp.appendChild(picSeqInput);
651
- wholeSeqGrp.appendChild(picSeqLabel);
652
- form.appendChild(wholeSeqGrp);
653
-
654
- // Additional details
655
- const dtlsGrp = document.createElement("div");
656
- dtlsGrp.classList.add("gvs-input-group");
657
- const detailsLabel = createLabel("gvs-report-details", this._t.gvs.report_details, faCommentDots);
658
- const detailsTextarea = document.createElement("textarea");
659
- detailsTextarea.name = "gvs-report-details";
660
- detailsTextarea.placeholder = this._t.gvs.report_details_placeholder;
661
- dtlsGrp.appendChild(detailsLabel);
662
- dtlsGrp.appendChild(detailsTextarea);
663
- form.appendChild(dtlsGrp);
664
-
665
- // Reporter email
666
- let emailInput;
667
- if(!userAccount) {
668
- const emailGrp = document.createElement("div");
669
- emailGrp.classList.add("gvs-input-group");
670
- const emailLabel = createLabel("email", this._t.gvs.report_email, faAt);
671
- emailInput = document.createElement("input");
672
- emailInput.type = "email";
673
- emailInput.name = "email";
674
- emailInput.placeholder = this._t.gvs.report_email_placeholder;
675
- emailGrp.appendChild(emailLabel);
676
- emailGrp.appendChild(emailInput);
677
- form.appendChild(emailGrp);
678
- }
679
-
680
- // Submit button
681
- const submitGrp = document.createElement("div");
682
- submitGrp.classList.add("gvs-input-btn");
683
- const submitButton = document.createElement("button");
684
- submitButton.type = "submit";
685
- submitButton.appendChild(fa(faPaperPlane));
686
- submitButton.appendChild(document.createTextNode(this._t.gvs.report_submit));
687
- submitGrp.appendChild(submitButton);
688
- form.appendChild(submitGrp);
689
-
690
- // Submit handler
691
- form.addEventListener("submit", e => {
692
- e.preventDefault();
693
- const params = {
694
- issue: issueSelect.value,
695
- picture_id: picSeqInput.checked ? null : picMeta.id,
696
- reporter_comments: detailsTextarea.value,
697
- reporter_email: emailInput?.value,
698
- sequence_id: picMeta.sequence.id
699
- };
700
-
701
- // Show loader
702
- this._viewer.setPopup(true, [
703
- createHeader("h4", `${fat(faTriangleExclamation)} ${this._t.gvs.report}`),
704
- document.createTextNode(this._t.gvs.report_wait)
705
- ]);
706
-
707
- // Call API
708
- this._viewer._api.sendReport(params).then(() => {
709
- this._viewer.setPopup(true, [
710
- createHeader("h4", `${fat(faTriangleExclamation)} ${this._t.gvs.report}`),
711
- document.createTextNode(this._t.gvs.report_success)
712
- ]);
713
- }).catch(e => {
714
- console.error(e);
715
- this._viewer.setPopup(true, [
716
- createHeader("h4", `${fat(faTriangleExclamation)} ${this._t.gvs.report}`),
717
- document.createTextNode(this._t.gvs.report_failure.replace("{e}", e))
718
- ]);
719
- });
720
- });
721
-
722
- this._viewer.setPopup(true, popupContent);
723
- this._viewer.dispatchEvent(new CustomEvent("focus-changed", { detail: { focus: "meta" } }));
724
- }
725
-
726
- /**
727
- * Creates expand/reduce mini component.
728
- * This should be called only if map is enabled.
729
- * @private
730
- */
731
- _initWidgetMiniActions() {
732
- // Mini widget expand
733
- const imgExpand = document.createElement("img");
734
- imgExpand.alt = "";
735
- imgExpand.height = 120;
736
- imgExpand.draggable = false;
737
- imgExpand.src = SwitchBig;
738
- const lblExpand = document.createElement("span");
739
- lblExpand.classList.add("gvs-mobile-hidden");
740
- lblExpand.appendChild(document.createTextNode(this._t.gvs.expand));
741
- const btnExpand = createButton("gvs-mini-expand", lblExpand, this._t.gvs.expand_info, ["gvs-only-mini", "gvs-print-hidden"]);
742
- btnExpand.appendChild(imgExpand);
743
- this._corners["mini-top-right"].appendChild(btnExpand);
744
- btnExpand.addEventListener("click", () => {
745
- this._viewer.setFocus(this._viewer.isMapWide() ? "pic" : "map");
746
- });
747
-
748
- // Mini widget hide
749
- const imgReduce = document.createElement("img");
750
- imgReduce.alt = this._t.gvs.minimize_short;
751
- imgReduce.height = 120;
752
- imgReduce.draggable = false;
753
- imgReduce.src = SwitchMini;
754
- const btnHide = createButton("gvs-mini-hide", imgReduce, this._t.gvs.minimize, ["gvs-only-mini", "gvs-print-hidden"]);
755
- this._corners["mini-bottom-left"].appendChild(btnHide);
756
- btnHide.addEventListener("click", () => {
757
- this._viewer.setUnfocusedVisible(false);
758
- });
759
-
760
- // Mini widget show
761
- const btnShow = createButton("gvs-mini-show", null, null, ["gvs-btn-large", "gvs-only-mini-hidden", "gvs-print-hidden"]);
762
- this._corners["main-bottom-left"].appendChild(btnShow);
763
- btnShow.addEventListener("click", () => {
764
- if(isInIframe()) {
765
- this._viewer.setFocus(this._viewer.isMapWide() ? "pic" : "map");
766
- }
767
- else {
768
- this._viewer.setUnfocusedVisible(true);
769
- }
770
- });
771
-
772
- const miniBtnRendering = () => {
773
- if(this._viewer.map && this._viewer.isMapWide()) {
774
- btnShow.title = this._t.gvs.show_psv;
775
- btnShow.innerHTML = fat(faPanorama);
776
- }
777
- else {
778
- btnShow.title = this._t.gvs.show_map;
779
- btnShow.innerHTML = fat(faMap);
780
- }
781
- };
782
-
783
- miniBtnRendering();
784
- this._viewer.addEventListener("focus-changed", miniBtnRendering);
785
- }
786
-
787
- /**
788
- * Creates search bar component.
789
- * This should be called only if map is enabled.
790
- * @private
791
- */
792
- _initWidgetSearch() {
793
- const overridenGeocoder = query => {
794
- const rgxCoords = /([-+]?\d{1,2}\.\d+),\s*([-+]?\d{1,3}\.\d+)/;
795
- const coordsMatch = query.match(rgxCoords);
796
- const rgxUuid = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/;
797
- const uuidMatch = query.match(rgxUuid);
798
-
799
- if(coordsMatch) {
800
- const lat = parseFloat(coordsMatch[1]);
801
- const lon = parseFloat(coordsMatch[2]);
802
- this._viewer.map.flyTo({
803
- center: [lon, lat],
804
- zoom: 16,
805
- });
806
- return Promise.resolve(true);
807
- }
808
- else if(uuidMatch) {
809
- this._viewer.select(null, query);
810
- return Promise.resolve(true);
811
- }
812
- else {
813
- return this._viewer.map.geocoder({
814
- query,
815
- limit: 3,
816
- bbox: this._viewer.map.getBounds().toArray().map(d => d.join(",")).join(","),
817
- proximity: this._viewer.map.getCenter().lat+","+this._viewer.map.getCenter().lng,
818
- }).then(data => {
819
- data = data.features.map(f => ({
820
- title: f.place_name.split(",")[0],
821
- subtitle: f.place_name.split(",").slice(1).join(", "),
822
- data: f
823
- }));
824
- return data;
825
- });
826
- }
827
- };
828
- const geocoder = createSearchBar(
829
- "gvs-widget-search-bar",
830
- this._t.gvs.search_address,
831
- overridenGeocoder,
832
- (entry) => {
833
- if(entry) {
834
- if(entry.data.bounds) {
835
- this._viewer.map.fitBounds(entry.data.bounds);
836
- }
837
- else {
838
- this._viewer.map.flyTo({
839
- center: entry.data.center,
840
- zoom: entry.data.zoom || 13,
841
- });
842
- }
843
- }
844
- },
845
- this,
846
- undefined,
847
- this._viewer.isWidthSmall(),
848
- this._viewer.map._geolocate,
849
- );
850
-
851
- createGroup(
852
- "gvs-widget-search",
853
- this._viewer.isWidthSmall() ? "main-top-right" : "main-top-left",
854
- this,
855
- [geocoder],
856
- ["gvs-only-map", "gvs-print-hidden"]
857
- );
858
- }
859
-
860
- /**
861
- * Creates the map layers component.
862
- * This should be called only if map is enabled.
863
- * @private
864
- */
865
- _initWidgetMapLayers() {
866
- const btnLayers = createExpandableButton("gvs-map-layers", faLayerGroup, this._t.gvs.layers, this);
867
- const pnlLayers = createPanel(this, btnLayers, []);
868
- createGroup(
869
- "gvs-widget-map-layers",
870
- "main-top-right",
871
- this,
872
- [btnLayers, pnlLayers],
873
- ["gvs-group-large", "gvs-group-btnpanel", "gvs-only-map", "gvs-print-hidden"]
874
- );
875
-
876
- // Map background selector
877
- if(this._viewer.map.hasTwoBackgrounds()) {
878
- pnlLayers.innerHTML = `
879
- <h4>${fat(faEarthEurope)} ${this._t.gvs.map_background}</h4>
880
- <div id="gvs-map-bg" class="gvs-input-group">
881
- <input type="radio" id="gvs-map-bg-streets" name="gvs-map-bg" value="streets" />
882
- <label for="gvs-map-bg-streets">
883
- <img id="gvs-map-bg-streets-img" alt="" />
884
- ${this._t.gvs.map_background_streets}
885
- </label>
886
- <input type="radio" id="gvs-map-bg-aerial" name="gvs-map-bg" value="aerial" />
887
- <label for="gvs-map-bg-aerial">
888
- <img id="gvs-map-bg-aerial-img" alt="" />
889
- ${this._t.gvs.map_background_aerial}
890
- </label>
891
- </div>`;
892
- }
893
-
894
- // Map theme selector
895
- pnlLayers.innerHTML += `
896
- <h4>${fat(faPalette)} ${this._t.gvs.map_theme}</h4>
897
- <div class="gvs-input-group">
898
- <select id="gvs-map-theme" style="width: 100%;">
899
- <option value="default">${this._t.gvs.map_theme_default}</option>
900
- <option value="age">${this._t.gvs.map_theme_age}</option>
901
- <option value="type">${this._t.gvs.map_theme_type}</option>
902
- ${this._viewer?.map?._hasQualityScore() ? "<option value=\"score\">"+this._t.gvs.map_theme_score+"</option>" : ""}
903
- </select>
904
- </div>
905
- <div>
906
- <div id="gvs-map-theme-legend-age" class="gvs-map-theme-legend gvs-hidden">
907
- <div>
908
- <div class="gvs-map-theme-legend-entry">
909
- <span class="gvs-map-theme-color" style="background-color: ${COLORS["PALETTE_4"]}"></span>
910
- ${this._t.gvs["map_theme_age_4"]}
911
- </div>
912
- <div class="gvs-map-theme-legend-entry">
913
- <span class="gvs-map-theme-color" style="background-color: ${COLORS["PALETTE_3"]}"></span>
914
- ${this._t.gvs["map_theme_age_3"]}
915
- </div>
916
- </div>
917
- <div>
918
- <div class="gvs-map-theme-legend-entry">
919
- <span class="gvs-map-theme-color" style="background-color: ${COLORS["PALETTE_2"]}"></span>
920
- ${this._t.gvs["map_theme_age_2"]}
921
- </div>
922
- <div class="gvs-map-theme-legend-entry">
923
- <span class="gvs-map-theme-color" style="background-color: ${COLORS["PALETTE_1"]}"></span>
924
- ${this._t.gvs["map_theme_age_1"]}
925
- </div>
926
- </div>
927
- </div>
928
- <div id="gvs-map-theme-legend-type" class="gvs-map-theme-legend gvs-hidden">
929
- <div class="gvs-map-theme-legend-entry">
930
- <span class="gvs-map-theme-color" style="background-color: ${COLORS.QUALI_1}"></span>
931
- ${this._t.gvs.picture_360}
932
- </div>
933
- <div class="gvs-map-theme-legend-entry">
934
- <span class="gvs-map-theme-color" style="background-color: ${COLORS.QUALI_2}"></span>
935
- ${this._t.gvs.picture_flat}
936
- </div>
937
- </div>
938
- <div id="gvs-map-theme-legend-score" class="gvs-map-theme-legend gvs-hidden">
939
- ${QUALITYSCORE_VALUES.map(pv => "<span class=\"gvs-qualityscore\" style=\"background-color: "+pv.color+";\">"+pv.label+"</span>").join("")}
940
- <button id="gvs-map-theme-quality-help" class="gvs-btn-link" title="${this._t.gvs.metadata_quality_help}">${fat(faInfoCircle, {transform: {x: 6, size: 24}})}</button>
941
- </div>
942
- </div>`;
943
-
944
- pnlLayers.querySelector("#gvs-map-theme-quality-help").addEventListener("click", () => this._showQualityScoreDoc());
945
-
946
- // Map theme events
947
- const fMapTheme = pnlLayers.querySelector("#gvs-map-theme");
948
- const onChange = () => {
949
- this._onMapThemeChange();
950
- this._onMapFiltersChange();
951
- };
952
- fMapTheme.addEventListener("change", onChange);
953
- fMapTheme.addEventListener("keypress", onChange);
954
- fMapTheme.addEventListener("paste", onChange);
955
- fMapTheme.addEventListener("input", onChange);
956
-
957
- // Map background events
958
- if(this._viewer.map.hasTwoBackgrounds()) {
959
- const imgBgAerial = pnlLayers.querySelector("#gvs-map-bg-aerial-img");
960
- imgBgAerial.src = BackgroundAerial;
961
- const imgBgStreets = pnlLayers.querySelector("#gvs-map-bg-streets-img");
962
- imgBgStreets.src = BackgroundStreets;
963
- const radioBgAerial = pnlLayers.querySelector("#gvs-map-bg-aerial");
964
- const radioBgStreets = pnlLayers.querySelector("#gvs-map-bg-streets");
965
- const onBgChange = e => {
966
- this._viewer.map.setBackground(e.target.value);
967
- };
968
- radioBgAerial.addEventListener("change", onBgChange);
969
- radioBgStreets.addEventListener("change", onBgChange);
970
- this._viewer.addEventListener("map:background-changed", e => this._onMapBackgroundChange(e.detail.background));
971
- this._onMapBackgroundChange(this._viewer.map.getBackground());
972
- }
973
- }
974
-
975
- /**
976
- * Change the selected background in radio buttons
977
- * @param {string} bg The background to use
978
- * @private
979
- */
980
- _onMapBackgroundChange(bg) {
981
- const radioBgAerial = document.getElementById("gvs-map-bg-aerial");
982
- const radioBgStreets = document.getElementById("gvs-map-bg-streets");
983
- if(bg === "aerial") { radioBgAerial.checked = true; }
984
- else { radioBgStreets.checked = true; }
985
- }
986
-
987
- /**
988
- * Updates map theme legend when theme changes.
989
- * @private
990
- */
991
- _onMapThemeChange() {
992
- const fMapTheme = document.getElementById("gvs-map-theme");
993
- const layerBtn = document.getElementById("gvs-map-layers");
994
- const t = fMapTheme.value;
995
- layerBtn.setActive(t !== "default");
996
- for(let d of document.getElementsByClassName("gvs-map-theme-legend")) {
997
- if(d.id == "gvs-map-theme-legend-"+t) {
998
- d.classList.remove("gvs-hidden");
999
- }
1000
- else {
1001
- d.classList.add("gvs-hidden");
1002
- }
1003
- }
1004
- }
1005
-
1006
- /**
1007
- * Creates pictures filters component.
1008
- * This should be called only if map is enabled.
1009
- * @private
1010
- */
1011
- _initWidgetFilters(hasUserSearch, hasQualityScore) {
1012
- const btnFilter = createExpandableButton("gvs-filter", faSliders, this._t.gvs.filters, this, ["gvs-filter-unset-btn"]);
1013
- const pnlFilter = createPanel(this, btnFilter, []);
1014
- pnlFilter.innerHTML = `
1015
- <form id="gvs-filter-form">
1016
- <div class="gvs-filter-block">
1017
- <div class="gvs-filter-zoomin">${this._t.gvs.filter_zoom_in}</div>
1018
- <h4>${fat(faCalendar)} ${this._t.gvs.filter_date}</h4>
1019
- <div class="gvs-input-shortcuts">
1020
- <button data-for="gvs-filter-date-from" data-value="${new Date(new Date().setMonth(new Date().getMonth() - 1)).toISOString().split("T")[0]}">${this._t.gvs.filter_date_1month}</button>
1021
- <button data-for="gvs-filter-date-from" data-value="${new Date(new Date().setFullYear(new Date().getFullYear() - 1)).toISOString().split("T")[0]}">${this._t.gvs.filter_date_1year}</button>
1022
- </div>
1023
- <div class="gvs-input-group">
1024
- <input type="date" id="gvs-filter-date-from" />
1025
- ${fat(faArrowRight)}
1026
- <input type="date" id="gvs-filter-date-end" />
1027
- </div>
1028
- </div>
1029
- <div class="gvs-filter-block">
1030
- <h4>${fat(faImage)} ${this._t.gvs.filter_picture}</h4>
1031
- <div class="gvs-input-group gvs-checkbox-btns" style="justify-content: center;">
1032
- <input type="checkbox" id="gvs-filter-type-flat" name="flat" />
1033
- <label for="gvs-filter-type-flat">${fat(faImage)} ${this._t.gvs.picture_flat}</label>
1034
- <input type="checkbox" id="gvs-filter-type-360" name="360" />
1035
- <label for="gvs-filter-type-360">${fat(faPanorama)} ${this._t.gvs.picture_360}</label>
1036
- </div>
1037
- </div>
1038
- </form>
1039
- `;
1040
- const form = pnlFilter.children[0];
1041
- createGroup(
1042
- "gvs-widget-filter",
1043
- this._viewer.isWidthSmall() ? "main-top-right" : "main-top-left",
1044
- this,
1045
- [btnFilter, pnlFilter],
1046
- ["gvs-group-large", "gvs-group-btnpanel", "gvs-only-map", "gvs-print-hidden"]
1047
- );
1048
-
1049
- const resetBtn = btnFilter.querySelector(".gvs-filter-unset-btn");
1050
- if(resetBtn) {
1051
- resetBtn.addEventListener("click", e => {
1052
- e.stopPropagation();
1053
- form.reset();
1054
- });
1055
- }
1056
-
1057
- if(this._viewer.isWidthSmall()) {
1058
- pnlFilter.style.width = `${this._viewer.container.offsetWidth - 70}px`;
1059
- }
1060
-
1061
- // Create qualityscore filter
1062
- if(hasQualityScore) {
1063
- const block = document.createElement("div");
1064
- block.classList.add("gvs-filter-block");
1065
- form.appendChild(block);
1066
-
1067
- const zoomLbl = document.createElement("div");
1068
- zoomLbl.classList.add("gvs-filter-zoomin");
1069
- zoomLbl.appendChild(document.createTextNode(this._t.gvs.filter_zoom_in));
1070
- block.appendChild(zoomLbl);
1071
-
1072
- const title = document.createElement("h4");
1073
- title.innerHTML = `${fat(faMedal)} ${this._t.gvs.filter_qualityscore} <button class="gvs-btn-link" title="${this._t.gvs.metadata_quality_help}">${fat(faInfoCircle)}</button>`;
1074
- title.style.marginBottom = "3px";
1075
- title.lastChild.addEventListener("click", () => this._showQualityScoreDoc());
1076
- block.appendChild(title);
1077
-
1078
- const div = document.createElement("div");
1079
- div.id = "gvs-filter-qualityscore";
1080
- div.classList.add("gvs-input-group");
1081
-
1082
- QUALITYSCORE_VALUES.forEach(pv => {
1083
- const input = document.createElement("input");
1084
- input.id = "gvs-filter-qualityscore-" + pv.label;
1085
- input.type = "checkbox";
1086
- input.name = "qualityscore";
1087
- input.value = pv.label;
1088
-
1089
- const label = document.createElement("label");
1090
- label.setAttribute("for", input.id);
1091
- label.title = this._t.gvs.filter_qualityscore_help;
1092
- label.appendChild(document.createTextNode(pv.label));
1093
- label.style.backgroundColor = pv.color;
1094
-
1095
- div.appendChild(input);
1096
- div.appendChild(label);
1097
- });
1098
-
1099
- block.appendChild(div);
1100
- }
1101
-
1102
- // Create search bar for users
1103
- if(hasUserSearch) {
1104
- const block = document.createElement("div");
1105
- block.classList.add("gvs-filter-block");
1106
- form.appendChild(block);
1107
-
1108
- const title = document.createElement("h4");
1109
- title.innerHTML = `${fat(faUser)} ${this._t.gvs.filter_user}`;
1110
- block.appendChild(title);
1111
-
1112
- // Shortcut for my own pictures
1113
- const userAccount = getUserAccount();
1114
- let mypics;
1115
- if(userAccount) {
1116
- const shortcuts = document.createElement("div");
1117
- shortcuts.classList.add("gvs-input-shortcuts");
1118
- mypics = document.createElement("button");
1119
- mypics.appendChild(document.createTextNode(this._t.gvs.filter_user_mypics));
1120
- shortcuts.appendChild(mypics);
1121
- block.appendChild(shortcuts);
1122
- }
1123
-
1124
- const input = document.createElement("div");
1125
- input.id = "gvs-filter-user";
1126
- input.classList.add("gvs-input-group");
1127
-
1128
- let userSearch;
1129
- userSearch = createSearchBar(
1130
- "gvs-filter-search-user",
1131
- this._t.gvs.search_user,
1132
- q => {
1133
- userSearch.classList.remove("gvs-filter-active");
1134
- return this._viewer._api.searchUsers(q)
1135
- .then(data => ((data || [])
1136
- .map(f => ({
1137
- title: f.label,
1138
- data: f
1139
- }))
1140
- ));
1141
- },
1142
- d => {
1143
- if(d) { userSearch.classList.add("gvs-filter-active"); }
1144
- else { userSearch.classList.remove("gvs-filter-active"); }
1145
- return this._viewer.map.setVisibleUsers(d ? [d.data.id] : ["geovisio"]);
1146
- },
1147
- this,
1148
- true
1149
- );
1150
- block.addEventListener("reset", () => {
1151
- userSearch.classList.remove("gvs-filter-active");
1152
- userSearch.resetSearch();
1153
- });
1154
- input.appendChild(userSearch);
1155
- block.appendChild(input);
1156
-
1157
- // Trigger "my pictures" shortcut action
1158
- if(userAccount) {
1159
- mypics.addEventListener("click", () => {
1160
- const userInput = userSearch.querySelector("input");
1161
- if(userInput.value === userAccount.name) {
1162
- userSearch.resetSearch();
1163
- }
1164
- else {
1165
- userInput.goItem({ title: userAccount.name, data: { id: userAccount.id } });
1166
- }
1167
- });
1168
- }
1169
- }
1170
-
1171
- // Shortcuts
1172
- pnlFilter.querySelectorAll(".gvs-input-shortcuts button").forEach(btn => {
1173
- btn.addEventListener("click", () => {
1174
- const elem = document.getElementById(btn.getAttribute("data-for"));
1175
- const val = btn.getAttribute("data-value");
1176
- if(elem) {
1177
- if(elem.value !== val) { elem.value = val; }
1178
- else { elem.value = ""; }
1179
- }
1180
- });
1181
- });
1182
-
1183
- // Fields change events (for active highlighting)
1184
- const fMinDate = document.getElementById("gvs-filter-date-from");
1185
- const fMaxDate = document.getElementById("gvs-filter-date-end");
1186
- [fMinDate, fMaxDate].forEach(f => {
1187
- f.addEventListener("change", () => {
1188
- if(f.value) { f.classList.add("gvs-filter-active"); }
1189
- else { f.classList.remove("gvs-filter-active"); }
1190
- });
1191
- });
1192
-
1193
- // Form update events
1194
- this._formDelay = null;
1195
-
1196
- const onFormChange = () => {
1197
- if(this._formDelay) { clearTimeout(this._formDelay); }
1198
-
1199
- this._formDelay = setTimeout(() => {
1200
- this._onMapFiltersChange();
1201
- }, 250);
1202
- };
1203
-
1204
- form.addEventListener("change", onFormChange);
1205
- form.addEventListener("reset", onFormChange);
1206
- form.addEventListener("submit", e => {
1207
- onFormChange(e);
1208
- e.preventDefault();
1209
- return false;
1210
- }, true);
1211
-
1212
- for(let i of form.getElementsByTagName("input")) {
1213
- i.addEventListener("change", onFormChange);
1214
- i.addEventListener("keypress", onFormChange);
1215
- i.addEventListener("paste", onFormChange);
1216
- i.addEventListener("input", onFormChange);
1217
- }
1218
- }
1219
-
1220
- /**
1221
- * Send viewer new map filters values.
1222
- * @private
1223
- */
1224
- _onMapFiltersChange() {
1225
- const fMinDate = document.getElementById("gvs-filter-date-from");
1226
- const fMaxDate = document.getElementById("gvs-filter-date-end");
1227
- const fTypeFlat = document.getElementById("gvs-filter-type-flat");
1228
- const fType360 = document.getElementById("gvs-filter-type-360");
1229
- const fMapTheme = document.getElementById("gvs-map-theme");
1230
-
1231
- let type = "";
1232
- if(fType360.checked && !fTypeFlat.checked) { type = "equirectangular"; }
1233
- if(!fType360.checked && fTypeFlat.checked) { type = "flat"; }
1234
-
1235
- let qualityscore = [];
1236
- if(this._viewer?.map?._hasQualityScore()) {
1237
- const fScoreA = document.getElementById("gvs-filter-qualityscore-A");
1238
- const fScoreB = document.getElementById("gvs-filter-qualityscore-B");
1239
- const fScoreC = document.getElementById("gvs-filter-qualityscore-C");
1240
- const fScoreD = document.getElementById("gvs-filter-qualityscore-D");
1241
- const fScoreE = document.getElementById("gvs-filter-qualityscore-E");
1242
- if(fScoreA.checked) { qualityscore.push(5); }
1243
- if(fScoreB.checked) { qualityscore.push(4); }
1244
- if(fScoreC.checked) { qualityscore.push(3); }
1245
- if(fScoreD.checked) { qualityscore.push(2); }
1246
- if(fScoreE.checked) { qualityscore.push(1); }
1247
- if(qualityscore.length == 5) { qualityscore = []; }
1248
- }
1249
-
1250
- const values = {
1251
- minDate: fMinDate.value,
1252
- maxDate: fMaxDate.value,
1253
- type,
1254
- theme: fMapTheme.value,
1255
- qualityscore,
1256
- };
1257
-
1258
- this._viewer.setFilters(values);
1259
- }
1260
-
1261
- /**
1262
- * Listen to viewer events to follow map filters changes.
1263
- * @private
1264
- */
1265
- _listenMapFiltersChanges() {
1266
- const btnFilter = document.getElementById("gvs-filter");
1267
- const fMinDate = document.getElementById("gvs-filter-date-from");
1268
- const fMaxDate = document.getElementById("gvs-filter-date-end");
1269
- const fTypeFlat = document.getElementById("gvs-filter-type-flat");
1270
- const fType360 = document.getElementById("gvs-filter-type-360");
1271
- const fMapTheme = document.getElementById("gvs-map-theme");
1272
- const fScoreA = document.getElementById("gvs-filter-qualityscore-A");
1273
- const fScoreB = document.getElementById("gvs-filter-qualityscore-B");
1274
- const fScoreC = document.getElementById("gvs-filter-qualityscore-C");
1275
- const fScoreD = document.getElementById("gvs-filter-qualityscore-D");
1276
- const fScoreE = document.getElementById("gvs-filter-qualityscore-E");
1277
-
1278
- // Update widget based on programmatic filter changes
1279
- this._viewer.addEventListener("filters-changed", e => {
1280
- if(e.detail.minDate) {
1281
- fMinDate.value = e.detail.minDate;
1282
- fMinDate.classList.add("gvs-filter-active");
1283
- }
1284
- else { fMinDate.classList.remove("gvs-filter-active"); }
1285
- if(e.detail.maxDate) {
1286
- fMaxDate.value = e.detail.maxDate;
1287
- fMaxDate.classList.add("gvs-filter-active");
1288
- }
1289
- else { fMaxDate.classList.remove("gvs-filter-active"); }
1290
- if(e.detail.theme) { fMapTheme.value = e.detail.theme; }
1291
- if(e.detail.type) {
1292
- fType360.checked = ["", "equirectangular"].includes(e.detail.type);
1293
- fTypeFlat.checked = ["", "flat"].includes(e.detail.type);
1294
- }
1295
- if(e.detail.qualityscore) {
1296
- fScoreA.checked = e.detail.qualityscore.includes(5) && e.detail.qualityscore.length < 5;
1297
- fScoreB.checked = e.detail.qualityscore.includes(4) && e.detail.qualityscore.length < 5;
1298
- fScoreC.checked = e.detail.qualityscore.includes(3) && e.detail.qualityscore.length < 5;
1299
- fScoreD.checked = e.detail.qualityscore.includes(2) && e.detail.qualityscore.length < 5;
1300
- fScoreE.checked = e.detail.qualityscore.includes(1) && e.detail.qualityscore.length < 5;
1301
- }
1302
- let activeFilters = (
1303
- Object.keys(e.detail).filter(d => d && d !== "theme").length > 0
1304
- || this._viewer.map.getVisibleUsers().filter(u => u !== "geovisio").length > 0
1305
- );
1306
- btnFilter.setActive(activeFilters);
1307
- this._onMapThemeChange();
1308
- });
1309
-
1310
- // Listen to user visibility changes to switch the filter active icon
1311
- this._viewer.addEventListener("map:users-changed", e => {
1312
- let activeFilters = (
1313
- Object.keys(this._viewer._mapFilters).filter(d => d && d !== "theme").length > 0
1314
- || e.detail.usersIds.filter(u => u !== "geovisio").length > 0
1315
- );
1316
- btnFilter.setActive(activeFilters);
1317
- });
1318
-
1319
- // Show/hide zoom in warning when map zoom changes
1320
- const lblsZoomIn = document.getElementsByClassName("gvs-filter-zoomin");
1321
- const changeLblZoomInDisplay = () => {
1322
- for(let lblZoomIn of lblsZoomIn) {
1323
- if(this._viewer.map.getZoom() < 7) { lblZoomIn.style.display = null; }
1324
- else { lblZoomIn.style.display = "none"; }
1325
- }
1326
- };
1327
- changeLblZoomInDisplay();
1328
- this._viewer.map.on("zoomend", changeLblZoomInDisplay);
1329
- }
1330
-
1331
- /**
1332
- * Creates share map/picture widget.
1333
- * @private
1334
- */
1335
- _initWidgetShare() {
1336
- const btnShare = createButton("gvs-share", fa(faShareNodes), this._t.gvs.share, ["gvs-btn-large"]);
1337
- const pnlShare = createPanel(this, btnShare, []);
1338
- pnlShare.innerHTML = `
1339
- <div class="gvs-hidden gvs-needs-picture">
1340
- <p id="gvs-share-license" style="margin: 0 0 10px 0;"></p>
1341
- </div>
1342
- <h4 style="margin-top: 0">${fat(faLink)} ${this._t.gvs.share_links}</h4>
1343
- <div id="gvs-share-links" class="gvs-input-btn">
1344
- <a id="gvs-share-image" class="gvs-link-btn gvs-hidden gvs-needs-picture" download target="_blank">${fat(faCloudArrowDown)} ${this._t.gvs.share_image}</a>
1345
- <button id="gvs-share-url" data-copy="true" style="flex-basis: 100%; flex-grow: 2; flex-shrink: 2;">${fat(faCopy)} ${this._t.gvs.share_page}</button>
1346
- <button id="gvs-share-print" style="flex-basis: 100%; flex-grow: 2; flex-shrink: 2;">${fat(faPrint)} ${this._t.gvs.share_print}</button>
1347
- </div>
1348
- <h4>
1349
- ${fat(faMap)} ${this._t.gvs.share_embed}
1350
- <a href="https://docs.panoramax.fr/web-viewer/03_URL_settings/"
1351
- title="${this._t.gvs.share_embed_docs}"
1352
- target="_blank"
1353
- style="vertical-align: middle">
1354
- ${fat(faCircleInfo)}
1355
- </a>
1356
- </h4>
1357
- <div class="gvs-input-btn">
1358
- <textarea id="gvs-share-iframe" readonly></textarea>
1359
- <button data-input="gvs-share-iframe">${fat(faCopy)} ${this._t.gvs.copy}</button>
1360
- </div>
1361
- <h4 class="gvs-hidden gvs-needs-picture">${fat(faPen)} ${this._t.gvs.edit_osm}</h4>
1362
- <div class="gvs-input-btn gvs-hidden gvs-needs-picture" style="justify-content: center">
1363
- <a id="gvs-edit-id" class="gvs-link-btn" target="_blank">${fat(faLocationDot)} ${this._t.gvs.id}</a>
1364
- <button id="gvs-edit-josm" title="${this._t.gvs.josm_live}">${fat(faSatelliteDish)} ${this._t.gvs.josm}</button>
1365
- </div>
1366
- `;
1367
- createGroup(
1368
- "gvs-widget-share",
1369
- "main-bottom-right",
1370
- this,
1371
- [btnShare, pnlShare],
1372
- ["gvs-group-large", "gvs-group-btnpanel", "gvs-mobile-hidden", "gvs-print-hidden"]
1373
- );
1374
-
1375
- const grpLinks = document.getElementById("gvs-share-links");
1376
- const hdLink = document.getElementById("gvs-share-image");
1377
- const pageLink = document.getElementById("gvs-share-url");
1378
-
1379
- // Add RSS link if available
1380
- if(this._viewer._api.getRSSURL()) {
1381
- const btnRss = document.createElement("a");
1382
- btnRss.id = "gvs-share-rss";
1383
- btnRss.classList.add("gvs-link-btn");
1384
- btnRss.setAttribute("target", "_blank");
1385
- btnRss.setAttribute("title", this._t.gvs.share_rss_title);
1386
- btnRss.appendChild(fa(faSquareRss));
1387
- btnRss.appendChild(document.createTextNode(this._t.gvs.share_rss));
1388
- grpLinks.insertBefore(btnRss, pageLink);
1389
- }
1390
-
1391
- // Update picture download links
1392
- this._viewer.addEventListener("psv:picture-loaded", () => {
1393
- const picMeta = this._viewer.psv.getPictureMetadata();
1394
- hdLink.href = picMeta?.panorama?.hdUrl;
1395
-
1396
- const lblLicense = document.getElementById("gvs-share-license");
1397
- lblLicense.innerHTML = picMeta?.caption?.license ? this._t.gvs.legend_license.replace("{l}", picMeta.caption.license) : "";
1398
-
1399
- const picElems = pnlShare.getElementsByClassName("gvs-needs-picture");
1400
- for(let i=0; i < picElems.length; i++) {
1401
- const h = picElems[i];
1402
- if(picMeta) {
1403
- h.classList.remove("gvs-hidden");
1404
- }
1405
- else {
1406
- h.classList.add("gvs-hidden");
1407
- }
1408
- }
1409
- });
1410
-
1411
- // Update links
1412
- const updateLinks = e => {
1413
- const baseUrl = e?.detail?.url || window.location.href.replace(/\/$/, "");
1414
- const iframeBaseUrl = this._options.iframeBaseURL ?
1415
- this._options.iframeBaseURL + window.location.hash
1416
- : baseUrl;
1417
- const fUrl = pnlShare.querySelector("#gvs-share-url");
1418
- const fIframe = pnlShare.querySelector("#gvs-share-iframe");
1419
- const btnId = pnlShare.querySelector("#gvs-edit-id");
1420
- const btnRss = pnlShare.querySelector("#gvs-share-rss");
1421
-
1422
- fUrl.setAttribute("data-copy", this._viewer?._hash?.getShortLink(baseUrl) || baseUrl);
1423
- fIframe.innerText = `<iframe src="${iframeBaseUrl}" style="border: none; width: 500px; height: 300px"></iframe>`;
1424
-
1425
- const meta = this._viewer.psv.getPictureMetadata();
1426
- if(meta) {
1427
- const idOpts = {
1428
- "map": `19/${meta.gps[1]}/${meta.gps[0]}`,
1429
- "source": "Panoramax",
1430
- "photo_overlay": "panoramax",
1431
- "photo": `panoramax/${meta.id}`,
1432
- };
1433
- btnId.setAttribute("href", `${this._options.editIdUrl}#${new URLSearchParams(idOpts).toString()}`);
1434
- }
1435
-
1436
- if(btnRss) {
1437
- btnRss.setAttribute("href", this._viewer._api.getRSSURL(this._viewer?.map?.getBounds()));
1438
- }
1439
- };
1440
-
1441
- updateLinks();
1442
- this._viewer.addEventListener("ready", updateLinks, { once: true });
1443
- this._viewer?._hash?.addEventListener("url-changed", updateLinks);
1444
-
1445
- // Copy to clipboard on button click
1446
- enableCopyButton(pnlShare, this._viewer._t);
1447
-
1448
- // JOSM live edit button
1449
- const btnJosm = pnlShare.querySelector("#gvs-edit-josm");
1450
- btnJosm.addEventListener("click", () => {
1451
- // Disable
1452
- if(btnJosm.classList.contains("gvs-btn-active")) {
1453
- this._viewer.toggleJOSMLive(false);
1454
- }
1455
- // Enable
1456
- else {
1457
- this._viewer.toggleJOSMLive(true).catch(e => {
1458
- console.warn(e);
1459
- alert(this._t.gvs.error_josm);
1460
- });
1461
- }
1462
- });
1463
- this._viewer.addEventListener("josm-live-enabled", () => btnJosm.classList.add("gvs-btn-active"));
1464
- this._viewer.addEventListener("josm-live-disabled", () => btnJosm.classList.remove("gvs-btn-active"));
1465
-
1466
- // Print button
1467
- const printLink = pnlShare.querySelector("#gvs-share-print");
1468
- printLink.addEventListener("click", window.print.bind(window));
1469
- }
1470
- }