@panoramax/web-viewer 3.2.3 → 4.0.0-develop-39167b4d

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 (255) hide show
  1. package/.gitlab-ci.yml +13 -6
  2. package/CHANGELOG.md +53 -1
  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 +12 -12
  7. package/build/index.css.map +1 -1
  8. package/build/index.html +1 -1
  9. package/build/index.js +2126 -14
  10. package/build/index.js.map +1 -1
  11. package/build/map.html +1 -1
  12. package/build/photo.html +1 -0
  13. package/build/static/media/atkinson-hyperlegible-next-latin-400-normal..woff +0 -0
  14. package/build/static/media/atkinson-hyperlegible-next-latin-400-normal..woff2 +0 -0
  15. package/build/static/media/atkinson-hyperlegible-next-latin-ext-400-normal..woff +0 -0
  16. package/build/static/media/atkinson-hyperlegible-next-latin-ext-400-normal..woff2 +0 -0
  17. package/build/viewer.html +12 -1
  18. package/build/widgets.html +1 -0
  19. package/config/jest/mocks.js +201 -0
  20. package/config/paths.js +2 -0
  21. package/config/webpack.config.js +52 -0
  22. package/docs/03_URL_settings.md +14 -16
  23. package/docs/05_Compatibility.md +59 -76
  24. package/docs/09_Develop.md +46 -11
  25. package/docs/90_Releases.md +2 -2
  26. package/docs/images/class_diagram.drawio +60 -45
  27. package/docs/images/class_diagram.jpg +0 -0
  28. package/docs/images/screenshot.jpg +0 -0
  29. package/docs/index.md +135 -0
  30. package/docs/reference/components/core/Basic.md +196 -0
  31. package/docs/reference/components/core/CoverageMap.md +210 -0
  32. package/docs/reference/components/core/Editor.md +224 -0
  33. package/docs/reference/components/core/PhotoViewer.md +307 -0
  34. package/docs/reference/components/core/Viewer.md +350 -0
  35. package/docs/reference/components/layout/BottomDrawer.md +35 -0
  36. package/docs/reference/components/layout/CorneredGrid.md +29 -0
  37. package/docs/reference/components/layout/Mini.md +45 -0
  38. package/docs/reference/components/layout/Tabs.md +45 -0
  39. package/docs/reference/components/menus/MapBackground.md +32 -0
  40. package/docs/reference/components/menus/MapFilters.md +15 -0
  41. package/docs/reference/components/menus/MapLayers.md +15 -0
  42. package/docs/reference/components/menus/MapLegend.md +15 -0
  43. package/docs/reference/components/menus/PictureLegend.md +16 -0
  44. package/docs/reference/components/menus/PictureMetadata.md +15 -0
  45. package/docs/reference/components/menus/PlayerOptions.md +15 -0
  46. package/docs/reference/components/menus/QualityScoreDoc.md +15 -0
  47. package/docs/reference/components/menus/ReportForm.md +15 -0
  48. package/docs/reference/components/menus/ShareMenu.md +15 -0
  49. package/docs/reference/components/ui/Button.md +40 -0
  50. package/docs/reference/components/ui/ButtonGroup.md +36 -0
  51. package/docs/reference/components/ui/CopyButton.md +38 -0
  52. package/docs/reference/components/ui/Grade.md +32 -0
  53. package/docs/reference/components/ui/LinkButton.md +45 -0
  54. package/docs/reference/components/ui/ListGroup.md +22 -0
  55. package/docs/reference/components/ui/Loader.md +56 -0
  56. package/docs/reference/components/ui/Map.md +239 -0
  57. package/docs/reference/components/ui/MapMore.md +256 -0
  58. package/docs/reference/components/ui/Photo.md +385 -0
  59. package/docs/reference/components/ui/Popup.md +56 -0
  60. package/docs/reference/components/ui/ProgressBar.md +32 -0
  61. package/docs/reference/components/ui/QualityScore.md +45 -0
  62. package/docs/reference/components/ui/SearchBar.md +63 -0
  63. package/docs/reference/components/ui/TogglableGroup.md +39 -0
  64. package/docs/reference/components/ui/widgets/GeoSearch.md +32 -0
  65. package/docs/reference/components/ui/widgets/Legend.md +49 -0
  66. package/docs/reference/components/ui/widgets/MapFiltersButton.md +33 -0
  67. package/docs/reference/components/ui/widgets/MapLayersButton.md +15 -0
  68. package/docs/reference/components/ui/widgets/OSMEditors.md +15 -0
  69. package/docs/reference/components/ui/widgets/PictureLegendActions.md +32 -0
  70. package/docs/reference/components/ui/widgets/Player.md +33 -0
  71. package/docs/reference/components/ui/widgets/Zoom.md +15 -0
  72. package/docs/reference/utils/API.md +334 -0
  73. package/docs/reference/utils/InitParameters.md +68 -0
  74. package/docs/reference/utils/URLHandler.md +107 -0
  75. package/docs/reference.md +79 -0
  76. package/docs/shortcuts.md +11 -0
  77. package/docs/tutorials/aerial_imagery.md +19 -0
  78. package/docs/tutorials/authentication.md +10 -0
  79. package/docs/tutorials/custom_widgets.md +59 -0
  80. package/docs/tutorials/map_style.md +39 -0
  81. package/docs/tutorials/migrate_v4.md +153 -0
  82. package/docs/tutorials/synced_coverage.md +43 -0
  83. package/mkdocs.yml +66 -5
  84. package/package.json +22 -17
  85. package/public/editor.html +21 -29
  86. package/public/index.html +17 -12
  87. package/public/map.html +19 -18
  88. package/public/photo.html +55 -0
  89. package/public/viewer.html +22 -26
  90. package/public/widgets.html +306 -0
  91. package/scripts/doc.js +79 -0
  92. package/src/components/core/Basic.css +48 -0
  93. package/src/components/core/Basic.js +349 -0
  94. package/src/components/core/CoverageMap.css +9 -0
  95. package/src/components/core/CoverageMap.js +139 -0
  96. package/src/components/core/Editor.css +23 -0
  97. package/src/components/core/Editor.js +390 -0
  98. package/src/components/core/PhotoViewer.css +48 -0
  99. package/src/components/core/PhotoViewer.js +499 -0
  100. package/src/components/core/Viewer.css +98 -0
  101. package/src/components/core/Viewer.js +564 -0
  102. package/src/components/core/index.js +12 -0
  103. package/src/components/index.js +13 -0
  104. package/src/components/layout/BottomDrawer.js +257 -0
  105. package/src/components/layout/CorneredGrid.js +112 -0
  106. package/src/components/layout/Mini.js +117 -0
  107. package/src/components/layout/Tabs.js +133 -0
  108. package/src/components/layout/index.js +9 -0
  109. package/src/components/menus/MapBackground.js +106 -0
  110. package/src/components/menus/MapFilters.js +400 -0
  111. package/src/components/menus/MapLayers.js +143 -0
  112. package/src/components/menus/MapLegend.js +34 -0
  113. package/src/components/menus/PictureLegend.js +257 -0
  114. package/src/components/menus/PictureMetadata.js +317 -0
  115. package/src/components/menus/PlayerOptions.js +95 -0
  116. package/src/components/menus/QualityScoreDoc.js +36 -0
  117. package/src/components/menus/ReportForm.js +133 -0
  118. package/src/components/menus/Share.js +100 -0
  119. package/src/components/menus/index.js +15 -0
  120. package/src/components/styles.js +383 -0
  121. package/src/components/ui/Button.js +77 -0
  122. package/src/components/ui/ButtonGroup.css +57 -0
  123. package/src/components/ui/ButtonGroup.js +68 -0
  124. package/src/components/ui/CopyButton.js +106 -0
  125. package/src/components/ui/Grade.js +54 -0
  126. package/src/components/ui/LinkButton.js +67 -0
  127. package/src/components/ui/ListGroup.js +66 -0
  128. package/src/components/ui/Loader.js +203 -0
  129. package/src/components/{Map.css → ui/Map.css} +5 -17
  130. package/src/components/{Map.js → ui/Map.js} +148 -156
  131. package/src/components/ui/MapMore.js +324 -0
  132. package/src/components/{Photo.css → ui/Photo.css} +6 -6
  133. package/src/components/{Photo.js → ui/Photo.js} +313 -101
  134. package/src/components/ui/Popup.js +145 -0
  135. package/src/components/ui/ProgressBar.js +104 -0
  136. package/src/components/ui/QualityScore.js +147 -0
  137. package/src/components/ui/SearchBar.js +367 -0
  138. package/src/components/ui/TogglableGroup.js +157 -0
  139. package/src/components/ui/index.js +22 -0
  140. package/src/components/ui/widgets/GeoSearch.css +21 -0
  141. package/src/components/ui/widgets/GeoSearch.js +139 -0
  142. package/src/components/ui/widgets/Legend.js +113 -0
  143. package/src/components/ui/widgets/MapFiltersButton.js +104 -0
  144. package/src/components/ui/widgets/MapLayersButton.js +79 -0
  145. package/src/components/ui/widgets/OSMEditors.js +155 -0
  146. package/src/components/ui/widgets/PictureLegendActions.js +117 -0
  147. package/src/components/ui/widgets/Player.css +7 -0
  148. package/src/components/ui/widgets/Player.js +151 -0
  149. package/src/components/ui/widgets/Zoom.js +82 -0
  150. package/src/components/ui/widgets/index.js +13 -0
  151. package/src/img/loader_base.jpg +0 -0
  152. package/src/img/panoramax.svg +13 -0
  153. package/src/img/switch_big.svg +20 -10
  154. package/src/index.js +7 -9
  155. package/src/translations/br.json +1 -0
  156. package/src/translations/da.json +38 -15
  157. package/src/translations/de.json +5 -3
  158. package/src/translations/en.json +35 -15
  159. package/src/translations/eo.json +38 -15
  160. package/src/translations/es.json +1 -1
  161. package/src/translations/fr.json +36 -16
  162. package/src/translations/hu.json +1 -1
  163. package/src/translations/it.json +39 -16
  164. package/src/translations/ja.json +182 -1
  165. package/src/translations/nl.json +106 -6
  166. package/src/translations/pl.json +1 -1
  167. package/src/translations/sv.json +182 -0
  168. package/src/translations/zh_Hant.json +35 -14
  169. package/src/utils/API.js +109 -49
  170. package/src/utils/InitParameters.js +388 -0
  171. package/src/utils/PhotoAdapter.js +1 -0
  172. package/src/utils/URLHandler.js +362 -0
  173. package/src/utils/geocoder.js +152 -0
  174. package/src/utils/{I18n.js → i18n.js} +7 -3
  175. package/src/utils/index.js +11 -0
  176. package/src/utils/{Map.js → map.js} +256 -77
  177. package/src/utils/picture.js +442 -0
  178. package/src/utils/utils.js +324 -0
  179. package/src/utils/widgets.js +55 -0
  180. package/tests/components/core/Basic.test.js +121 -0
  181. package/tests/components/core/BasicMock.js +25 -0
  182. package/tests/components/core/CoverageMap.test.js +20 -0
  183. package/tests/components/core/Editor.test.js +20 -0
  184. package/tests/components/core/PhotoViewer.test.js +57 -0
  185. package/tests/components/core/Viewer.test.js +84 -0
  186. package/tests/components/core/__snapshots__/PhotoViewer.test.js.snap +73 -0
  187. package/tests/components/core/__snapshots__/Viewer.test.js.snap +145 -0
  188. package/tests/components/ui/CopyButton.test.js +52 -0
  189. package/tests/components/ui/Loader.test.js +55 -0
  190. package/tests/components/{Map.test.js → ui/Map.test.js} +73 -61
  191. package/tests/components/{Photo.test.js → ui/Photo.test.js} +97 -63
  192. package/tests/components/ui/Popup.test.js +26 -0
  193. package/tests/components/ui/QualityScore.test.js +18 -0
  194. package/tests/components/ui/SearchBar.test.js +110 -0
  195. package/tests/components/ui/__snapshots__/CopyButton.test.js.snap +33 -0
  196. package/tests/components/ui/__snapshots__/Loader.test.js.snap +56 -0
  197. package/tests/components/{__snapshots__ → ui/__snapshots__}/Map.test.js.snap +11 -38
  198. package/tests/components/{__snapshots__ → ui/__snapshots__}/Photo.test.js.snap +70 -6
  199. package/tests/components/ui/__snapshots__/Popup.test.js.snap +29 -0
  200. package/tests/components/ui/__snapshots__/QualityScore.test.js.snap +11 -0
  201. package/tests/components/ui/__snapshots__/SearchBar.test.js.snap +65 -0
  202. package/tests/utils/API.test.js +83 -83
  203. package/tests/utils/InitParameters.test.js +499 -0
  204. package/tests/utils/URLHandler.test.js +401 -0
  205. package/tests/utils/__snapshots__/API.test.js.snap +10 -0
  206. package/tests/utils/__snapshots__/URLHandler.test.js.snap +21 -0
  207. package/tests/utils/__snapshots__/{Map.test.js.snap → geocoder.test.js.snap} +1 -1
  208. package/tests/utils/__snapshots__/map.test.js.snap +11 -0
  209. package/tests/utils/__snapshots__/picture.test.js.snap +327 -0
  210. package/tests/utils/__snapshots__/widgets.test.js.snap +19 -0
  211. package/tests/utils/geocoder.test.js +37 -0
  212. package/tests/utils/{I18n.test.js → i18n.test.js} +8 -8
  213. package/tests/utils/map.test.js +126 -0
  214. package/tests/utils/picture.test.js +745 -0
  215. package/tests/utils/utils.test.js +288 -0
  216. package/tests/utils/widgets.test.js +31 -0
  217. package/docs/01_Start.md +0 -149
  218. package/docs/02_Usage.md +0 -831
  219. package/docs/04_Advanced_examples.md +0 -216
  220. package/src/Editor.css +0 -37
  221. package/src/Editor.js +0 -361
  222. package/src/StandaloneMap.js +0 -114
  223. package/src/Viewer.css +0 -203
  224. package/src/Viewer.js +0 -1246
  225. package/src/components/CoreView.css +0 -70
  226. package/src/components/CoreView.js +0 -175
  227. package/src/components/Loader.css +0 -74
  228. package/src/components/Loader.js +0 -120
  229. package/src/img/loader_hd.jpg +0 -0
  230. package/src/utils/Exif.js +0 -193
  231. package/src/utils/Utils.js +0 -631
  232. package/src/utils/Widgets.js +0 -562
  233. package/src/viewer/URLHash.js +0 -469
  234. package/src/viewer/Widgets.css +0 -880
  235. package/src/viewer/Widgets.js +0 -1470
  236. package/tests/Editor.test.js +0 -126
  237. package/tests/StandaloneMap.test.js +0 -45
  238. package/tests/Viewer.test.js +0 -366
  239. package/tests/__snapshots__/Editor.test.js.snap +0 -298
  240. package/tests/__snapshots__/StandaloneMap.test.js.snap +0 -30
  241. package/tests/__snapshots__/Viewer.test.js.snap +0 -195
  242. package/tests/components/CoreView.test.js +0 -92
  243. package/tests/components/Loader.test.js +0 -38
  244. package/tests/components/__snapshots__/Loader.test.js.snap +0 -15
  245. package/tests/utils/Exif.test.js +0 -124
  246. package/tests/utils/Map.test.js +0 -113
  247. package/tests/utils/Utils.test.js +0 -300
  248. package/tests/utils/Widgets.test.js +0 -107
  249. package/tests/utils/__snapshots__/Exif.test.js.snap +0 -43
  250. package/tests/utils/__snapshots__/Utils.test.js.snap +0 -41
  251. package/tests/utils/__snapshots__/Widgets.test.js.snap +0 -44
  252. package/tests/viewer/URLHash.test.js +0 -559
  253. package/tests/viewer/Widgets.test.js +0 -127
  254. package/tests/viewer/__snapshots__/URLHash.test.js.snap +0 -108
  255. package/tests/viewer/__snapshots__/Widgets.test.js.snap +0 -403
