@panoramax/web-viewer 3.2.3-develop-f219e404 → 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 +33 -3
  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
@@ -0,0 +1,188 @@
1
+ import { LitElement, html, nothing } from "lit";
2
+ import { fa } from "../../utils/widgets";
3
+ import { faCircleInfo } from "@fortawesome/free-solid-svg-icons/faCircleInfo";
4
+ import { faTriangleExclamation } from "@fortawesome/free-solid-svg-icons/faTriangleExclamation";
5
+ import { faLocationDot } from "@fortawesome/free-solid-svg-icons/faLocationDot";
6
+ import { faMedal } from "@fortawesome/free-solid-svg-icons/faMedal";
7
+ import { faInfoCircle } from "@fortawesome/free-solid-svg-icons/faInfoCircle";
8
+ import { faCamera } from "@fortawesome/free-solid-svg-icons/faCamera";
9
+ import { faGear } from "@fortawesome/free-solid-svg-icons/faGear";
10
+ import { titles, tables, expandable } from "../styles";
11
+ import { createTable, createLinkCell, createWebComp } from "../../utils/widgets";
12
+ import { getGPSPrecision } from "../../utils/picture";
13
+ import {
14
+ getGrade, QUALITYSCORE_GPS_VALUES, QUALITYSCORE_RES_360_VALUES,
15
+ QUALITYSCORE_RES_FLAT_VALUES, QUALITYSCORE_POND_GPS, QUALITYSCORE_POND_RES
16
+ } from "../../utils/utils";
17
+
18
+ /**
19
+ * Picture metadata displays detailed info about a single picture (ID, capture context, EXIF attributes...).
20
+ * @class Panoramax.components.menus.PictureMetadata
21
+ * @element pnx-picture-metadata
22
+ * @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
23
+ * @example
24
+ * ```html
25
+ * <pnx-picture-metadata ._parent=${viewer} />
26
+ * ```
27
+ */
28
+ export default class PictureMetadata extends LitElement {
29
+ /** @private */
30
+ static styles = [ titles, tables, expandable ];
31
+
32
+ /** @private */
33
+ static properties = {
34
+ _meta: {state: true},
35
+ };
36
+
37
+ /** @private */
38
+ connectedCallback() {
39
+ super.connectedCallback();
40
+
41
+ this._meta = this._parent?.psv?.getPictureMetadata();
42
+ this._parent?.psv?.addEventListener("picture-loaded", () => {
43
+ this._meta = this._parent?.psv?.getPictureMetadata();
44
+ });
45
+ }
46
+
47
+ /** @private */
48
+ render() {
49
+ /* eslint-disable indent */
50
+ if(!this._meta) { return nothing; }
51
+
52
+ // General metadata
53
+ const generalData = [
54
+ {
55
+ section: this._parent?._t.pnx.metadata_general_picid,
56
+ classes: ["pnx-td-with-id"],
57
+ values: createLinkCell(
58
+ this._meta.id,
59
+ this._parent.api.getPictureMetadataUrl(this._meta.id, this._meta?.sequence?.id),
60
+ this._parent?._t.pnx.metadata_general_picid_link,
61
+ this._parent?._t
62
+ )
63
+ },
64
+ {
65
+ section: this._parent?._t.pnx.metadata_general_seqid,
66
+ classes: ["pnx-td-with-id"],
67
+ values: createLinkCell(
68
+ this._meta?.sequence?.id,
69
+ this._parent.api.getSequenceMetadataUrl(this._meta?.sequence?.id),
70
+ this._parent?._t.pnx.metadata_general_seqid_link,
71
+ this._parent?._t
72
+ )
73
+ },
74
+ { section: this._parent?._t.pnx.metadata_general_author, value: this._meta?.caption?.producer },
75
+ { section: this._parent?._t.pnx.metadata_general_license, value: this._meta?.caption?.license },
76
+ {
77
+ section: this._parent?._t.pnx.metadata_general_date,
78
+ value: this._meta?.caption?.date?.toLocaleDateString(undefined, {
79
+ year: "numeric", month: "long", day: "numeric",
80
+ hour: "numeric", minute: "numeric", second: "numeric",
81
+ fractionalSecondDigits: 3, timeZoneName: "short"
82
+ })
83
+ },
84
+ ];
85
+
86
+ // Camera details
87
+ const focal = this._meta?.properties?.["pers:interior_orientation"]?.focal_length ? `${this._meta?.properties?.["pers:interior_orientation"]?.focal_length} mm` : "❓";
88
+ let resmp = this._meta?.properties?.["pers:interior_orientation"]?.["sensor_array_dimensions"];
89
+ if(resmp) { resmp = `${resmp[0]} x ${resmp[1]} px (${Math.floor(resmp[0] * resmp[1] / 1000000)} Mpx)`;}
90
+ let pictype = this._parent?._t.pnx.picture_flat;
91
+ let picFov = this._meta?.properties?.["pers:interior_orientation"]?.["field_of_view"]; // Use raw value instead of horizontalFov to avoid default showing up
92
+ if(picFov !== null && picFov !== undefined) {
93
+ if(picFov === 360) { pictype = this._parent?._t.pnx.picture_360; }
94
+ else { pictype += ` (${picFov}°)`; }
95
+ }
96
+
97
+ const cameraData = [
98
+ { section: this._parent?._t.pnx.metadata_camera_make, value: this._meta?.properties?.["pers:interior_orientation"]?.camera_manufacturer || "❓" },
99
+ { section: this._parent?._t.pnx.metadata_camera_model, value: this._meta?.properties?.["pers:interior_orientation"]?.camera_model || "❓" },
100
+ { section: this._parent?._t.pnx.metadata_camera_type, value: pictype },
101
+ { section: this._parent?._t.pnx.metadata_camera_resolution, value: resmp || "❓" },
102
+ { section: this._parent?._t.pnx.metadata_camera_focal_length, value: focal },
103
+ ];
104
+
105
+ // Location details
106
+ const orientation = this._meta?.properties?.["view:azimuth"] !== undefined ? `${this._meta.properties["view:azimuth"]}°` : "❓";
107
+ const gpsPrecisionLabel = getGPSPrecision(this._meta);
108
+ const locationData = [
109
+ { section: this._parent?._t.pnx.metadata_location_longitude, value: this._meta.gps[0] },
110
+ { section: this._parent?._t.pnx.metadata_location_latitude, value: this._meta.gps[1] },
111
+ { section: this._parent?._t.pnx.metadata_location_orientation, value: orientation },
112
+ { section: this._parent?._t.pnx.metadata_location_precision, value: gpsPrecisionLabel },
113
+ ];
114
+
115
+ // Picture quality level
116
+ const hasQualityScore = this._parent?.map?._hasQualityScore?.();
117
+ let qualityData;
118
+ if(hasQualityScore) {
119
+ const gpsGrade = getGrade(QUALITYSCORE_GPS_VALUES, this._meta?.properties?.["quality:horizontal_accuracy"]);
120
+ const resGrade = getGrade(
121
+ this._meta?.horizontalFov === 360 ? QUALITYSCORE_RES_360_VALUES : QUALITYSCORE_RES_FLAT_VALUES,
122
+ this._meta?.properties?.["panoramax:horizontal_pixel_density"]
123
+ );
124
+
125
+ // Note: score is also calculated in utils/map code
126
+ const generalGrade = Math.round((resGrade || 1) * QUALITYSCORE_POND_RES + (gpsGrade || 1) * QUALITYSCORE_POND_GPS);
127
+
128
+ qualityData = [
129
+ { section: this._parent?._t.pnx.metadata_quality_score, value: createWebComp("pnx-quality-score", { grade: generalGrade }) },
130
+ { section: this._parent?._t.pnx.metadata_quality_gps_score, value: createWebComp("pnx-grade", { stars: gpsGrade, _t: this._parent?._t }) },
131
+ { section: this._parent?._t.pnx.metadata_quality_resolution_score, value: createWebComp("pnx-grade", { stars: resGrade, _t: this._parent?._t }) },
132
+ ];
133
+ }
134
+
135
+ return html`
136
+ <h4>${fa(faCircleInfo)} ${this._parent?._t.pnx.metadata}</h4>
137
+
138
+ ${this._parent.api._endpoints.report ?
139
+ html`
140
+ <pnx-button ._t=${this._parent?._t} @click=${() => this._parent._showReportForm()}>
141
+ ${fa(faTriangleExclamation)} ${this._parent?._t.pnx.report}
142
+ </pnx-button>
143
+ ` :
144
+ nothing
145
+ }
146
+
147
+ ${createTable("pnx-table-light", generalData)}
148
+
149
+ <h4>${fa(faCamera)} ${this._parent?._t.pnx.metadata_camera}</h4>
150
+ ${createTable("pnx-table-light", cameraData)}
151
+
152
+ <h4>${fa(faLocationDot)} ${this._parent?._t.pnx.metadata_location}</h4>
153
+ ${createTable("pnx-table-light", locationData)}
154
+
155
+ ${hasQualityScore ?
156
+ html`
157
+ <h4 style="margin-bottom: 5px">
158
+ ${fa(faMedal)} ${this._parent?._t.pnx.metadata_quality}
159
+ <pnx-button
160
+ title="${this._parent?._t.pnx.metadata_quality_help}"
161
+ kind="outline"
162
+ @click=${() => this._parent?._showQualityScoreDoc()}
163
+ >
164
+ ${fa(faInfoCircle)}
165
+ </pnx-button>
166
+ </h4>
167
+ ${createTable("pnx-table-light", qualityData)}
168
+ ` :
169
+ nothing
170
+ }
171
+
172
+ ${this._meta.properties?.exif ?
173
+ html`
174
+ <details>
175
+ <summary>${fa(faGear)} ${this._parent?._t.pnx.metadata_exif}</summary>
176
+ ${createTable("", Object.entries(this._meta.properties.exif)
177
+ .sort()
178
+ .map(([key, value]) => ({ section: key, value: value })
179
+ ))}
180
+ </details>
181
+ ` :
182
+ nothing
183
+ }
184
+ `;
185
+ }
186
+ }
187
+
188
+ customElements.define("pnx-picture-metadata", PictureMetadata);
@@ -0,0 +1,96 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import { faSvg } from "../styles";
3
+ import { faRocket } from "@fortawesome/free-solid-svg-icons/faRocket";
4
+ import { faLightbulb } from "@fortawesome/free-solid-svg-icons/faLightbulb";
5
+ import { faPersonBiking } from "@fortawesome/free-solid-svg-icons/faPersonBiking";
6
+ import { PIC_MAX_STAY_DURATION } from "../ui/Photo";
7
+ import { fa } from "../../utils/widgets";
8
+
9
+ /**
10
+ * Player Options menu displays player speed and contrast settings.
11
+ * @class Panoramax.components.menus.PlayerOptions
12
+ * @element pnx-player-options
13
+ * @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
14
+ * @example
15
+ * ```html
16
+ * <pnx-player-options ._parent=${viewer} />
17
+ * ```
18
+ */
19
+ export default class PlayerOptions extends LitElement {
20
+ /** @private */
21
+ static styles = [ faSvg, css`
22
+ :host {
23
+ display: flex;
24
+ flex-direction: row;
25
+ align-items: center;
26
+ justify-content: center;
27
+ gap: 15px;
28
+ padding: 5px 15px;
29
+ }
30
+
31
+ .pnx-input-range {
32
+ display: flex;
33
+ justify-content: space-between;
34
+ gap: 10px;
35
+ align-items: center;
36
+ width: 100%;
37
+ }
38
+ ` ];
39
+
40
+ /** @private */
41
+ connectedCallback() {
42
+ super.connectedCallback();
43
+ this._parent?.psv?.addEventListener("transition-duration-changed", e => {
44
+ this.renderRoot.querySelector("#pnx-player-speed").value = PIC_MAX_STAY_DURATION - e.detail.value;
45
+ });
46
+ }
47
+
48
+ /** @private */
49
+ _onSpeedChange(e) {
50
+ const newSpeed = PIC_MAX_STAY_DURATION - e.target.value;
51
+ this._parent?.psv?.setTransitionDuration(newSpeed);
52
+ }
53
+
54
+ /** @private */
55
+ _onContrastClick() {
56
+ const btn = this.renderRoot.querySelector("#pnx-player-contrast");
57
+ if(btn.getAttribute("active") == "true") {
58
+ btn.removeAttribute("active");
59
+ this._parent?.psv?.setHigherContrast(false);
60
+ }
61
+ else {
62
+ btn.setAttribute("active", "true");
63
+ this._parent?.psv?.setHigherContrast(true);
64
+ }
65
+ }
66
+
67
+ /** @private */
68
+ render() {
69
+ return html`
70
+ <div class="pnx-input-range" title="${this._parent?._t.pnx.sequence_speed}">
71
+ ${fa(faPersonBiking)}
72
+ <input
73
+ id="pnx-player-speed"
74
+ type="range" name="speed"
75
+ min="0" max="${PIC_MAX_STAY_DURATION - 100}"
76
+ value="${PIC_MAX_STAY_DURATION - this._parent?.psv.getTransitionDuration()}"
77
+ title="${this._parent?._t.pnx.sequence_speed}"
78
+ style="width: 100%;"
79
+ @change=${this._onSpeedChange}
80
+ />
81
+ ${fa(faRocket)}
82
+ </div>
83
+ <pnx-button
84
+ id="pnx-player-contrast"
85
+ title="${this._parent?._t.pnx.contrast}"
86
+ kind="outline"
87
+ style="width: 26px;"
88
+ @click=${this._onContrastClick}
89
+ >
90
+ ${fa(faLightbulb)}
91
+ </pnx-button>
92
+ `;
93
+ }
94
+ }
95
+
96
+ customElements.define("pnx-player-options", PlayerOptions);
@@ -0,0 +1,36 @@
1
+ import { LitElement, html } from "lit";
2
+ import { faSvg, titles } from "../styles";
3
+ import { fa } from "../../utils/widgets";
4
+ import { faMedal } from "@fortawesome/free-solid-svg-icons/faMedal";
5
+
6
+ /**
7
+ * Quality Score Doc displays a synthetic summary of how Quality Score is computed.
8
+ * @class Panoramax.components.menus.QualityScoreDoc
9
+ * @element pnx-quality-score-doc
10
+ * @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
11
+ * @example
12
+ * ```html
13
+ * <pnx-quality-score-doc ._t=${viewer._t} />
14
+ * ```
15
+ */
16
+ export default class QualityScoreDoc extends LitElement {
17
+ static styles = [ titles, faSvg ];
18
+
19
+ render() {
20
+ return html`
21
+ <h4>${fa(faMedal)} ${this._t?.pnx.qualityscore_title}</h4>
22
+ <p>${this._t?.pnx.qualityscore_doc_1}</p>
23
+ <p>${this._t?.pnx.qualityscore_doc_2}</p>
24
+ <pnx-quality-score grade="5"></pnx-quality-score>
25
+ <p>${this._t?.pnx.qualityscore_doc_3}</p>
26
+ <pnx-link-button
27
+ href="https://docs.panoramax.fr/pictures-metadata/quality_score/"
28
+ target="_blank"
29
+ kind="outline"
30
+ style="width: 100%"
31
+ >${this._t?.pnx.qualityscore_doc_link}</pnx-link-button>
32
+ `;
33
+ }
34
+ }
35
+
36
+ customElements.define("pnx-quality-score-doc", QualityScoreDoc);
@@ -0,0 +1,133 @@
1
+ import { LitElement, html, css, nothing } from "lit";
2
+ import { faSvg, titles, select, textarea, input } from "../styles";
3
+ import { fa } from "../../utils/widgets";
4
+ import { faAt } from "@fortawesome/free-solid-svg-icons/faAt";
5
+ import { faTriangleExclamation } from "@fortawesome/free-solid-svg-icons/faTriangleExclamation";
6
+ import { faCircleInfo } from "@fortawesome/free-solid-svg-icons/faCircleInfo";
7
+ import { faCommentDots } from "@fortawesome/free-solid-svg-icons/faCommentDots";
8
+ import { faPaperPlane } from "@fortawesome/free-solid-svg-icons/faPaperPlane";
9
+ import { getUserAccount } from "../../utils/utils";
10
+
11
+ const REPORT_NATURE = [
12
+ "", "blur_missing", "blur_excess", "inappropriate", "privacy",
13
+ "picture_low_quality", "mislocated", "copyright", "other"
14
+ ];
15
+
16
+ /**
17
+ * Report Form displays a form to send issues about pictures.
18
+ * @class Panoramax.components.menus.ReportForm
19
+ * @element pnx-report-form
20
+ * @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
21
+ * @example
22
+ * ```html
23
+ * <pnx-report-form ._parent=${viewer} />
24
+ * ```
25
+ */
26
+ export default class ReportForm extends LitElement {
27
+ /** @private */
28
+ static styles = [ titles, faSvg, select, textarea, input, css`
29
+ .group {
30
+ margin-bottom: 10px;
31
+ }
32
+
33
+ .group:not(.group-inline) label {
34
+ margin-bottom: 5px;
35
+ display: inline-block;
36
+ }
37
+ ` ];
38
+
39
+ /** @private */
40
+ static properties = {
41
+ mode: {state: true},
42
+ failure: {state: true},
43
+ };
44
+
45
+ /** @private */
46
+ _onSubmit(e) {
47
+ e.preventDefault();
48
+
49
+ const picMeta = this._parent?.psv?.getPictureMetadata();
50
+ const data = Object.fromEntries(new FormData(e.target));
51
+ const params = {
52
+ issue: data.issue,
53
+ picture_id: data["whole-sequence"] ? null : picMeta?.id,
54
+ reporter_comments: data.comments,
55
+ reporter_email: data.email,
56
+ sequence_id: picMeta?.sequence?.id
57
+ };
58
+
59
+ this.mode = "wait";
60
+
61
+ // Call API
62
+ this._parent?.api.sendReport(params).then(() => {
63
+ this.mode = "success";
64
+ }).catch(e => {
65
+ console.error(e);
66
+ this.mode = "failure";
67
+ this.failure = e;
68
+ });
69
+ }
70
+
71
+ /** @private */
72
+ render() {
73
+ const userAccount = getUserAccount();
74
+
75
+ if(this.mode) {
76
+ return html`
77
+ <h4>${fa(faTriangleExclamation)} ${this._parent?._t.pnx.report}</h4>
78
+ <p>
79
+ ${this.mode === "wait" ? this._parent?._t.pnx.report_wait : nothing}
80
+ ${this.mode === "success" ? this._parent?._t.pnx.report_success : nothing}
81
+ ${this.mode === "failure" ? this._parent?._t.pnx.report_failure.replace("{e}", this.failure) : nothing}
82
+ </p>
83
+ `;
84
+ }
85
+
86
+ return html`
87
+ <h4>${fa(faTriangleExclamation)} ${this._parent?._t.pnx.report}</h4>
88
+
89
+ ${userAccount && this._parent?._t ? html`
90
+ <p>${this._parent?._t.pnx.report_auth.replace("{a}", userAccount.name)}</p>
91
+ ` : nothing}
92
+
93
+ <form @submit=${this._onSubmit.bind(this)}>
94
+ <div class="group">
95
+ <label for="issue">${fa(faCircleInfo)} ${this._parent?._t.pnx.report_nature_label}</label>
96
+ <select name="issue" id="issue" required class="pnx-100w">
97
+ ${REPORT_NATURE.map(nature => html`
98
+ <option
99
+ value=${nature}
100
+ ${nature === "" ? html`disabled selected hidden` : nothing}
101
+ >
102
+ ${this._parent?._t.pnx.report_nature[nature]}
103
+ </option>
104
+ `)}
105
+ </select>
106
+ </div>
107
+
108
+ <div class="group group-inline">
109
+ <input name="whole-sequence" id="whole-sequence" type="checkbox" />
110
+ <label for="whole-sequence">${this._parent?._t.pnx.report_whole_sequence}</label>
111
+ </div>
112
+
113
+ <div class="group">
114
+ <label for="comments">${fa(faCommentDots)} ${this._parent?._t.pnx.report_details}</label>
115
+ <textarea name="comments" id="comments" placeholder=${this._parent?._t.pnx.report_details_placeholder}></textarea>
116
+ </div>
117
+
118
+ ${!userAccount ? html`
119
+ <div class="group">
120
+ <label for="email">${fa(faAt)} ${this._parent?._t.pnx.report_email}</label>
121
+ <input type="email" name="email" id="email" placeholder=${this._parent?._t.pnx.report_email_placeholder} class="pnx-100w" />
122
+ </div>
123
+ `: nothing}
124
+
125
+ <pnx-button type="submit" style="width: 100%">
126
+ ${fa(faPaperPlane)} ${this._parent?._t.pnx.report_submit}
127
+ </pnx-button>
128
+ </form>
129
+ `;
130
+ }
131
+ }
132
+
133
+ customElements.define("pnx-report-form", ReportForm);
@@ -0,0 +1,228 @@
1
+ import { LitElement, css, nothing } from "lit";
2
+ import { html, unsafeStatic } from "lit/static-html.js";
3
+ import { fa } from "../../utils/widgets";
4
+ import { josmBboxParameters } from "../../utils/utils";
5
+ import { faSvg, textarea, titles } from "../styles";
6
+
7
+ import { faLink } from "@fortawesome/free-solid-svg-icons/faLink";
8
+ import { faCloudArrowDown } from "@fortawesome/free-solid-svg-icons/faCloudArrowDown";
9
+ import { faCopy } from "@fortawesome/free-solid-svg-icons/faCopy";
10
+ import { faPrint } from "@fortawesome/free-solid-svg-icons/faPrint";
11
+ import { faMap } from "@fortawesome/free-solid-svg-icons/faMap";
12
+ import { faCircleInfo } from "@fortawesome/free-solid-svg-icons/faCircleInfo";
13
+ import { faPen } from "@fortawesome/free-solid-svg-icons/faPen";
14
+ import { faLocationDot } from "@fortawesome/free-solid-svg-icons/faLocationDot";
15
+ import { faSatelliteDish } from "@fortawesome/free-solid-svg-icons/faSatelliteDish";
16
+ import { faSquareRss } from "@fortawesome/free-solid-svg-icons/faSquareRss";
17
+
18
+ const JOSM_REMOTE_URL = "http://127.0.0.1:8111";
19
+ const ID_URL = "https://www.openstreetmap.org/edit?editor=id";
20
+
21
+ /**
22
+ * Share Menu displays links for quick picture sharing.
23
+ * @class Panoramax.components.menus.ShareMenu
24
+ * @element pnx-share-menu
25
+ * @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
26
+ * @example
27
+ * ```html
28
+ * <pnx-share-menu ._parent=${viewer} />
29
+ * ```
30
+ */
31
+ export default class ShareMenu extends LitElement {
32
+ /** @private */
33
+ static styles = [ faSvg, textarea, titles, css`
34
+ .pnx-many-buttons {
35
+ display: flex;
36
+ flex-direction: row;
37
+ width: 100%;
38
+ gap: 5px;
39
+ justify-content: space-between;
40
+ align-items: center;
41
+ flex-wrap: wrap;
42
+ white-space: nowrap;
43
+ }
44
+
45
+ .pnx-many-buttons textarea {
46
+ font-size: 0.8em;
47
+ height: 50px;
48
+ }
49
+
50
+ .pnx-many-buttons pnx-button,
51
+ .pnx-many-buttons pnx-copy-button,
52
+ .pnx-many-buttons pnx-link-button {
53
+ flex-basis: 100%;
54
+ flex-grow: 2;
55
+ flex-shrink: 2;
56
+ }
57
+
58
+ .pnx-many-buttons.pnx-edit-buttons pnx-button,
59
+ .pnx-many-buttons.pnx-edit-buttons pnx-link-button {
60
+ flex-basis: 30%;
61
+ flex-grow: 1;
62
+ flex-shrink: 1;
63
+ }
64
+ ` ];
65
+
66
+ /** @private */
67
+ static properties = {
68
+ _pic: {state: true},
69
+ _baseUrl: {state: true},
70
+ _josm: {state: true},
71
+ };
72
+
73
+ constructor() {
74
+ super();
75
+ this._josm = false;
76
+ }
77
+
78
+ /** @private */
79
+ connectedCallback() {
80
+ super.connectedCallback();
81
+ this._onUrlChange();
82
+ this._parent?.addEventListener("ready", this._onUrlChange.bind(this), { once: true });
83
+ this._parent?.urlHandler?.addEventListener("url-changed", this._onUrlChange.bind(this));
84
+ this._parent?.oncePSVReady().then(() => this._parent.psv.addEventListener("picture-loaded", this._onPictureLoad.bind(this)));
85
+ }
86
+
87
+ /** @private */
88
+ _onUrlChange() {
89
+ this._baseUrl = window.location.href.replace(/\/$/, "");
90
+ }
91
+
92
+ /** @private */
93
+ _onPictureLoad() {
94
+ this._pic = this._parent?.psv?.getPictureMetadata();
95
+ }
96
+
97
+ /**
98
+ * Enable or disable JOSM live editing using [Remote](https://josm.openstreetmap.de/wiki/Help/RemoteControlCommands)
99
+ * @private
100
+ */
101
+ _toggleJOSMLive() {
102
+ this._josm = !this._josm;
103
+
104
+ if(this._josm) {
105
+ // Check if JOSM remote is enabled
106
+ fetch(JOSM_REMOTE_URL+"/version")
107
+ .then(() => {
108
+ // First loading : download + zoom
109
+ const p1 = josmBboxParameters(this._parent?.psv?.getPictureMetadata?.());
110
+ if(p1) {
111
+ const url = `${JOSM_REMOTE_URL}/load_and_zoom?${p1}`;
112
+ fetch(url).catch(e => {
113
+ console.warn(e);
114
+ this._toggleJOSMLive();
115
+ });
116
+ }
117
+
118
+ // Enable event listening
119
+ this._josmListener = () => {
120
+ const p2 = josmBboxParameters(this._parent?.psv?.getPictureMetadata?.());
121
+ if(p2) {
122
+ // Next loadings : just zoom
123
+ // This avoids desktop focus to go on JOSM instead of
124
+ // staying on web browser
125
+ const url = `${JOSM_REMOTE_URL}/zoom?${p2}`;
126
+ fetch(url).catch(e => {
127
+ console.warn(e);
128
+ this._toggleJOSMLive();
129
+ });
130
+ }
131
+ };
132
+ this._parent?.psv?.addEventListener?.("picture-loaded", this._josmListener);
133
+ this._parent?.psv?.addEventListener?.("picture-loading", this._josmListener);
134
+ })
135
+ .catch(e => {
136
+ console.warn(e);
137
+ alert(this._parent?._t.pnx.error_josm);
138
+ this._toggleJOSMLive();
139
+ });
140
+ }
141
+ else {
142
+ if(this._josmListener) {
143
+ this._parent?.psv?.removeEventListener?.("picture-loading", this._josmListener);
144
+ this._parent?.psv?.removeEventListener?.("picture-loaded", this._josmListener);
145
+ delete this._josmListener;
146
+ }
147
+ }
148
+ }
149
+
150
+ /** @private */
151
+ render() {
152
+ const shareUrl = this._parent?.urlHandler?.nextShortLink(this._baseUrl) || this._baseUrl;
153
+ const iframe = `<iframe src="${shareUrl}" style="border: none; width: 500px; height: 300px"></iframe>`;
154
+
155
+ const idOpts = this._pic && {
156
+ "map": `19/${this._pic.gps[1]}/${this._pic.gps[0]}`,
157
+ "source": "Panoramax",
158
+ "photo_overlay": "panoramax",
159
+ "photo": `panoramax/${this._pic.id}`,
160
+ };
161
+ const idUrl = idOpts && `${ID_URL}#${new URLSearchParams(idOpts).toString()}`;
162
+
163
+ return html`
164
+ ${this._pic && html`
165
+ <p style="margin: 0 0 10px 0;">
166
+ ${this._pic?.caption?.license ? html`${unsafeStatic(this._parent?._t.pnx.legend_license.replace("{l}", this._pic.caption.license))}` : ""}
167
+ </p>
168
+ `}
169
+
170
+ <h4 style="margin-top: 0">${fa(faLink)} ${this._parent?._t.pnx.share_links}</h4>
171
+ <div class="pnx-many-buttons">
172
+ ${this._pic && html`
173
+ <pnx-link-button
174
+ download
175
+ target="_blank"
176
+ href=${this._pic?.panorama?.hdUrl}
177
+ >${fa(faCloudArrowDown)} ${this._parent?._t.pnx.share_image}</pnx-link-button>
178
+ `}
179
+ ${this._parent?.api?.getRSSURL() && html`
180
+ <pnx-link-button
181
+ target="_blank"
182
+ href=${this._parent?.api.getRSSURL(this._parent?.map?.getBounds?.())}
183
+ title=${this._parent?._t.pnx.share_rss_title}
184
+ >
185
+ ${fa(faSquareRss)} ${this._parent?._t.pnx.share_rss}
186
+ </pnx-link-button>
187
+ `}
188
+ <pnx-copy-button ._t=${this._parent?._t} text=${shareUrl}>
189
+ ${fa(faCopy)} ${this._parent?._t.pnx.share_page}
190
+ </pnx-copy-button>
191
+ <pnx-button @click=${window.print.bind(window)}>
192
+ ${fa(faPrint)} ${this._parent?._t.pnx.share_print}
193
+ </pnx-button>
194
+ </div>
195
+
196
+ <h4>
197
+ ${fa(faMap)} ${this._parent?._t.pnx.share_embed}
198
+ <pnx-link-button
199
+ href="https://docs.panoramax.fr/web-viewer/03_URL_settings/"
200
+ title="${this._parent?._t.pnx.share_embed_docs}"
201
+ target="_blank"
202
+ kind="outline">
203
+ ${fa(faCircleInfo, {styles: {padding: "0 3px"}})}
204
+ </pnx-link-button>
205
+ </h4>
206
+ <div class="pnx-many-buttons">
207
+ <textarea readonly>${iframe}</textarea>
208
+ <pnx-copy-button ._t=${this._parent?._t} text=${iframe}></pnx-copy-button>
209
+ </div>
210
+
211
+ ${this._pic && html`
212
+ <h4>${fa(faPen)} ${this._parent?._t.pnx.edit_osm}</h4>
213
+ <div class="pnx-many-buttons pnx-edit-buttons">
214
+ <pnx-link-button href=${idUrl} target="_blank">
215
+ ${fa(faLocationDot)} ${this._parent?._t.pnx.id}
216
+ </pnx-link-button>
217
+ <pnx-button
218
+ id="pnx-edit-josm"
219
+ active=${this._josm ? "" : nothing}
220
+ @click=${this._toggleJOSMLive}
221
+ title="${this._parent?._t.pnx.josm_live}"
222
+ >${fa(faSatelliteDish)} ${this._parent?._t.pnx.josm}</pnx-button>
223
+ </div>
224
+ `}`;
225
+ }
226
+ }
227
+
228
+ customElements.define("pnx-share-menu", ShareMenu);
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Complex menus components
3
+ * @module Panoramax:components:menus
4
+ */
5
+
6
+ export {default as MapFilters} from "./MapFilters";
7
+ export {default as MapLayers} from "./MapLayers";
8
+ export {default as MapBackground} from "./MapBackground";
9
+ export {default as PlayerOptions} from "./PlayerOptions";
10
+ export {default as QualityScoreDoc} from "./QualityScoreDoc";
11
+ export {default as ReportForm} from "./ReportForm";
12
+ export {default as Share} from "./Share";
13
+ export {default as PictureLegend} from "./PictureLegend";
14
+ export {default as MapLegend} from "./MapLegend";
15
+ export {default as PictureMetadata} from "./PictureMetadata";