@panoramax/web-viewer 3.2.3 → 4.0.0-develop-9f9cf858

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 +56 -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
@@ -0,0 +1,257 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import { classMap } from "lit/directives/class-map.js";
3
+
4
+ const OPENNESS_Y_PCT = { "opened": 0, "half-opened": 0.7, "closed": 1 };
5
+
6
+ /**
7
+ * BottomDrawer layout offers a mobile-like drawer menu, coming from bottom of the screen.
8
+ * @class Panoramax.components.layout.BottomDrawer
9
+ * @element pnx-bottom-drawer
10
+ * @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
11
+ * @slot `default` The drawer content
12
+ * @example
13
+ * ```html
14
+ * <pnx-bottom-drawer openness="opened">
15
+ * <p>My drawer content</p>
16
+ * </pnx-bottom-drawer>
17
+ * ```
18
+ */
19
+ export default class BottomDrawer extends LitElement {
20
+ /** @private */
21
+ static styles = css`
22
+ :host {
23
+ position: fixed;
24
+ bottom: 0;
25
+ left: 0;
26
+ width: 100%;
27
+ z-index: 130;
28
+ pointer-events: none;
29
+ touch-action: none;
30
+ }
31
+
32
+ .drawer {
33
+ background: var(--widget-bg);
34
+ border-top-left-radius: 16px;
35
+ border-top-right-radius: 16px;
36
+ box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.2);
37
+ display: flex;
38
+ flex-direction: column;
39
+ transition: transform 0.3s ease;
40
+ will-change: transform;
41
+ touch-action: auto;
42
+ pointer-events: auto;
43
+ }
44
+
45
+ .drawer.dragging { transition: none; }
46
+ .drawer.closed { transform: translateY(calc(100% - 30px)); }
47
+ .drawer.half-opened { transform: translateY(70%); }
48
+ .drawer.half-opened .content { overflow-y: hidden; }
49
+ .drawer.opened { transform: translateY(0%); }
50
+
51
+ .handle {
52
+ height: 30px;
53
+ display: flex;
54
+ align-items: center;
55
+ justify-content: center;
56
+ cursor: pointer;
57
+ }
58
+
59
+ .handle-bar {
60
+ width: 40px;
61
+ height: 5px;
62
+ background: var(--grey-pale);
63
+ border-radius: 3px;
64
+ }
65
+
66
+ .content {
67
+ overflow: auto;
68
+ flex: 1;
69
+ }
70
+ `;
71
+
72
+ /**
73
+ * Component properties.
74
+ * @memberof Panoramax.components.layout.BottomDrawer#
75
+ * @type {Object}
76
+ * @property {string} [openness=half-opened] How much the drawer should be opened (closed, half-opened, opened)
77
+ */
78
+ static properties = {
79
+ _fingerY: {state: true},
80
+ _deltaFingerY: {state: true},
81
+ _drawerY: {state: true},
82
+ _isDragging: {state: true},
83
+ _drawerHeight: {state: true},
84
+ openness: {type: String, reflect: true},
85
+ };
86
+
87
+ constructor() {
88
+ super();
89
+ this._isDragging = false;
90
+ this.openness = "half-opened";
91
+ }
92
+
93
+ /** @private */
94
+ firstUpdated() {
95
+ super.firstUpdated();
96
+
97
+ this._boundTouchMove = this._onTouchMove.bind(this);
98
+ this._boundTouchEnd = this._onTouchEnd.bind(this);
99
+
100
+ this._drawerHeight = window.innerHeight - 30;
101
+
102
+ const drawer = this._getDrawer();
103
+ if (!drawer) { return; }
104
+ drawer.style.height = `${this._drawerHeight}px`;
105
+ drawer.style.maxHeight = `${this._drawerHeight}px`;
106
+
107
+ this._parent?.onceReady().then(() => {
108
+ this._parent.map?.addEventListener("click", () => this.openness = "closed");
109
+ this._parent.psv?.addEventListener("click", () => this.openness = "closed");
110
+ });
111
+ }
112
+
113
+ /** @private */
114
+ attributeChangedCallback(name, old, value) {
115
+ super.attributeChangedCallback(name, old, value);
116
+
117
+ if(name === "openness") {
118
+ // If not fully opened, force content scroll back to top
119
+ if(value !== "opened") {
120
+ const content = this.shadowRoot.querySelector(".content");
121
+ if(content) { content.scrollTop = 0; }
122
+ }
123
+
124
+ // Remove hand-defined transform
125
+ const drawer = this._getDrawer();
126
+ if (drawer) { drawer.style.transform = null; }
127
+ }
128
+ }
129
+
130
+ /** @private */
131
+ disconnectedCallback() {
132
+ super.disconnectedCallback();
133
+ this._cleanupTouchListeners();
134
+ }
135
+
136
+ /** @private */
137
+ _getDrawer() {
138
+ return this.shadowRoot?.querySelector(".drawer");
139
+ }
140
+
141
+ /** @private */
142
+ _onHandleClick() {
143
+ if(this.openness === "opened") { this.openness = "closed"; }
144
+ else if(this.openness === "half-opened") { this.openness = "opened"; }
145
+ else if(this.openness === "closed") { this.openness = "half-opened"; }
146
+ }
147
+
148
+ /** @private */
149
+ _onTouchStart(e) {
150
+ this._isDragging = true;
151
+ this._startFingerY = e.touches[0].clientY;
152
+ this._deltaFingerY = 0;
153
+ this._drawerY = this._drawerHeight * OPENNESS_Y_PCT[this.openness];
154
+ window.addEventListener("touchmove", this._boundTouchMove, { passive: true });
155
+ window.addEventListener("touchend", this._boundTouchEnd);
156
+ window.addEventListener("touchcancel", this._boundTouchEnd);
157
+ }
158
+
159
+ /** @private */
160
+ _onTouchMove(e) {
161
+ if (!this._isDragging) return;
162
+
163
+ // Check if content has scroll bar, then only move if top scrolled
164
+ const content = this.shadowRoot.querySelector(".content");
165
+ if(content.scrollHeight > content.offsetHeight && content.scrollTop > 0) {
166
+ this._updateDrawerTransform(0);
167
+ return;
168
+ }
169
+
170
+ this._deltaFingerY = e.touches[0].clientY - this._startFingerY;
171
+ this._updateDrawerTransform(this._drawerY + this._deltaFingerY);
172
+ }
173
+
174
+ /** @private */
175
+ _onTouchEnd(e) {
176
+ // Touchend is also called when "clicking" on mobile
177
+ if ((!this._isDragging || Math.abs(this._deltaFingerY) < 30) && !e.target.closest(".handle")) { return; }
178
+ e.preventDefault();
179
+
180
+ this._isDragging = false;
181
+
182
+ if(this._deltaFingerY === 0 && this.openness === "closed") {
183
+ this.openness = "half-opened";
184
+ }
185
+ else {
186
+ this._updateDrawerTransform(this._drawerY + this._deltaFingerY, true);
187
+ }
188
+
189
+ this._cleanupTouchListeners();
190
+ this._startFingerY = null;
191
+ this._deltaFingerY = null;
192
+ }
193
+
194
+ /** @private */
195
+ _cleanupTouchListeners() {
196
+ window.removeEventListener("touchmove", this._boundTouchMove);
197
+ window.removeEventListener("touchend", this._boundTouchEnd);
198
+ window.removeEventListener("touchcancel", this._boundTouchCancel);
199
+ }
200
+
201
+ /** @private */
202
+ _updateDrawerTransform(y, snap = false) {
203
+ const drawer = this._getDrawer();
204
+ if (!drawer) { return; }
205
+
206
+ y = Math.max(0, Math.min(y, this._drawerHeight - 30));
207
+
208
+ // Snap to nearest static position
209
+ if(snap) {
210
+ // Gesture goes up
211
+ if(this._deltaFingerY < 0) {
212
+ if(this.openness === "closed") {
213
+ // How much it goes up ?
214
+ if(Math.abs(this._deltaFingerY) > this._drawerHeight * (1-OPENNESS_Y_PCT["half-opened"])) {
215
+ this.openness = "opened";
216
+ }
217
+ else { this.openness = "half-opened"; }
218
+ }
219
+ else { this.openness = "opened"; }
220
+ }
221
+ // Gesture goes down
222
+ else { this.openness = "closed"; }
223
+
224
+ this._drawerY = null;
225
+ y = Math.max(0, Math.min(OPENNESS_Y_PCT[this.openness] * this._drawerHeight, this._drawerHeight - 30));
226
+ }
227
+
228
+ drawer.style.transform = `translateY(${y}px)`;
229
+ }
230
+
231
+ /** @private */
232
+ render() {
233
+ const classes = {
234
+ "drawer": true,
235
+ [this.openness]: true,
236
+ "dragging": this._isDragging,
237
+ };
238
+
239
+ return html`
240
+ <div
241
+ class=${classMap(classes)}
242
+ @touchstart="${this._onTouchStart}"
243
+ @touchmove="${this._onTouchMove}"
244
+ @touchend="${this._onTouchEnd}"
245
+ >
246
+ <div class="handle" @click=${this._onHandleClick}>
247
+ <div class="handle-bar"></div>
248
+ </div>
249
+ <div class="content">
250
+ <slot></slot>
251
+ </div>
252
+ </div>
253
+ `;
254
+ }
255
+ }
256
+
257
+ customElements.define("pnx-bottom-drawer", BottomDrawer);
@@ -0,0 +1,112 @@
1
+ import { LitElement, html, css } from "lit";
2
+
3
+ /**
4
+ * CorneredGrid layout offers a convenient way to display overlay widgets on top of a background component.
5
+ * This is useful for showing buttons in corners over a map or picture display.
6
+ * Grid layout is organised through many slots (background and corners) : two rows (top, bottom) and three columns (right, middle, left).
7
+ * @class Panoramax.components.layout.CorneredGrid
8
+ * @element pnx-cornered-grid
9
+ * @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
10
+ * @slot `bg` The full size background component
11
+ * @slot `top-left` The top-left corner
12
+ * @slot `top` The top middle corner
13
+ * @slot `top-right` The top-right corner
14
+ * @slot `bottom-left` The bottom-left corner
15
+ * @slot `bottom` The bottom middle corner
16
+ * @slot `bottom-right` The bottom-right corner
17
+ * @example
18
+ * ```html
19
+ * <pnx-cornered-grid>
20
+ * <pnx-map slot="bg" />
21
+ * <pnx-button slot="top-left" />
22
+ * <pnx-button slot="top-left" />
23
+ * <pnx-legend slot="bottom" />
24
+ * </pnx-cornered-grid>
25
+ * ```
26
+ */
27
+ export default class CorneredGrid extends LitElement {
28
+ /** @private */
29
+ static styles = css`
30
+ :host {
31
+ position: relative;
32
+ width: 100%;
33
+ height: 100%;
34
+ display: flex;
35
+ flex-direction: column;
36
+ gap: 5px;
37
+ flex-wrap: no-wrap;
38
+ }
39
+
40
+ .bg {
41
+ position: absolute;
42
+ inset: 0;
43
+ z-index: 0;
44
+ }
45
+
46
+ .row {
47
+ display: flex;
48
+ flex-direction: row;
49
+ flex: 1 1 50%;
50
+ pointer-events: none;
51
+ }
52
+
53
+ .row.bottom { align-items: flex-end; }
54
+
55
+ .corner {
56
+ position: relative;
57
+ flex: 1 1 33%;
58
+ display: flex;
59
+ gap: 10px;
60
+ height: min-content;
61
+ }
62
+
63
+ .corner slot {
64
+ pointer-events: auto;
65
+ position: relative;
66
+ z-index: 120;
67
+ }
68
+
69
+ .row.bottom .corner:not(.middle) { flex-direction: column; }
70
+ .row.bottom .corner.left { flex-direction: column-reverse; justify-content: flex-start; align-items: flex-start; }
71
+ .row.bottom .corner.middle { justify-content: center; align-items: flex-end; }
72
+ .row.bottom .corner.right { justify-content: flex-end; align-items: flex-end; }
73
+
74
+ .row.top .corner { flex-direction: row; }
75
+ .row.top .corner.left { justify-content: flex-start; align-items: flex-start; }
76
+ .row.top .corner.middle { justify-content: center; }
77
+ .row.top .corner.right { justify-content: flex-end; align-items: flex-start; }
78
+ `;
79
+
80
+ /** @private */
81
+ render() {
82
+ return html`
83
+ <div class="bg">
84
+ <slot name="bg"></slot>
85
+ </div>
86
+ <div class="row top">
87
+ <div class="corner left" part="corner-top-left">
88
+ <slot name="top-left"></slot>
89
+ </div>
90
+ <div class="corner middle" part="corner-top">
91
+ <slot name="top"></slot>
92
+ </div>
93
+ <div class="corner right" part="corner-top-right">
94
+ <slot name="top-right"></slot>
95
+ </div>
96
+ </div>
97
+ <div class="row bottom">
98
+ <div class="corner left" part="corner-bottom-left">
99
+ <slot name="bottom-left"></slot>
100
+ </div>
101
+ <div class="corner middle" part="corner-bottom">
102
+ <slot name="bottom"></slot>
103
+ </div>
104
+ <div class="corner right" part="corner-bottom-right">
105
+ <slot name="bottom-right"></slot>
106
+ </div>
107
+ </div>
108
+ `;
109
+ }
110
+ }
111
+
112
+ customElements.define("pnx-cornered-grid", CorneredGrid);
@@ -0,0 +1,117 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import SwitchBig from "../../img/switch_big.svg";
3
+ import SwitchMini from "../../img/switch_mini.svg";
4
+
5
+ /**
6
+ * Mini layout allows to show a reduced or collapsed component in a corner of a main component.
7
+ * For example, show a reduced map on top of a photo display.
8
+ * @class Panoramax.components.layout.Mini
9
+ * @element pnx-mini
10
+ * @slot `default` The component to display.
11
+ * @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
12
+ * @fires Panoramax.components.layout.Mini#expand
13
+ * @example
14
+ * ```html
15
+ * <pnx-mini icon=${fa(faMap)} ._parent=${viewer}>
16
+ * <pnx-map />
17
+ * </pnx-mini>
18
+ * ```
19
+ */
20
+ export default class Mini extends LitElement {
21
+ /** @private */
22
+ static styles = css`
23
+ :host {
24
+ display: block;
25
+ position: absolute;
26
+ inset: 0;
27
+ pointer-events: none;
28
+ }
29
+
30
+ slot {
31
+ z-index: 120;
32
+ pointer-events: auto;
33
+ }
34
+
35
+ pnx-button {
36
+ z-index: 121;
37
+ position: absolute;
38
+ pointer-events: auto;
39
+ }
40
+
41
+ pnx-button img {
42
+ height: 20px;
43
+ }
44
+
45
+ pnx-button:not([size="xxl"])::part(btn) {
46
+ border-top-left-radius: 0;
47
+ border-bottom-right-radius: 0;
48
+ }
49
+ `;
50
+
51
+ /**
52
+ * Component properties.
53
+ * @memberof Panoramax.components.layout.Mini#
54
+ * @type {Object}
55
+ * @property {string} [icon] The icon to use in collapsed button.
56
+ * @property {boolean} [collapsed=false] Collapsed displays only a simple button.
57
+ */
58
+ static properties = {
59
+ icon: {type: String},
60
+ collapsed: {type: Boolean, reflect: true},
61
+ };
62
+
63
+ constructor() {
64
+ super();
65
+
66
+ this.collapsed = false;
67
+ this.icon = null;
68
+ }
69
+
70
+ /** @private */
71
+ render() {
72
+ const isSmall = this._parent?.isWidthSmall() || this._parent?.isHeightSmall();
73
+
74
+ /**
75
+ * Event when user ask for component expand
76
+ * @event Panoramax.components.layout.Mini#expand
77
+ * @type {Event}
78
+ */
79
+ const onExpand = () => this.dispatchEvent(new Event("expand", {bubbles: true, composed: true}));
80
+
81
+ if(this.collapsed || isSmall) {
82
+ const click = isSmall ? onExpand : () => this.collapsed = false;
83
+ return html`<pnx-button
84
+ kind="flat"
85
+ size="xxl"
86
+ style="bottom: 0"
87
+ @click=${click}
88
+ >${this.icon}</pnx-button>`;
89
+ }
90
+ else {
91
+ return html`
92
+ <slot></slot>
93
+
94
+ <pnx-button
95
+ kind="superflat"
96
+ title=${this._parent?._t.pnx.expand_info}
97
+ style="top: 0; right: 0"
98
+ @click=${onExpand}
99
+ >
100
+ <img src=${SwitchBig} alt="" />
101
+ ${this._parent?._t.pnx.expand}
102
+ </pnx-button>
103
+
104
+ <pnx-button
105
+ kind="flat"
106
+ title=${this._parent?._t.pnx.minimize}
107
+ style="bottom: 0; left: 0"
108
+ @click=${() => this.collapsed = true}
109
+ >
110
+ <img src=${SwitchMini} alt="" />
111
+ </pnx-button>
112
+ `;
113
+ }
114
+ }
115
+ }
116
+
117
+ customElements.define("pnx-mini", Mini);
@@ -0,0 +1,133 @@
1
+ import { LitElement, html, css } from "lit";
2
+
3
+ /**
4
+ * Tabs offers a nice paged content rendering based on tabs buttons.
5
+ * The list of tab names are passed through `title` slots, and content using `content` slots.
6
+ * Note that tab names and contents should respect a coherent order.
7
+ * @class Panoramax.components.layout.Tabs
8
+ * @element pnx-tabs
9
+ * @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
10
+ * @slot `title` A single tab name
11
+ * @slot `content` A single tab content
12
+ * @example
13
+ * ```html
14
+ * <pnx-tabs>
15
+ * <h4 slot="title">Tab 1</h4>
16
+ * <div slot="content">Tab 1 content</div>
17
+ *
18
+ * <h4 slot="title">Tab 2</h4>
19
+ * <div slot="content">Tab 2 content</div>
20
+ *
21
+ * <h4 slot="title">Tab 3</h4>
22
+ * <div slot="content">Tab 3 content</div>
23
+ * </pnx-tabs>
24
+ * ```
25
+ */
26
+ export default class Tabs extends LitElement {
27
+ /** @private */
28
+ static styles = css`
29
+ /* Tabs */
30
+ nav {
31
+ display: flex;
32
+ gap: 5px;
33
+ align-items: stretch;
34
+ overflow-x: auto;
35
+ touch-action: pan-x;
36
+ }
37
+ nav ::slotted(*) {
38
+ color: var(--grey-dark);
39
+ border-bottom: 2px solid rgba(0,0,0,0);
40
+ cursor: pointer;
41
+ transition: all 0.1s;
42
+ flex: 1;
43
+ }
44
+ nav ::slotted(*:hover:not(.active)) { background-color: var(--blue-pale); }
45
+ nav ::slotted(*.active) {
46
+ color: var(--blue-dark);
47
+ border-bottom: 2px solid var(--blue-dark);
48
+ }
49
+
50
+ /* Content */
51
+ .contents ::slotted(div) { display: none !important; }
52
+ .contents ::slotted(.active) { display: block !important; }
53
+ `;
54
+
55
+ /**
56
+ * Component properties.
57
+ * @memberof Panoramax.components.layout.Tabs#
58
+ * @type {Object}
59
+ * @property {string} [activeTabIndex=0] The selected tab index
60
+ */
61
+ static properties = {
62
+ activeTabIndex: {type: Number, reflect: true},
63
+ };
64
+
65
+ constructor() {
66
+ super();
67
+ this.activeTabIndex = 0;
68
+ }
69
+
70
+ /** @private */
71
+ _getTabs() {
72
+ return this.shadowRoot.querySelector("slot[name='title']").assignedNodes();
73
+ }
74
+
75
+ /** @private */
76
+ _getContents() {
77
+ return this.shadowRoot.querySelector("slot[name='content']").assignedNodes();
78
+ }
79
+
80
+ /** @private */
81
+ _changeTab(tabTarget, tabIndex) {
82
+ const tabs = this._getTabs();
83
+ const contents = this._getContents();
84
+
85
+ // Check if tab change is possible
86
+ if(tabTarget !== undefined || tabIndex !== undefined) {
87
+ // For tab target, check if a nav tab has really been clicked
88
+ if(tabTarget) { tabIndex = tabs.findIndex(tab => (
89
+ tab === tabTarget || tab === tabTarget.parentNode
90
+ )); }
91
+
92
+ if(!isNaN(tabIndex) && tabIndex >= 0 && tabIndex < tabs.length) {
93
+ tabs.forEach((tab, index) => {
94
+ if (index == tabIndex) {
95
+ this.activeTabIndex = index;
96
+ contents[index].classList.add("active");
97
+ tab.classList.add("active");
98
+ } else {
99
+ contents[index].classList.remove("active");
100
+ tab.classList.remove("active");
101
+ }
102
+ });
103
+ }
104
+ }
105
+
106
+ }
107
+
108
+ /** @private */
109
+ _handleTabClick(event) {
110
+ this._changeTab(event.target);
111
+ }
112
+
113
+ /** @private */
114
+ updated(changedProperties) {
115
+ if(changedProperties.has("activeTabIndex")) {
116
+ this._changeTab(undefined, this.activeTabIndex);
117
+ }
118
+ }
119
+
120
+ /** @private */
121
+ render() {
122
+ return html`
123
+ <nav @click="${this._handleTabClick}">
124
+ <slot name="title"></slot>
125
+ </nav>
126
+ <div class="contents">
127
+ <slot name="content"></slot>
128
+ </div>
129
+ `;
130
+ }
131
+ }
132
+
133
+ customElements.define("pnx-tabs", Tabs);
@@ -0,0 +1,9 @@
1
+ /**
2
+ * General layout components
3
+ * @module Panoramax:components:layout
4
+ */
5
+
6
+ export {default as BottomDrawer} from "./BottomDrawer";
7
+ export {default as CorneredGrid} from "./CorneredGrid";
8
+ export {default as Mini} from "./Mini";
9
+ export {default as Tabs} from "./Tabs";