package/src/Viewer.js DELETED
@@ -1,1246 +0,0 @@
1
- import "./Viewer.css";
2
- import { SYSTEM as PSSystem, DEFAULTS as PSDefaults } from "@photo-sphere-viewer/core";
3
- import Widgets from "./viewer/Widgets";
4
- import URLHash from "./viewer/URLHash";
5
- import { COLORS, QUALITYSCORE_VALUES, josmBboxParameters, linkMapAndPhoto } from "./utils/Utils";
6
- import CoreView from "./components/CoreView";
7
- import Photo, { PSV_DEFAULT_ZOOM, PSV_ANIM_DURATION, PIC_MAX_STAY_DURATION } from "./components/Photo";
8
- import Map from "./components/Map";
9
- import { TILES_PICTURES_ZOOM, MAP_EXPR_QUALITYSCORE } from "./utils/Map";
10
- import { enableCopyButton, fa } from "./utils/Widgets";
11
- import { faXmark } from "@fortawesome/free-solid-svg-icons/faXmark";
12
-
13
-
14
- const PSV_ZOOM_DELTA = 20;
15
- const PSV_MOVE_DELTA = Math.PI / 6;
16
- const MAP_MOVE_DELTA = 100;
17
- const JOSM_REMOTE_URL = "http://127.0.0.1:8111";
18
-
19
- const MAP_THEMES = {
20
- DEFAULT: "default",
21
- AGE: "age",
22
- TYPE: "type",
23
- SCORE: "score",
24
- };
25
-
26
-
27
- /**
28
- * Viewer is the main component of Panoramax JS library, showing pictures and map.
29
- *
30
- * Note that you can use any of the [CoreView](#CoreView) class functions as well.
31
- *
32
- * @param {string|Element} container The DOM element to create viewer into
33
- * @param {string} endpoint URL to API to use (must be a [STAC API](https://github.com/radiantearth/stac-api-spec/blob/main/overview.md))
34
- * @param {object} [options] Viewer options
35
- * @param {string} [options.selectedPicture] Initial picture identifier to display
36
- * @param {number[]} [options.position] Initial position to go to (in [lat, lon] format)
37
- * @param {boolean} [options.hash=true] Enable URL hash settings
38
- * @param {string} [options.lang] Override language to use (defaults to navigator language, or English if translation not available)
39
- * @param {int} [options.transition=250] Duration of stay on a picture during sequence play (excludes loading time)
40
- * @param {object} [options.fetchOptions=null] Set custom options for fetch calls made against API ([same syntax as fetch options parameter](https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters))
41
- * @param {string|string[]} [options.users] The IDs of users whom data should appear on map (defaults to all). Only works with API having a "user-xyz" or "user-xyz-style" endpoint.
42
- * @param {string} [options.picturesNavigation] The allowed navigation between pictures ("any": no restriction (default), "seq": only pictures in same sequence, "pic": only selected picture)
43
- * @param {boolean|object} [options.map=false] Enable contextual map for locating pictures. Setting to true or passing an object enables the map. Various settings can be passed, either the ones defined here, or any of [MapLibre GL settings](https://maplibre.org/maplibre-gl-js-docs/api/map/#map-parameters)
44
- * @param {boolean} [options.map.startWide] Show the map as main element at startup (defaults to false, viewer is wider at start)
45
- * @param {number} [options.map.minZoom=0] The minimum zoom level of the map (0-24).
46
- * @param {number} [options.map.maxZoom=24] The maximum zoom level of the map (0-24).
47
- * @param {string|object} [options.style] The map's MapLibre style. This can be an a JSON object conforming to the schema described in the [MapLibre Style Specification](https://maplibre.org/maplibre-gl-js-docs/style-spec/), or a URL string pointing to one.
48
- * @param {object} [options.map.raster] The MapLibre raster source for aerial background. This must be a JSON object following [MapLibre raster source definition](https://maplibre.org/maplibre-style-spec/sources/#raster).
49
- * @param {external:maplibre-gl.LngLatLike} [options.map.center=[0, 0]] The initial geographical centerpoint of the map. If `center` is not specified in the constructor options, MapLibre GL JS will look for it in the map's style object. If it is not specified in the style, either, it will default to `[0, 0]` Note: MapLibre GL uses longitude, latitude coordinate order (as opposed to latitude, longitude) to match GeoJSON.
50
- * @param {number} [options.map.zoom=0] The initial zoom level of the map. If `zoom` is not specified in the constructor options, MapLibre GL JS will look for it in the map's style object. If it is not specified in the style, either, it will default to `0`.
51
- * @param {external:maplibre-gl.LngLatBoundsLike} [options.map.bounds] The initial bounds of the map. If `bounds` is specified, it overrides `center` and `zoom` constructor options.
52
- * @param {object} [options.map.geocoder] Optional geocoder settings
53
- * @param {string} [options.map.geocoder.engine] Set the geocoder engine to use (nominatim, ban)
54
- * @param {string} [options.map.background] Choose default map background to display (streets or aerial, if raster aerial background available). Defaults to street.
55
- * @param {string} [options.map.theme=default] The colouring scheme to use for pictures and sequences on map (default, age, type)
56
- * @param {object} [options.widgets] Settings related to viewer buttons and widgets
57
- * @param {string} [options.widgets.editIdUrl] URL to the OpenStreetMap iD editor (defaults to OSM.org iD instance)
58
- * @param {string|Element} [options.widgets.customWidget] A user-defined widget to add (will be shown over "Share" button)
59
- * @param {string} [options.widgets.mapAttribution] Override the default map attribution (read from MapLibre style)
60
- * @param {string} [options.widgets.iframeBaseURL] Set a custom base URL for the "Share as iframe" menu (defaults to current page)
61
- *
62
- * @property {Map} map The map widget
63
- * @property {Photo} psv The photo widget
64
- */
65
- class Viewer extends CoreView {
66
- constructor(container, endpoint, options = {}){
67
- super(container, endpoint, options);
68
-
69
- if(this._options.map == null) { this._options.map = {}; }
70
- if(this._options.widgets == null) { this._options.widgets = {}; }
71
-
72
- // Set variables
73
- this._sequencePlaying = false;
74
- this._prevSequence = null;
75
- this._mapTheme = options?.map?.theme || MAP_THEMES.DEFAULT;
76
- this._picNav = options?.picturesNavigation || "any";
77
-
78
- // Skip all init phases for more in-depth testing
79
- if(this._options.testing) { return; }
80
-
81
- // Read initial options from URL hash
82
- let hashOpts;
83
- if(this._options.hash === true || this._options.hash === undefined) {
84
- this._hash = new URLHash(this);
85
- hashOpts = this._hash._getCurrentHash();
86
-
87
- if(hashOpts.map === "none") {
88
- this._options.map = false;
89
- }
90
-
91
- if(typeof this._options.map === "object") { this._options.map.hash = false; }
92
-
93
- // Restore focus
94
- if(this._options.map && hashOpts.focus) {
95
- this._options.map.startWide = hashOpts.focus === "map";
96
- }
97
-
98
- // Restore map background
99
- if(this._options.map && hashOpts.background) {
100
- this._options.map.background = hashOpts.background;
101
- }
102
-
103
- // Restore visible users
104
- if(this._options.map && hashOpts.users) {
105
- this._options.users = [...new Set(hashOpts.users.split(","))];
106
- }
107
-
108
- // Restore viewer position
109
- if(hashOpts.xyz) {
110
- const coords = this._hash.getXyzOptionsFromHashString(hashOpts.xyz);
111
- this.addEventListener("psv:picture-loaded", () => {
112
- this.psv.setXYZ(coords.x, coords.y, coords.z);
113
- }, { once: true });
114
- }
115
-
116
- // Restore map zoom/center
117
- if(this._options.map && typeof hashOpts.map === "string") {
118
- const mapOpts = this._hash.getMapOptionsFromHashString(hashOpts.map);
119
- if(mapOpts) {
120
- this._options.map = Object.assign({}, this._options.map, mapOpts);
121
- }
122
- }
123
-
124
- // Restore map filters
125
- if(this._options.map) {
126
- this.setFilters(this._hash.getMapFiltersFromHashVals(hashOpts), true);
127
- }
128
-
129
- // Restore picture from URL hash
130
- if(hashOpts.pic) {
131
- const picIds = hashOpts.pic.split(";"); // Handle multiple IDs coming from OSM
132
- if(picIds.length > 1) {
133
- console.warn("Multiple picture IDs passed in URL, only first one kept");
134
- }
135
- this._options.selectedPicture = picIds[0];
136
- }
137
-
138
- // Restore play speed
139
- if(typeof hashOpts.speed === "string") {
140
- this._options.transition = parseInt(hashOpts.speed);
141
- }
142
-
143
- // Restore pictures navigation
144
- if(hashOpts.nav) {
145
- this._picNav = hashOpts.nav;
146
- }
147
- }
148
-
149
- // Init all DOM and components
150
- this._initContainerStructure();
151
- try {
152
- this.psv = new Photo(this, this.psvContainer, {
153
- transitionDuration: this._options.transition,
154
- shouldGoFast: this._psvShouldGoFast.bind(this),
155
- keyboard: "always",
156
- keyboardActions: {
157
- ...PSDefaults.keyboardActions,
158
- "8": "ROTATE_UP",
159
- "2": "ROTATE_DOWN",
160
- "4": "ROTATE_LEFT",
161
- "6": "ROTATE_RIGHT",
162
-
163
- "PageUp": () => this.psv.goToNextPicture(),
164
- "9": () => this.psv.goToNextPicture(),
165
-
166
- "PageDown": () => this.psv.goToPrevPicture(),
167
- "3": () => this.psv.goToPrevPicture(),
168
-
169
- "5": () => this.moveCenter(),
170
- "*": () => this.moveCenter(),
171
-
172
- "Home": () => this.toggleFocus(),
173
- "7": () => this.toggleFocus(),
174
-
175
- "End": () => this.toggleUnfocusedVisible(),
176
- "1": () => this.toggleUnfocusedVisible(),
177
-
178
- " ": () => this.toggleSequencePlaying(),
179
- "0": () => this.toggleSequencePlaying(),
180
- },
181
- });
182
- this.psv.addEventListener("dblclick", () => {
183
- if(this.map && this.isMapWide()) { this.setFocus("pic"); }
184
- });
185
- }
186
- catch(e) {
187
- let err = !PSSystem.isWebGLSupported ? this._t.gvs.error_webgl : this._t.gvs.error_psv;
188
- this._loader.dismiss(e, err);
189
- }
190
-
191
- // Call appropriate functions at start according to initial options
192
- const onceStuffReady = () => {
193
- this._widgets = new Widgets(this, this._options.widgets);
194
-
195
- // Hide mini component if no selected picture
196
- if(this.map && !this._options.selectedPicture) {
197
- this.setUnfocusedVisible(false);
198
- }
199
-
200
- if(this._options.selectedPicture) {
201
- this.select(null, this._options.selectedPicture, true);
202
- this.addEventListener("psv:picture-loaded", () => {
203
- // Force setting of sequence ID after load
204
- if(!this._selectedSeqId && this.psv?._myVTour?.getCurrentNode()?.sequence?.id) {
205
- this.select(this.psv._myVTour.getCurrentNode().sequence.id, this._options.selectedPicture);
206
- }
207
- if(this.map && this._options.map) {
208
- this.map.jumpTo(this._options.map);
209
- }
210
- if(hashOpts?.focus === "meta") {
211
- this._widgets._showPictureMetadataPopup();
212
- }
213
- this._loader.dismiss();
214
- }, { once: true });
215
- }
216
- else {
217
- this._loader.dismiss();
218
- }
219
-
220
- if(this._options.position) {
221
- this.goToPosition(...this._options.position).catch(e => this._loader.dismiss(e, this._t.gvs.error_nopic));
222
- }
223
-
224
- if(this._hash && this.map) {
225
- this.map._attribution._container.classList.add("gvs-hidden");
226
- this._hash.bindMapEvents();
227
- if(this._mapFilters) {
228
- this.setFilters(this._mapFilters, true);
229
- }
230
-
231
- // Restore user ID in filters
232
- if(hashOpts.users) {
233
- Promise.all(
234
- this.map.getVisibleUsers()
235
- .filter(uid => uid != "geovisio")
236
- .map(uid => this._api.getUserName(uid))
237
- ).then(userNames => {
238
- userNames = userNames.filter(un => un != null).join(", ");
239
- const userSearchField = document.getElementById("gvs-filter-search-user").querySelector("input");
240
- if(userSearchField) {
241
- userSearchField.setItem(userNames);
242
- userSearchField.parentNode.classList.add("gvs-filter-active");
243
- }
244
- }).catch(e => console.warn("Error when looking up for user names", e));
245
- }
246
- }
247
-
248
- // Dismiss popup with Escape
249
- document.addEventListener("keyup", e => {
250
- if(e.key === "Escape" && !this.popupContainer.classList.contains("gvs-hidden")) {
251
- this.setPopup(false);
252
- }
253
- });
254
- };
255
-
256
- this._api.onceReady().then(() => {
257
- if(this._options.map) {
258
- if(this._options.map.doubleClickZoom === undefined) {
259
- this._options.map.doubleClickZoom = false;
260
- }
261
-
262
- this._initMap()
263
- .then(onceStuffReady)
264
- .catch(e => this._loader.dismiss(e, this._t.gvs.error_api_compatibility));
265
- }
266
- else {
267
- onceStuffReady();
268
- }
269
- });
270
- }
271
-
272
- getClassName() {
273
- return "Viewer";
274
- }
275
-
276
- /**
277
- * Ends all form of life in this object.
278
- *
279
- * This is useful for Single Page Applications (SPA), to remove various event listeners.
280
- */
281
- destroy() {
282
- super.destroy();
283
-
284
- // Delete sub-components
285
- this._widgets.destroy();
286
- delete this._widgets;
287
- this._hash.destroy();
288
- delete this._hash;
289
- if (this.map) {
290
- this.map.destroy();
291
- }
292
- delete this.map;
293
- delete this._mapFilters;
294
- this.psv.destroy();
295
- delete this.psv;
296
-
297
- // Clean-up DOM
298
- this.miniContainer.remove();
299
- this.mainContainer.remove();
300
- this.mapContainer.remove();
301
- this.psvContainer.remove();
302
- this.popupContainer.remove();
303
- this.container.innerHTML = "";
304
- this.container.classList.remove(...[...this.container.classList].filter(c => c.startsWith("gvs")));
305
- }
306
-
307
- /**
308
- * Creates appropriate HTML elements in container to host map + viewer
309
- *
310
- * @private
311
- */
312
- _initContainerStructure() {
313
- // Create mini-component container
314
- this.miniContainer = document.createElement("div");
315
- this.miniContainer.classList.add("gvs-mini");
316
-
317
- // Create main-component container
318
- this.mainContainer = document.createElement("div");
319
- this.mainContainer.classList.add("gvs-main");
320
-
321
- // Crate a popup container
322
- this.popupContainer = document.createElement("div");
323
- this.popupContainer.classList.add("gvs-popup", "gvs-hidden");
324
-
325
- // Create PSV container
326
- this.psvContainer = document.createElement("div");
327
- this.mainContainer.appendChild(this.psvContainer);
328
-
329
- // Create map container
330
- this.mapContainer = document.createElement("div");
331
- this.miniContainer.appendChild(this.mapContainer);
332
-
333
- // Add in root container
334
- this.container.appendChild(this.mainContainer);
335
- this.container.appendChild(this.miniContainer);
336
- this.container.appendChild(this.popupContainer);
337
- }
338
-
339
- /**
340
- * Inits MapLibre GL component
341
- *
342
- * @private
343
- * @returns {Promise} Resolves when map is ready
344
- */
345
- async _initMap() {
346
- await new Promise(resolve => {
347
- this.map = new Map(this, this.mapContainer, this._options.map);
348
- this.map._getLayerColorStyle = this._getLayerColorStyle.bind(this);
349
- this.map._getLayerSortStyle = this._getLayerSortStyle.bind(this);
350
- this.addEventListener("map:users-changed", resolve, { once: true });
351
- this.container.classList.add("gvs-has-mini");
352
-
353
- // Map double-click: unselect if focused, toggle focus if unfocused
354
- this.map.on("dblclick", () => {
355
- if(!this.isMapWide()) { this.setFocus("map"); }
356
- else { this.select(); }
357
- });
358
-
359
- if (typeof this._options.map === "object" && this._options.map.startWide) {
360
- this.setFocus("map", true);
361
- }
362
- else {
363
- this.setFocus("pic", true);
364
- }
365
- });
366
-
367
- this._initMapKeyboardHandler();
368
- linkMapAndPhoto(this);
369
- }
370
-
371
- /**
372
- * Adds events related to keyboard
373
- * @private
374
- */
375
- _initMapKeyboardHandler() {
376
- const that = this;
377
- this.map.keyboard.keydown = function(e) {
378
- if (e.altKey || e.ctrlKey || e.metaKey) return;
379
-
380
- // Custom keys
381
- switch(e.key) {
382
- case "*":
383
- case "5":
384
- that.moveCenter();
385
- return;
386
-
387
- case "PageUp":
388
- case "9":
389
- that.psv.goToNextPicture();
390
- return;
391
-
392
- case "PageDown":
393
- case "3":
394
- that.psv.goToPrevPicture();
395
- return;
396
-
397
- case "Home":
398
- case "7":
399
- e.stopPropagation();
400
- that.toggleFocus();
401
- return;
402
-
403
- case "End":
404
- case "1":
405
- that.toggleUnfocusedVisible();
406
- return;
407
-
408
- case " ":
409
- case "0":
410
- that.toggleSequencePlaying();
411
- return;
412
- }
413
-
414
- let zoomDir = 0;
415
- let bearingDir = 0;
416
- let pitchDir = 0;
417
- let xDir = 0;
418
- let yDir = 0;
419
-
420
- switch (e.keyCode) {
421
- case 61:
422
- case 107:
423
- case 171:
424
- case 187:
425
- zoomDir = 1;
426
- break;
427
-
428
- case 189:
429
- case 109:
430
- case 173:
431
- zoomDir = -1;
432
- break;
433
-
434
- case 37:
435
- case 100:
436
- if (e.shiftKey) {
437
- bearingDir = -1;
438
- } else {
439
- e.preventDefault();
440
- xDir = -1;
441
- }
442
- break;
443
-
444
- case 39:
445
- case 102:
446
- if (e.shiftKey) {
447
- bearingDir = 1;
448
- } else {
449
- e.preventDefault();
450
- xDir = 1;
451
- }
452
- break;
453
-
454
- case 38:
455
- case 104:
456
- if (e.shiftKey) {
457
- pitchDir = 1;
458
- } else {
459
- e.preventDefault();
460
- yDir = -1;
461
- }
462
- break;
463
-
464
- case 40:
465
- case 98:
466
- if (e.shiftKey) {
467
- pitchDir = -1;
468
- } else {
469
- e.preventDefault();
470
- yDir = 1;
471
- }
472
- break;
473
-
474
- default:
475
- return;
476
- }
477
-
478
- if (this._rotationDisabled) {
479
- bearingDir = 0;
480
- pitchDir = 0;
481
- }
482
-
483
- return {
484
- cameraAnimation: (map) => {
485
- const tr = this._tr;
486
- map.easeTo({
487
- duration: 300,
488
- easeId: "keyboardHandler",
489
- easing: t => t * (2-t),
490
- zoom: zoomDir ? Math.round(tr.zoom) + zoomDir * (e.shiftKey ? 2 : 1) : tr.zoom,
491
- bearing: tr.bearing + bearingDir * this._bearingStep,
492
- pitch: tr.pitch + pitchDir * this._pitchStep,
493
- offset: [-xDir * this._panStep, -yDir * this._panStep],
494
- center: tr.center
495
- }, {originalEvent: e});
496
- }
497
- };
498
- }.bind(this.map.keyboard);
499
- }
500
-
501
- /**
502
- * Given context, should tiles be loaded in PSV.
503
- * @private
504
- */
505
- _psvShouldGoFast() {
506
- return (this._sequencePlaying && this.psv.getTransitionDuration() < 1000)
507
- || (this.map && this.isMapWide());
508
- }
509
-
510
- /**
511
- * Force reload of texture and tiles in Photo Sphere Viewer.
512
- */
513
- refreshPSV() {
514
- const cn = this.psv._myVTour.getCurrentNode();
515
-
516
- // Refresh mode for flat pictures
517
- if(cn && cn.panorama.baseUrl !== cn?.panorama?.origBaseUrl) {
518
- const prevZoom = this.psv.getZoomLevel();
519
- const prevPos = this.psv.getPosition();
520
- this.psv._myVTour.state.currentNode = null;
521
- this.psv._myVTour.setCurrentNode(cn.id, {
522
- zoomTo: prevZoom,
523
- rotateTo: prevPos,
524
- fadeIn: false,
525
- speed: 0,
526
- rotation: false,
527
- });
528
- }
529
-
530
- // Refresh mode for 360 pictures
531
- if(cn && cn.panorama.rows > 1) {
532
- this.psv.adapter.__refresh();
533
- }
534
- }
535
-
536
- /**
537
- * Change full-page popup visibility and content
538
- * @param {boolean} visible True to make it appear
539
- * @param {string|Element[]} [content] The new popup content
540
- */
541
- setPopup(visible, content = null) {
542
- if(!visible) {
543
- this.popupContainer.classList.add("gvs-hidden");
544
- this.psv.startKeyboardControl();
545
- }
546
- else if(content) {
547
- this.psv.stopKeyboardControl();
548
- this.popupContainer.innerHTML = "";
549
- const backdrop = document.createElement("div");
550
- backdrop.classList.add("gvs-popup-backdrop");
551
- backdrop.addEventListener("click", () => this.setPopup(false));
552
- const innerDiv = document.createElement("div");
553
- innerDiv.classList.add("gvs-widget-bg");
554
-
555
- if(typeof content === "string") { innerDiv.innerHTML = content; }
556
- else if(Array.isArray(content)) { content.forEach(c => innerDiv.appendChild(c)); }
557
-
558
- // Add close button
559
- const btnClose = document.createElement("button");
560
- btnClose.id = "gvs-popup-btn-close";
561
- btnClose.classList.add("gvs-btn", "gvs-widget-bg");
562
- btnClose.appendChild(fa(faXmark));
563
- btnClose.addEventListener("click", () => this.setPopup(false));
564
- innerDiv.insertBefore(btnClose, innerDiv.firstChild);
565
-
566
- this.popupContainer.appendChild(backdrop);
567
- this.popupContainer.appendChild(innerDiv);
568
- this.popupContainer.classList.remove("gvs-hidden");
569
- enableCopyButton(this.popupContainer, this._t);
570
- }
571
- else {
572
- this.popupContainer.classList.remove("gvs-hidden");
573
- }
574
- }
575
-
576
- /**
577
- * Goes continuously to next picture in sequence as long as possible
578
- */
579
- playSequence() {
580
- this._sequencePlaying = true;
581
-
582
- /**
583
- * Event for sequence starting to play
584
- *
585
- * @event sequence-playing
586
- * @memberof Viewer
587
- */
588
- const event = new Event("sequence-playing");
589
- this.dispatchEvent(event);
590
-
591
- const nextPicturePlay = () => {
592
- if(this._sequencePlaying) {
593
- this.addEventListener("psv:picture-loaded", () => {
594
- this._playTimer = setTimeout(() => {
595
- nextPicturePlay();
596
- }, this.psv.getTransitionDuration());
597
- }, { once: true });
598
-
599
- try {
600
- this.psv.goToNextPicture();
601
- }
602
- catch(e) {
603
- this.stopSequence();
604
- }
605
- }
606
- };
607
-
608
- // Stop playing if user clicks on image
609
- this.psv.addEventListener("click", () => {
610
- this.stopSequence();
611
- });
612
-
613
- nextPicturePlay();
614
- }
615
-
616
- /**
617
- * Stops playing current sequence
618
- */
619
- stopSequence() {
620
- this._sequencePlaying = false;
621
-
622
- // Next picture timer is pending
623
- if(this._playTimer) {
624
- clearTimeout(this._playTimer);
625
- delete this._playTimer;
626
- }
627
-
628
- // Force refresh of PSV to eventually load tiles
629
- this.refreshPSV();
630
-
631
- /**
632
- * Event for sequence stopped playing
633
- *
634
- * @event sequence-stopped
635
- * @memberof Viewer
636
- */
637
- const event = new Event("sequence-stopped");
638
- this.dispatchEvent(event);
639
- }
640
-
641
- /**
642
- * Is there any sequence being played right now ?
643
- *
644
- * @returns {boolean} True if sequence is playing
645
- */
646
- isSequencePlaying() {
647
- return this._sequencePlaying;
648
- }
649
-
650
- /**
651
- * Starts/stops the reading of pictures in a sequence
652
- */
653
- toggleSequencePlaying() {
654
- if(this.isSequencePlaying()) {
655
- this.stopSequence();
656
- }
657
- else {
658
- this.playSequence();
659
- }
660
- }
661
-
662
- /**
663
- * Move the view of main component to its center.
664
- * For map, center view on selected picture.
665
- * For picture, center view on image center.
666
- */
667
- moveCenter() {
668
- const meta = this.psv.getPictureMetadata();
669
- if(!meta) { return; }
670
-
671
- if(this.map && this.isMapWide()) {
672
- this.map.flyTo({ center: meta.gps, zoom: 20 });
673
- }
674
- else {
675
- this._psvAnimate({
676
- speed: PSV_ANIM_DURATION,
677
- yaw: 0,
678
- pitch: 0,
679
- zoom: PSV_DEFAULT_ZOOM
680
- });
681
- }
682
- }
683
-
684
- /**
685
- * Moves the view of main component slightly to the left.
686
- */
687
- moveLeft() {
688
- this._moveToDirection("left");
689
- }
690
-
691
- /**
692
- * Moves the view of main component slightly to the right.
693
- */
694
- moveRight() {
695
- this._moveToDirection("right");
696
- }
697
-
698
- /**
699
- * Moves the view of main component slightly to the top.
700
- */
701
- moveUp() {
702
- this._moveToDirection("up");
703
- }
704
-
705
- /**
706
- * Moves the view of main component slightly to the bottom.
707
- */
708
- moveDown() {
709
- this._moveToDirection("down");
710
- }
711
-
712
- /**
713
- * Moves map or picture viewer to given direction.
714
- * @param {string} dir Direction to move to (up, left, down, right)
715
- * @private
716
- */
717
- _moveToDirection(dir) {
718
- if(this.map && this.isMapWide()) {
719
- let pan;
720
- switch(dir) {
721
- case "up":
722
- pan = [0, -MAP_MOVE_DELTA];
723
- break;
724
- case "left":
725
- pan = [-MAP_MOVE_DELTA, 0];
726
- break;
727
- case "down":
728
- pan = [0, MAP_MOVE_DELTA];
729
- break;
730
- case "right":
731
- pan = [MAP_MOVE_DELTA, 0];
732
- break;
733
- }
734
- this.map.panBy(pan);
735
- }
736
- else {
737
- let pos = this.psv.getPosition();
738
- switch(dir) {
739
- case "up":
740
- pos.pitch += PSV_MOVE_DELTA;
741
- break;
742
- case "left":
743
- pos.yaw -= PSV_MOVE_DELTA;
744
- break;
745
- case "down":
746
- pos.pitch -= PSV_MOVE_DELTA;
747
- break;
748
- case "right":
749
- pos.yaw += PSV_MOVE_DELTA;
750
- break;
751
- }
752
- this._psvAnimate({ speed: PSV_ANIM_DURATION, ...pos });
753
- }
754
- }
755
-
756
- /**
757
- * Overrided PSV animate function to ensure a single animation plays at once.
758
- * @param {object} options PSV animate options
759
- * @private
760
- */
761
- _psvAnimate(options) {
762
- if(this._lastPsvAnim) { this._lastPsvAnim.cancel(); }
763
- this._lastPsvAnim = this.psv.animate(options);
764
- }
765
-
766
- /**
767
- * Is the map shown as main element instead of viewer (wide map mode) ?
768
- *
769
- * @returns {boolean} True if map is wider than viewer
770
- */
771
- isMapWide() {
772
- if(!this.map) { throw new Error("Map is not enabled"); }
773
- return this.mapContainer.parentNode == this.mainContainer;
774
- }
775
-
776
- /**
777
- * Computes dates to use for map theme by picture/sequence age
778
- * @private
779
- */
780
- _getDatesForLayerColors() {
781
- const oneDay = 24 * 60 * 60 * 1000;
782
- const d0 = Date.now();
783
- const d1 = d0 - 30 * oneDay;
784
- const d2 = d0 - 365 * oneDay;
785
- const d3 = d0 - 2 * 365 * oneDay;
786
- return [d1, d2, d3].map(d => new Date(d).toISOString().split("T")[0]);
787
- }
788
-
789
- /**
790
- * Retrieve map layer color scheme according to selected theme.
791
- * @private
792
- */
793
- _getLayerColorStyle(layer) {
794
- // Hidden style
795
- const s = ["case",
796
- ["==", ["get", "hidden"], true], COLORS.HIDDEN
797
- ];
798
-
799
- // Selected sequence style
800
- const picId = this.psv._myVTour?.state?.loadingNode || this.psv._myVTour?.state?.currentNode?.id;
801
- const seqId = picId ? this.psv._picturesSequences[picId] : null;
802
- if(layer == "sequences" && seqId) {
803
- s.push(["==", ["get", "id"], seqId], COLORS.SELECTED);
804
- }
805
- else if(layer == "pictures" && seqId) {
806
- s.push(["in", seqId, ["get", "sequences"]], COLORS.SELECTED);
807
- }
808
-
809
- // Themes styles
810
- if(this._mapTheme == MAP_THEMES.AGE) {
811
- const prop = layer == "sequences" ? "date" : "ts";
812
- const dt = this._getDatesForLayerColors();
813
-
814
- s.push(
815
- ["!", ["has", prop]], COLORS.BASE,
816
- [">=", ["get", prop], dt[0]], COLORS.PALETTE_4,
817
- [">=", ["get", prop], dt[1]], COLORS.PALETTE_3,
818
- [">=", ["get", prop], dt[2]], COLORS.PALETTE_2,
819
- COLORS.PALETTE_1
820
- );
821
- }
822
- else if(this._mapTheme == MAP_THEMES.TYPE) {
823
- s.push(
824
- ["!", ["has", "type"]], COLORS.BASE,
825
- ["==", ["get", "type"], "equirectangular"], COLORS.QUALI_1,
826
- COLORS.QUALI_2
827
- );
828
- }
829
- else if(this._mapTheme == MAP_THEMES.SCORE) {
830
- s.push(
831
- ["==", MAP_EXPR_QUALITYSCORE, 5], QUALITYSCORE_VALUES[0].color,
832
- ["==", MAP_EXPR_QUALITYSCORE, 4], QUALITYSCORE_VALUES[1].color,
833
- ["==", MAP_EXPR_QUALITYSCORE, 3], QUALITYSCORE_VALUES[2].color,
834
- ["==", MAP_EXPR_QUALITYSCORE, 2], QUALITYSCORE_VALUES[3].color,
835
- QUALITYSCORE_VALUES[4].color,
836
- );
837
- }
838
- else {
839
- s.push(COLORS.BASE);
840
- }
841
-
842
- return s;
843
- }
844
-
845
- /**
846
- * Retrieve map sort key according to selected theme.
847
- * @private
848
- */
849
- _getLayerSortStyle(layer) {
850
- // Values
851
- // - 100 : on top / selected feature
852
- // - 90 : hidden feature
853
- // - 20-80 : custom ranges
854
- // - 10 : basic feature
855
- // - 0 : on bottom / feature with undefined property
856
-
857
- // Hidden style
858
- const s = ["case",
859
- ["==", ["get", "hidden"], true], 90
860
- ];
861
-
862
- // Selected sequence style
863
- const picId = this.psv._myVTour?.state?.loadingNode || this.psv._myVTour?.state?.currentNode?.id;
864
- const seqId = picId ? this.psv._picturesSequences[picId] : null;
865
- if(layer == "sequences" && seqId) {
866
- s.push(["==", ["get", "id"], seqId], 100);
867
- }
868
- else if(layer == "pictures" && seqId) {
869
- s.push(["in", seqId, ["get", "sequences"]], 100);
870
- }
871
-
872
- // Themes styles
873
- if(this._mapTheme == MAP_THEMES.AGE) {
874
- const prop = layer == "sequences" ? "date" : "ts";
875
- const dt = this._getDatesForLayerColors();
876
- s.push(
877
- ["!", ["has", prop]], 0,
878
- [">=", ["get", prop], dt[0]], 50,
879
- [">=", ["get", prop], dt[1]], 49,
880
- [">=", ["get", prop], dt[2]], 48,
881
- );
882
- }
883
- else if(this._mapTheme == MAP_THEMES.TYPE) {
884
- s.push(
885
- ["!", ["has", "type"]], 0,
886
- ["==", ["get", "type"], "equirectangular"], 50,
887
- );
888
- }
889
- else if(this._mapTheme == MAP_THEMES.SCORE) {
890
- s.push(
891
- ["==", MAP_EXPR_QUALITYSCORE, 5], 80,
892
- ["==", MAP_EXPR_QUALITYSCORE, 4], 65,
893
- ["==", MAP_EXPR_QUALITYSCORE, 3], 50,
894
- ["==", MAP_EXPR_QUALITYSCORE, 2], 35,
895
- ["==", MAP_EXPR_QUALITYSCORE, 1], 20,
896
- );
897
- }
898
-
899
- s.push(10);
900
- return s;
901
- }
902
-
903
- /**
904
- * Get current pictures navigation mode.
905
- * @returns {string} The picture navigation mode ("any": no restriction, "seq": only pictures in same sequence, "pic": only selected picture)
906
- */
907
- getPicturesNavigation() {
908
- return this._picNav;
909
- }
910
-
911
- /**
912
- * Switch the allowed navigation between pictures.
913
- * @param {string} pn The picture navigation mode ("any": no restriction, "seq": only pictures in same sequence, "pic": only selected picture)
914
- */
915
- setPicturesNavigation(pn) {
916
- this._picNav = pn;
917
-
918
- /**
919
- * Event for pictures navigation mode change
920
- *
921
- * @event pictures-navigation-changed
922
- * @memberof Viewer
923
- * @type {object}
924
- * @property {object} detail Event information
925
- * @property {string} detail.value New mode (any, pic, seq)
926
- */
927
- const event = new CustomEvent("pictures-navigation-changed", { detail: { value: pn } });
928
- this.dispatchEvent(event);
929
- }
930
-
931
- /**
932
- * Filter function
933
- * @param {object} link A STAC next/prev/related link definition
934
- * @returns {boolean} True if link should be kept
935
- * @private
936
- */
937
- _picturesNavFilter(link) {
938
- switch(this._picNav) {
939
- case "seq":
940
- return ["next", "prev"].includes(link.rel);
941
- case "pic":
942
- return false;
943
- case "any":
944
- default:
945
- return true;
946
- }
947
- }
948
-
949
- /**
950
- * Enable or disable JOSM live editing using [Remote](https://josm.openstreetmap.de/wiki/Help/RemoteControlCommands)
951
- * @param {boolean} enabled Set to true to enable JOSM live
952
- * @returns {Promise} Resolves on JOSM live being enabled or disabled
953
- */
954
- toggleJOSMLive(enabled) {
955
- if(enabled) {
956
- /**
957
- * Event for JOSM live enabled
958
- *
959
- * @event josm-live-enabled
960
- * @memberof Viewer
961
- */
962
- const event = new CustomEvent("josm-live-enabled");
963
- this.dispatchEvent(event);
964
-
965
- // Check if JOSM remote is enabled
966
- return fetch(JOSM_REMOTE_URL+"/version")
967
- .catch(e => {
968
- this.dispatchEvent(new CustomEvent("josm-live-disabled"));
969
- throw e;
970
- })
971
- .then(() => {
972
- // First loading : download + zoom
973
- const p1 = josmBboxParameters(this.psv.getPictureMetadata());
974
- if(p1) {
975
- const url = `${JOSM_REMOTE_URL}/load_and_zoom?${p1}`;
976
- fetch(url).catch(e => {
977
- console.warn(e);
978
- this.toggleJOSMLive(false);
979
- });
980
- }
981
-
982
- // Enable event listening
983
- this._josmListener = () => {
984
- const p2 = josmBboxParameters(this.psv.getPictureMetadata());
985
- if(p2) {
986
- // Next loadings : just zoom
987
- // This avoids desktop focus to go on JOSM instead of
988
- // staying on web browser
989
- const url = `${JOSM_REMOTE_URL}/zoom?${p2}`;
990
- fetch(url).catch(e => {
991
- console.warn(e);
992
- this.toggleJOSMLive(false);
993
- });
994
- }
995
- };
996
- this.addEventListener("psv:picture-loaded", this._josmListener);
997
- this.addEventListener("psv:picture-loading", this._josmListener);
998
- });
999
- }
1000
- else {
1001
- /**
1002
- * Event for JOSM live disabled
1003
- *
1004
- * @event josm-live-disabled
1005
- * @memberof Viewer
1006
- */
1007
- const event = new CustomEvent("josm-live-disabled");
1008
- this.dispatchEvent(event);
1009
-
1010
- if(this._josmListener) {
1011
- this.removeEventListener("psv:picture-loading", this._josmListener);
1012
- this.removeEventListener("psv:picture-loaded", this._josmListener);
1013
- delete this._josmListener;
1014
- }
1015
- return Promise.resolve();
1016
- }
1017
- }
1018
-
1019
- /**
1020
- * Change the viewer focus (either on picture or map)
1021
- *
1022
- * @param {string} focus The object to focus on (map, pic)
1023
- * @param {boolean} [skipEvent=false] True to not send focus-changed event
1024
- */
1025
- setFocus(focus, skipEvent = false) {
1026
- if(focus === "map" && !this.map) { throw new Error("Map is not enabled"); }
1027
- if(!["map", "pic"].includes(focus)) { throw new Error("Invalid focus value (should be pic or map)"); }
1028
- if(
1029
- (focus === "map" && this.map && this.isMapWide())
1030
- || (focus === "pic" && (!this.map || !this.isMapWide()))
1031
- ) { return; }
1032
-
1033
- this.mapContainer.parentElement?.removeChild(this.mapContainer);
1034
- this.psvContainer.parentElement?.removeChild(this.psvContainer);
1035
-
1036
- if(focus === "map") {
1037
- this.psv.stopKeyboardControl();
1038
- this.map.keyboard.enable();
1039
- this.container.classList.add("gvs-focus-map");
1040
- this.mainContainer.appendChild(this.mapContainer);
1041
- this.miniContainer.appendChild(this.psvContainer);
1042
- this.map.getCanvas().focus();
1043
- }
1044
- else {
1045
- this?.map?.keyboard.disable();
1046
- this.psv.startKeyboardControl();
1047
- this.container.classList.remove("gvs-focus-map");
1048
- this.mainContainer.appendChild(this.psvContainer);
1049
- this.miniContainer.appendChild(this.mapContainer);
1050
- this.psvContainer.focus();
1051
- }
1052
-
1053
- this?.map?.resize();
1054
- this.psv.autoSize();
1055
- this.refreshPSV();
1056
-
1057
- if(!skipEvent) {
1058
- /**
1059
- * Event for focus change (either map or picture is shown wide)
1060
- *
1061
- * @event focus-changed
1062
- * @memberof Viewer
1063
- * @type {object}
1064
- * @property {object} detail Event information
1065
- * @property {string} detail.focus Component now focused on (map, pic)
1066
- */
1067
- const event = new CustomEvent("focus-changed", { detail: { focus } });
1068
- this.dispatchEvent(event);
1069
- }
1070
- }
1071
-
1072
- /**
1073
- * Toggle the viewer focus (either on picture or map)
1074
- */
1075
- toggleFocus() {
1076
- if(!this.map) { throw new Error("Map is not enabled"); }
1077
- this.setFocus(this.isMapWide() ? "pic" : "map");
1078
- }
1079
-
1080
- /**
1081
- * Change the visibility of reduced component (picture or map)
1082
- *
1083
- * @param {boolean} visible True to make reduced component visible
1084
- */
1085
- setUnfocusedVisible(visible) {
1086
- if(!this.map) { throw new Error("Map is not enabled"); }
1087
-
1088
- if(visible) {
1089
- this.container.classList.remove("gvs-mini-hidden");
1090
- }
1091
- else {
1092
- this.container.classList.add("gvs-mini-hidden");
1093
- }
1094
-
1095
- this.map.resize();
1096
- this.psv.autoSize();
1097
- }
1098
-
1099
- /**
1100
- * Toggle the visibility of reduced component (picture or map)
1101
- */
1102
- toggleUnfocusedVisible() {
1103
- if(!this.map) { throw new Error("Map is not enabled"); }
1104
- this.setUnfocusedVisible(this.container.classList.contains("gvs-mini-hidden"));
1105
- }
1106
-
1107
- /**
1108
- * Change the map filters
1109
- * @param {object} filters Filtering values
1110
- * @param {string} [filters.minDate] Start date for pictures (format YYYY-MM-DD)
1111
- * @param {string} [filters.maxDate] End date for pictures (format YYYY-MM-DD)
1112
- * @param {string} [filters.type] Type of picture to keep (flat, equirectangular)
1113
- * @param {string} [filters.camera] Camera make and model to keep
1114
- * @param {string} [filters.theme] Map theme to use
1115
- * @param {number[]} [filters.qualityscore] QualityScore values, as a list of 1 to 5 grades
1116
- * @param {boolean} [skipZoomIn=false] If true, doesn't force zoom in to map level >= 7
1117
- */
1118
- setFilters(filters, skipZoomIn = false) {
1119
- let mapSeqFilters = [];
1120
- let mapPicFilters = [];
1121
- let reloadMapStyle = false;
1122
- this._mapFilters = {};
1123
-
1124
- if(filters.minDate && filters.minDate !== "") {
1125
- this._mapFilters.minDate = filters.minDate;
1126
- mapSeqFilters.push([">=", ["get", "date"], filters.minDate]);
1127
- mapPicFilters.push([">=", ["get", "ts"], filters.minDate]);
1128
- }
1129
-
1130
- if(filters.maxDate && filters.maxDate !== "") {
1131
- this._mapFilters.maxDate = filters.maxDate;
1132
- mapSeqFilters.push(["<=", ["get", "date"], filters.maxDate]);
1133
-
1134
- // Get tomorrow date for pictures filtering
1135
- // (because ts is date+time, so comparing date only string would fail otherwise)
1136
- let d = new Date(filters.maxDate);
1137
- d.setDate(d.getDate() + 1);
1138
- d = d.toISOString().split("T")[0];
1139
- mapPicFilters.push(["<=", ["get", "ts"], d]);
1140
- }
1141
-
1142
- if(filters.type && filters.type !== "") {
1143
- this._mapFilters.type = filters.type;
1144
- mapSeqFilters.push(["==", ["get", "type"], filters.type]);
1145
- mapPicFilters.push(["==", ["get", "type"], filters.type]);
1146
- }
1147
- if(this.map?._hasGridStats()) {
1148
- reloadMapStyle = true;
1149
- }
1150
-
1151
- if(filters.camera && filters.camera !== "") {
1152
- this._mapFilters.camera = filters.camera;
1153
- // low/high model hack : to enable fuzzy filtering of camera make and model
1154
- const lowModel = filters.camera.toLowerCase().trim() + " ";
1155
- const highModel = filters.camera.toLowerCase().trim() + "zzzzzzzzzzzzzzzzzzzz";
1156
- const collator = ["collator", { "case-sensitive": false, "diacritic-sensitive": false } ];
1157
- mapSeqFilters.push([">=", ["get", "model"], lowModel, collator]);
1158
- mapSeqFilters.push(["<=", ["get", "model"], highModel, collator]);
1159
- mapPicFilters.push([">=", ["get", "model"], lowModel, collator]);
1160
- mapPicFilters.push(["<=", ["get", "model"], highModel, collator]);
1161
- }
1162
-
1163
- if(filters.qualityscore && filters.qualityscore.length > 0) {
1164
- this._mapFilters.qualityscore = filters.qualityscore;
1165
- mapSeqFilters.push(["in", MAP_EXPR_QUALITYSCORE, ["literal", this._mapFilters.qualityscore]]);
1166
- mapPicFilters.push(["in", MAP_EXPR_QUALITYSCORE, ["literal", this._mapFilters.qualityscore]]);
1167
- }
1168
-
1169
- if(filters.theme && Object.values(MAP_THEMES).includes(filters.theme)) {
1170
- this._mapFilters.theme = filters.theme;
1171
- if(this.map) {
1172
- this._mapTheme = this._mapFilters.theme;
1173
- reloadMapStyle = true;
1174
- }
1175
- }
1176
-
1177
- if(mapSeqFilters.length == 0) { mapSeqFilters = null; }
1178
- else {
1179
- mapSeqFilters.unshift("all");
1180
- }
1181
-
1182
- if(mapPicFilters.length == 0) { mapPicFilters = null; }
1183
- else {
1184
- mapPicFilters.unshift("all");
1185
- mapPicFilters = ["step", ["zoom"],
1186
- true,
1187
- TILES_PICTURES_ZOOM, mapPicFilters
1188
- ];
1189
- }
1190
-
1191
- if(this.map) {
1192
- if(reloadMapStyle) {
1193
- this.map.reloadLayersStyles();
1194
- }
1195
-
1196
- const allUsers = this.map.getVisibleUsers().includes("geovisio");
1197
- if(mapSeqFilters && allUsers) {
1198
- mapSeqFilters = ["step", ["zoom"],
1199
- true,
1200
- 7, mapSeqFilters
1201
- ];
1202
- }
1203
-
1204
- this.map.filterUserLayersContent("sequences", mapSeqFilters);
1205
- this.map.filterUserLayersContent("pictures", mapPicFilters);
1206
- if(
1207
- !skipZoomIn
1208
- && (
1209
- mapSeqFilters !== null
1210
- || mapPicFilters !== null
1211
- || (this._mapFilters.theme !== null && this._mapFilters.theme !== MAP_THEMES.DEFAULT)
1212
- )
1213
- && allUsers
1214
- && this.map.getZoom() < 7
1215
- && !this.map._hasGridStats()
1216
- ) {
1217
- this.map.easeTo({ zoom: 7 });
1218
- }
1219
- }
1220
-
1221
- /**
1222
- * Event for filters changes
1223
- *
1224
- * @event filters-changed
1225
- * @memberof Viewer
1226
- * @type {object}
1227
- * @property {object} detail Event information
1228
- * @property {string} [detail.minDate] The minimum date in time range (ISO format)
1229
- * @property {string} [detail.maxDate] The maximum date in time range (ISO format)
1230
- * @property {string} [detail.type] Camera type (equirectangular, flat, null/empty string for both)
1231
- * @property {string} [detail.camera] Camera make and model
1232
- * @property {string} [detail.theme] Map theme
1233
- * @property {number[]} [detail.qualityscore] QualityScore values, as a list of 1 to 5 grades
1234
- */
1235
- const event = new CustomEvent("filters-changed", { detail: Object.assign({}, this._mapFilters) });
1236
- this.dispatchEvent(event);
1237
- }
1238
- }
1239
-
1240
- export {
1241
- Viewer as default, // eslint-disable-line import/no-unused-modules
1242
- Viewer, // eslint-disable-line import/no-unused-modules
1243
- PSV_ZOOM_DELTA,
1244
- PSV_ANIM_DURATION,
1245
- PIC_MAX_STAY_DURATION,
1246
- };