@panoramax/web-viewer 4.0.1-develop-32d1fb01 → 4.0.1-develop-2250d7d3

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.
@@ -35,4 +35,5 @@ Component properties.
35
35
  | [kind] | <code>string</code> | <code>&quot;full&quot;</code> | The style variation of the button (full, outline, flat, superflat, inline, superinline) |
36
36
  | [size] | <code>string</code> | <code>&quot;md&quot;</code> | The size of the button (sm, md, l, xl, xxl) |
37
37
  | [unstyled] | <code>boolean</code> | <code>false</code> | Disable all styling (for list group integration) |
38
+ | [title] | <code>string</code> | | Tooltip text displayed when hovering over the button |
38
39
 
@@ -0,0 +1,32 @@
1
+ <a name="Panoramax.components.ui.widgets.CopyCoordinates"></a>
2
+
3
+ ## Panoramax.components.ui.widgets.CopyCoordinates ⇐ <code>[lit.LitElement](https://lit.dev/docs/api/LitElement/)</code>
4
+ **Kind**: static class of <code>Panoramax.components.ui.widgets</code>
5
+ **Extends**: <code>[lit.LitElement](https://lit.dev/docs/api/LitElement/)</code>
6
+ **Element**: pnx-copy-coordinates
7
+
8
+ * [.CopyCoordinates](#Panoramax.components.ui.widgets.CopyCoordinates) ⇐ <code>[lit.LitElement](https://lit.dev/docs/api/LitElement/)</code>
9
+ * [new CopyCoordinates()](#new_Panoramax.components.ui.widgets.CopyCoordinates_new)
10
+ * [.properties](#Panoramax.components.ui.widgets.CopyCoordinates+properties) : <code>Object</code>
11
+
12
+ <a name="new_Panoramax.components.ui.widgets.CopyCoordinates_new"></a>
13
+
14
+ ### new CopyCoordinates()
15
+ Copy Coordinates button allows easy copy of several format of map coordinates.
16
+
17
+ **Example**
18
+ ```html
19
+ <pnx-copy-coordinates gps=${[-1.7, 48.6]} _parent=${viewer} />
20
+ ```
21
+ <a name="Panoramax.components.ui.widgets.CopyCoordinates+properties"></a>
22
+
23
+ ### copyCoordinates.properties : <code>Object</code>
24
+ Component properties.
25
+
26
+ **Kind**: instance property of [<code>CopyCoordinates</code>](#Panoramax.components.ui.widgets.CopyCoordinates)
27
+ **Properties**
28
+
29
+ | Name | Type | Description |
30
+ | --- | --- | --- |
31
+ | gps | <code>Array.&lt;number&gt;</code> | GPS/map coordinates, as [lon, lat] |
32
+
package/docs/reference.md CHANGED
@@ -61,6 +61,7 @@ Basic UI components:
61
61
 
62
62
  More complex UI components (but not menus):
63
63
 
64
+ - [CopyCoordinates](./reference/components/ui/widgets/CopyCoordinates.md) : a copy-to-clipboard button for coordinates, with many format options.
64
65
  - [GeoSearch](./reference/components/ui/widgets/GeoSearch.md) : a geocoder search bar with GPS location tool.
65
66
  - [Legend](./reference/components/ui/widgets/Legend.md) : a togglable map/picture legend.
66
67
  - [MapFiltersButton](./reference/components/ui/widgets/MapFiltersButton.md) : a togglable map filters button & menu.
package/mkdocs.yml CHANGED
@@ -77,6 +77,7 @@ nav:
77
77
  - ShareMenu: 'reference/components/menus/ShareMenu.md'
78
78
  - ui:
79
79
  - widgets:
80
+ - CopyCoordinates: 'reference/components/ui/widgets/CopyCoordinates.md'
80
81
  - GeoSearch: 'reference/components/ui/widgets/GeoSearch.md'
81
82
  - Legend: 'reference/components/ui/widgets/Legend.md'
82
83
  - MapFiltersButton: 'reference/components/ui/widgets/MapFiltersButton.md'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@panoramax/web-viewer",
3
- "version": "4.0.1-develop-32d1fb01",
3
+ "version": "4.0.1-develop-2250d7d3",
4
4
  "description": "Panoramax web viewer for geolocated pictures",
5
5
  "main": "build/index.js",
6
6
  "author": "Panoramax team",
@@ -146,8 +146,18 @@ export default class PictureMetadata extends LitElement {
146
146
  // Location tab
147
147
  const orientation = this._meta?.properties?.["view:azimuth"] !== undefined && `${this._meta.properties["view:azimuth"]}°`;
148
148
  const locationData = [
149
- { title: this._parent?._t.pnx.metadata_location_longitude, content: this._meta.gps[0] },
150
- { title: this._parent?._t.pnx.metadata_location_latitude, content: this._meta.gps[1] },
149
+ {
150
+ title: this._parent?._t.pnx.metadata_location_coordinates,
151
+ content: html`
152
+ ${this._meta.gps[0]}, ${this._meta.gps[1]}
153
+ <pnx-copy-coordinates
154
+ ._parent=${this._parent}
155
+ .gps=${this._meta.gps}
156
+ style="margin-left: 10px"
157
+ ></pnx-copy-coordinates>
158
+ `,
159
+ style: "width: 100%"
160
+ },
151
161
  { title: this._parent?._t.pnx.metadata_location_orientation, content: orientation || missing() },
152
162
  { title: this._parent?._t.pnx.metadata_location_precision, content: getGPSPrecision(this._meta) || missing() },
153
163
  ];
@@ -274,6 +274,67 @@ export const btn = css`
274
274
  }
275
275
  `;
276
276
 
277
+ // Button group
278
+ export const btngroup = css`
279
+ pnx-button-group > pnx-button {
280
+ display: inline-flex;
281
+ }
282
+
283
+ pnx-button-group > pnx-button::part(btn) {
284
+ height: unset;
285
+ }
286
+
287
+ /* Togglable in group */
288
+ pnx-button-group > pnx-togglable-group > pnx-button {
289
+ width: 100%;
290
+ height: 100%;
291
+ }
292
+
293
+ /* Row */
294
+ pnx-button-group[dir="row"] > :not(:first-child):not(:last-child)::part(btn),
295
+ pnx-button-group[dir="row"] > :not(:first-child):not(:last-child) > ::part(btn) {
296
+ border-radius: 0;
297
+ border-left: none;
298
+ border-right: none;
299
+ }
300
+
301
+ pnx-button-group[dir="row"] > :first-child::part(btn),
302
+ pnx-button-group[dir="row"] > :first-child > ::part(btn) {
303
+ border-top-right-radius: 0;
304
+ border-bottom-right-radius: 0;
305
+ border-right: none;
306
+ }
307
+
308
+ pnx-button-group[dir="row"] > :last-child::part(btn),
309
+ pnx-button-group[dir="row"] > :last-child > ::part(btn) {
310
+ border-top-left-radius: 0;
311
+ border-bottom-left-radius: 0;
312
+ border-left: none;
313
+ }
314
+
315
+ /* Column */
316
+ pnx-button-group[dir="column"] > :not(:first-child):not(:last-child)::part(btn),
317
+ pnx-button-group[dir="column"] > :not(:first-child):not(:last-child) > ::part(btn) {
318
+ border-radius: 0;
319
+ border-top: none;
320
+ border-bottom: none;
321
+ }
322
+
323
+ pnx-button-group[dir="column"] > :first-child::part(btn),
324
+ pnx-button-group[dir="column"] > :first-child > ::part(btn) {
325
+ border-bottom-right-radius: 0;
326
+ border-bottom-left-radius: 0;
327
+ border-bottom: none;
328
+ }
329
+
330
+ pnx-button-group[dir="column"] > :last-child::part(btn),
331
+ pnx-button-group[dir="column"] > :last-child > ::part(btn) {
332
+ border-top-left-radius: 0;
333
+ border-top-right-radius: 0;
334
+ border-top: none;
335
+ }
336
+ `;
337
+
277
338
  // Titles
278
339
  export const titles = css`
279
340
  h1, h2, h3, h4, h5, h6 {
@@ -1,3 +1,5 @@
1
+ /* Also defined in styles.js */
2
+
1
3
  pnx-button-group > pnx-button {
2
4
  display: inline-flex;
3
5
  }
@@ -41,6 +41,7 @@ export default class CopyButton extends LitElement {
41
41
  * @property {string} [kind=full] The style variation of the button (full, outline, flat, superflat, inline, superinline)
42
42
  * @property {string} [size=md] The size of the button (sm, md, l, xl, xxl)
43
43
  * @property {boolean} [unstyled=false] Disable all styling (for list group integration)
44
+ * @property {string} [title] Tooltip text displayed when hovering over the button
44
45
  */
45
46
  static properties = {
46
47
  text: {type: String},
@@ -48,6 +49,7 @@ export default class CopyButton extends LitElement {
48
49
  kind: {type: String},
49
50
  size: {type: String},
50
51
  unstyled: {type: Boolean},
52
+ title: { type: String },
51
53
  _active: {state: true, type: Boolean},
52
54
  };
53
55
 
@@ -94,7 +96,7 @@ export default class CopyButton extends LitElement {
94
96
  [`pnx-btn-${this.kind}`]: !this.unstyled,
95
97
  [`pnx-btn-${this.size}`]: !this.unstyled,
96
98
  };
97
- return html`<button class=${classMap(classes)} part="btn">
99
+ return html`<button title=${this.title} class=${classMap(classes)} part="btn">
98
100
  ${this._active ?
99
101
  html`${this._t?.pnx.copied || ""} ${fa(faCheck)}` :
100
102
  html`<slot>${fa(faCopy)} ${this._t?.pnx.copy || ""}</slot>`
@@ -145,7 +145,7 @@ export default class TogglableGroup extends LitElement {
145
145
  "pnx-padded": this.padded !== "false",
146
146
  };
147
147
 
148
- return html`<div class="container">
148
+ return html`<div class="container" @click=${e => e.stopPropagation()}>
149
149
  <slot name="button" @slotchange=${this.handleButtonSlotChange}></slot>
150
150
  <div class=${classMap(panelClasses)} part="menu">
151
151
  <slot></slot>
@@ -0,0 +1,75 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import { degToDms } from "../../../utils/utils";
3
+ import { fa } from "../../../utils/widgets";
4
+ import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown";
5
+ import { btngroup } from "../../styles";
6
+
7
+ /**
8
+ * Copy Coordinates button allows easy copy of several format of map coordinates.
9
+ * @class Panoramax.components.ui.widgets.CopyCoordinates
10
+ * @element pnx-copy-coordinates
11
+ * @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
12
+ * @example
13
+ * ```html
14
+ * <pnx-copy-coordinates gps=${[-1.7, 48.6]} _parent=${viewer} />
15
+ * ```
16
+ */
17
+ export default class CopyCoordinates extends LitElement {
18
+ /** @private */
19
+ static styles = [btngroup, css`
20
+ pnx-togglable-group::part(menu) {
21
+ border-radius: 5px;
22
+ min-width: unset;
23
+ }
24
+ `];
25
+
26
+ /**
27
+ * Component properties.
28
+ * @memberof Panoramax.components.ui.widgets.CopyCoordinates#
29
+ * @type {Object}
30
+ * @property {number[]} gps GPS/map coordinates, as [lon, lat]
31
+ */
32
+ static properties = {
33
+ gps: {type: Array},
34
+ };
35
+
36
+ /** @private */
37
+ render() {
38
+ const dmslonval = degToDms(this.gps[0]);
39
+ const dmslon = dmslonval.d < 0 ? `${Math.abs(dmslonval.d)}°${dmslonval.m}'${dmslonval.s}"W` : `${dmslonval.d}°${dmslonval.m}'${dmslonval.s}"E`;
40
+ const dmslatval = degToDms(this.gps[1]);
41
+ const dmslat = dmslatval.d < 0 ? `${Math.abs(dmslatval.d)}°${dmslatval.m}'${dmslatval.s}"S` : `${dmslatval.d}°${dmslatval.m}'${dmslatval.s}"N`;
42
+
43
+ const values = {
44
+ ddeclatlon: `${this.gps[1].toFixed(7)}, ${this.gps[0].toFixed(7)}`,
45
+ ddeclonlat: `${this.gps[0].toFixed(7)}, ${this.gps[1].toFixed(7)}`,
46
+ ddmslonlat: `${dmslon}, ${dmslat}`,
47
+ ddmslatlon: `${dmslat}, ${dmslon}`,
48
+ };
49
+
50
+ return html`<pnx-button-group style="display: inline-block; vertical-align: baseline" dir="row">
51
+ <pnx-copy-button
52
+ size="sm"
53
+ text=${values.ddeclonlat}
54
+ title=${this._parent?._t.pnx.metadata_location_copy.replace("{v}", values.ddeclonlat)}
55
+ ></pnx-copy-button>
56
+
57
+ <pnx-togglable-group padded="false" ._parent=${this._parent}>
58
+ <pnx-button
59
+ size="sm"
60
+ slot="button"
61
+ title=${this._parent?._t.pnx.metadata_location_copy_more}
62
+ >${fa(faChevronDown)}</pnx-button>
63
+ <pnx-list-group>
64
+ ${Object.values(values).map(text => html`
65
+ <pnx-copy-button unstyled text=${text} ._t=${this._parent._t}>
66
+ ${this._parent?._t.pnx.metadata_location_copy.replace("{v}", text)}
67
+ </pnx-copy-button>
68
+ `)}
69
+ </pnx-list-group>
70
+ </pnx-togglable-group>
71
+ </pnx-button-group>`;
72
+ }
73
+ }
74
+
75
+ customElements.define("pnx-copy-coordinates", CopyCoordinates);
@@ -58,7 +58,7 @@ export default class PictureLegendActions extends LitElement {
58
58
 
59
59
  /** @private */
60
60
  _closeGroup() {
61
- this.renderRoot.querySelector("pnx-togglable-group").close();
61
+ this.renderRoot.querySelector("#pic-legend-headline-menu").close();
62
62
  }
63
63
 
64
64
  /** @private */
@@ -3,6 +3,7 @@
3
3
  * @module Panoramax:components:ui:widgets
4
4
  */
5
5
 
6
+ export {default as CopyCoordinates} from "./CopyCoordinates";
6
7
  export {default as GeoSearch} from "./GeoSearch";
7
8
  export {default as Legend} from "./Legend";
8
9
  export {default as MapFiltersButton} from "./MapFiltersButton";
@@ -156,10 +156,11 @@
156
156
  "metadata_camera_resolution": "Resolution",
157
157
  "metadata_camera_focal_length": "Focal length",
158
158
  "metadata_location": "Position",
159
- "metadata_location_longitude": "Longitude",
160
- "metadata_location_latitude": "Latitude",
159
+ "metadata_location_coordinates": "Coordinates (longitude, latitude)",
161
160
  "metadata_location_orientation": "Direction",
162
161
  "metadata_location_precision": "Positioning precision",
162
+ "metadata_location_copy": "Copy {v}",
163
+ "metadata_location_copy_more": "More coordinates copy options",
163
164
  "metadata_quality": "Quality",
164
165
  "metadata_quality_help": "Know more about Quality Score",
165
166
  "metadata_quality_score": "Global score",
@@ -156,10 +156,11 @@
156
156
  "metadata_camera_resolution": "Résolution",
157
157
  "metadata_camera_focal_length": "Longueur focale",
158
158
  "metadata_location": "Position",
159
- "metadata_location_longitude": "Longitude",
160
- "metadata_location_latitude": "Latitude",
159
+ "metadata_location_coordinates": "Coordonnées (longitude, latitude)",
161
160
  "metadata_location_orientation": "Direction",
162
161
  "metadata_location_precision": "Précision du positionnement",
162
+ "metadata_location_copy": "Copier {v}",
163
+ "metadata_location_copy_more": "Copier d'autres formats de coordonnées",
163
164
  "metadata_quality": "Qualité",
164
165
  "metadata_quality_help": "En savoir plus sur le score de qualité",
165
166
  "metadata_quality_score": "Note globale",
@@ -183,7 +183,9 @@
183
183
  "contribute_id": "Contribuisci a OpenStreetMap con l’editor iD",
184
184
  "geo_uri": "App esterna",
185
185
  "filter_date_6months": "6 mesi",
186
- "picture_all": "Tutte"
186
+ "picture_all": "Tutte",
187
+ "metadata_exif_doc": "Doc per gli attributi EXIF",
188
+ "metadata_exif_doc_title": "Vai alla documentazione di Exiv2 per i dettagli completi sulle definizioni degli attributi EXIF e XMP"
187
189
  },
188
190
  "psv": {
189
191
  "loadError": "Impossibile caricare l’immagine panoramica",
@@ -211,6 +211,19 @@ export function xyzToPosition(x, y, z) {
211
211
  };
212
212
  }
213
213
 
214
+ /**
215
+ * Transforms decimal degrees into degrees/minutes/seconds format.
216
+ * @param {number} degrees The decimal degrees value
217
+ * @returns {object} Coordinate as {d,m,s} object
218
+ */
219
+ export function degToDms(degrees) {
220
+ const d = degrees < 0 ? Math.ceil(degrees) : Math.floor(degrees);
221
+ const rm = Math.abs(degrees - d) * 60;
222
+ const m = Math.floor(rm);
223
+ const s = parseFloat(((rm - m) * 60).toFixed(3));
224
+ return { d, m, s };
225
+ }
226
+
214
227
  /**
215
228
  * Get the query string for JOSM to load current picture area
216
229
  * @returns {string} The query string, or null if not available
@@ -8,18 +8,18 @@ jest.mock("../../src/utils/map", () => ({
8
8
 
9
9
  describe("getGrade", () => {
10
10
  it("works with null-like", () => {
11
- expect(utils.getGrade(utils.QUALITYSCORE_RES_FLAT_VALUES, null)).toBeNull();
12
- expect(utils.getGrade(utils.QUALITYSCORE_RES_FLAT_VALUES, undefined)).toBeNull();
13
- expect(utils.getGrade(utils.QUALITYSCORE_RES_FLAT_VALUES, "")).toBeNull();
14
- });
11
+ expect(utils.getGrade(utils.QUALITYSCORE_RES_FLAT_VALUES, null)).toBeNull();
12
+ expect(utils.getGrade(utils.QUALITYSCORE_RES_FLAT_VALUES, undefined)).toBeNull();
13
+ expect(utils.getGrade(utils.QUALITYSCORE_RES_FLAT_VALUES, "")).toBeNull();
14
+ });
15
15
 
16
16
  it("works with grade values", () => {
17
- expect(utils.getGrade(utils.QUALITYSCORE_RES_FLAT_VALUES, 0)).toBe(1);
18
- expect(utils.getGrade(utils.QUALITYSCORE_RES_FLAT_VALUES, 5)).toBe(1);
19
- expect(utils.getGrade(utils.QUALITYSCORE_RES_FLAT_VALUES, 12)).toBe(2);
20
- expect(utils.getGrade(utils.QUALITYSCORE_RES_FLAT_VALUES, 25)).toBe(3);
21
- expect(utils.getGrade(utils.QUALITYSCORE_RES_FLAT_VALUES, 40)).toBe(4);
22
- });
17
+ expect(utils.getGrade(utils.QUALITYSCORE_RES_FLAT_VALUES, 0)).toBe(1);
18
+ expect(utils.getGrade(utils.QUALITYSCORE_RES_FLAT_VALUES, 5)).toBe(1);
19
+ expect(utils.getGrade(utils.QUALITYSCORE_RES_FLAT_VALUES, 12)).toBe(2);
20
+ expect(utils.getGrade(utils.QUALITYSCORE_RES_FLAT_VALUES, 25)).toBe(3);
21
+ expect(utils.getGrade(utils.QUALITYSCORE_RES_FLAT_VALUES, 40)).toBe(4);
22
+ });
23
23
  });
24
24
 
25
25
  describe("getDistance", () => {
@@ -32,25 +32,25 @@ describe("getDistance", () => {
32
32
  });
33
33
 
34
34
  describe("svgToPSVLink", () => {
35
- it("works", () => {
36
- const base64Svg = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnPjxjaXJjbGUgY3g9JzUnIGN5PSc1JyByPSc1JyBmaWxsPScjMDAwJy8+PC9zdmc+";
37
- const fillColor = "red";
38
- const result = utils.svgToPSVLink(base64Svg, fillColor);
39
-
40
- expect(result).toBeInstanceOf(HTMLButtonElement);
41
- expect(result.classList.contains("pnx-psv-tour-arrows")).toBe(true);
42
- expect(result.style.color).toBe(fillColor);
43
- expect(result.querySelector("svg")).not.toBeNull();
44
- });
45
-
46
- it("works with invalid input", () => {
47
- const invalidBase64Svg = "http://test.net/invalid_string";
48
- const result = utils.svgToPSVLink(invalidBase64Svg, "blue");
49
-
50
- expect(result).toBeInstanceOf(HTMLImageElement);
51
- expect(result.src).toBe(invalidBase64Svg);
52
- expect(result.alt).toBe("");
53
- });
35
+ it("works", () => {
36
+ const base64Svg = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnPjxjaXJjbGUgY3g9JzUnIGN5PSc1JyByPSc1JyBmaWxsPScjMDAwJy8+PC9zdmc+";
37
+ const fillColor = "red";
38
+ const result = utils.svgToPSVLink(base64Svg, fillColor);
39
+
40
+ expect(result).toBeInstanceOf(HTMLButtonElement);
41
+ expect(result.classList.contains("pnx-psv-tour-arrows")).toBe(true);
42
+ expect(result.style.color).toBe(fillColor);
43
+ expect(result.querySelector("svg")).not.toBeNull();
44
+ });
45
+
46
+ it("works with invalid input", () => {
47
+ const invalidBase64Svg = "http://test.net/invalid_string";
48
+ const result = utils.svgToPSVLink(invalidBase64Svg, "blue");
49
+
50
+ expect(result).toBeInstanceOf(HTMLImageElement);
51
+ expect(result.src).toBe(invalidBase64Svg);
52
+ expect(result.alt).toBe("");
53
+ });
54
54
  });
55
55
 
56
56
  describe("getAzimuth", () => {
@@ -197,92 +197,119 @@ describe("xyzToPosition", () => {
197
197
  });
198
198
  });
199
199
 
200
+ describe("degToDms", () => {
201
+ it("converts positive decimal degrees to DMS correctly", () => {
202
+ const result = utils.degToDms(45.7896541);
203
+ expect(result).toEqual({ d: 45, m: 47, s: 22.755 });
204
+ });
205
+
206
+ it("converts negative decimal degrees to DMS correctly 1", () => {
207
+ const result = utils.degToDms(-12.751234);
208
+ expect(result).toEqual({ d: -12, m: 45, s: 4.442 });
209
+ });
210
+
211
+ it("converts negative decimal degrees to DMS correctly 2", () => {
212
+ const result = utils.degToDms(-21.007598);
213
+ expect(result).toEqual({ d: -21, m: 0, s: 27.353 });
214
+ });
215
+
216
+ it("converts zero degrees to DMS correctly", () => {
217
+ const result = utils.degToDms(0);
218
+ expect(result).toEqual({ d: 0, m: 0, s: 0 });
219
+ });
220
+
221
+ it("handles integer degrees correctly", () => {
222
+ const result = utils.degToDms(90);
223
+ expect(result).toEqual({ d: 90, m: 0, s: 0 });
224
+ });
225
+ });
226
+
200
227
  describe("josmBboxParameters", () => {
201
228
  it("works with null-like", () => {
202
- expect(utils.josmBboxParameters(null)).toBeNull();
203
- expect(utils.josmBboxParameters(undefined)).toBeNull();
204
- });
205
-
206
- it("works without azimuth", () => {
207
- const meta = { gps: [2.3522, 48.8566] };
208
- const result = utils.josmBboxParameters(meta);
209
- expect(result).toBe("left=2.3522&right=2.3522&top=48.8566&bottom=48.8566&changeset_source=Panoramax");
210
- });
211
-
212
- it("works with azimuth = 0", () => {
213
- const meta = { gps: [2.3522, 48.8566], properties: { "view:azimuth": 0 } };
214
- const result = utils.josmBboxParameters(meta);
215
- expect(result).toBe("left=2.3522&right=2.3524&top=48.8568&bottom=48.8566&changeset_source=Panoramax");
216
- });
217
-
218
- it("works with azimuth = 180", () => {
219
- const meta = { gps: [2.3522, 48.8566], properties: { "view:azimuth": 180 } };
220
- const result = utils.josmBboxParameters(meta);
221
- expect(result).toBe("left=2.352&right=2.3524&top=48.8566&bottom=48.8564&changeset_source=Panoramax");
222
- });
223
-
224
- it("works with azimuth = 90", () => {
225
- const meta = { gps: [2.3522, 48.8566], properties: { "view:azimuth": 90 } };
226
- const result = utils.josmBboxParameters(meta);
227
- expect(result).toBe("left=2.3522&right=2.3524&top=48.8568&bottom=48.8564&changeset_source=Panoramax");
228
- });
229
-
230
- it("works with azimuth = 270", () => {
231
- const meta = { gps: [2.3522, 48.8566], properties: { "view:azimuth": 270 } };
232
- const result = utils.josmBboxParameters(meta);
233
- expect(result).toBe("left=2.352&right=2.3522&top=48.8568&bottom=48.8564&changeset_source=Panoramax");
234
- });
229
+ expect(utils.josmBboxParameters(null)).toBeNull();
230
+ expect(utils.josmBboxParameters(undefined)).toBeNull();
231
+ });
232
+
233
+ it("works without azimuth", () => {
234
+ const meta = { gps: [2.3522, 48.8566] };
235
+ const result = utils.josmBboxParameters(meta);
236
+ expect(result).toBe("left=2.3522&right=2.3522&top=48.8566&bottom=48.8566&changeset_source=Panoramax");
237
+ });
238
+
239
+ it("works with azimuth = 0", () => {
240
+ const meta = { gps: [2.3522, 48.8566], properties: { "view:azimuth": 0 } };
241
+ const result = utils.josmBboxParameters(meta);
242
+ expect(result).toBe("left=2.3522&right=2.3524&top=48.8568&bottom=48.8566&changeset_source=Panoramax");
243
+ });
244
+
245
+ it("works with azimuth = 180", () => {
246
+ const meta = { gps: [2.3522, 48.8566], properties: { "view:azimuth": 180 } };
247
+ const result = utils.josmBboxParameters(meta);
248
+ expect(result).toBe("left=2.352&right=2.3524&top=48.8566&bottom=48.8564&changeset_source=Panoramax");
249
+ });
250
+
251
+ it("works with azimuth = 90", () => {
252
+ const meta = { gps: [2.3522, 48.8566], properties: { "view:azimuth": 90 } };
253
+ const result = utils.josmBboxParameters(meta);
254
+ expect(result).toBe("left=2.3522&right=2.3524&top=48.8568&bottom=48.8564&changeset_source=Panoramax");
255
+ });
256
+
257
+ it("works with azimuth = 270", () => {
258
+ const meta = { gps: [2.3522, 48.8566], properties: { "view:azimuth": 270 } };
259
+ const result = utils.josmBboxParameters(meta);
260
+ expect(result).toBe("left=2.352&right=2.3522&top=48.8568&bottom=48.8564&changeset_source=Panoramax");
261
+ });
235
262
  });
236
263
 
237
264
  describe("getCookie", () => {
238
- it("should return the value of the specified cookie", () => {
239
- jest.spyOn(document, "cookie", "get").mockReturnValueOnce("session=abc123");
240
- expect(utils.getCookie("session")).toBe("abc123");
241
- });
242
-
243
- it("should return null if the cookie is not found", () => {
244
- jest.spyOn(document, "cookie", "get").mockReturnValueOnce("session=abc123");
245
- expect(utils.getCookie("user_id")).toBeUndefined();
246
- });
247
-
248
- it("should return the correct value when multiple cookies are set", () => {
249
- jest.spyOn(document, "cookie", "get").mockReturnValueOnce("session=abc123; user_id=789; user_name=John");
250
- expect(utils.getCookie("user_id")).toBe("789");
251
- });
252
-
253
- it("should return null if cookie with the specified name has no value", () => {
254
- jest.spyOn(document, "cookie", "get").mockReturnValueOnce("session=; user_id=789");
255
- expect(utils.getCookie("session")).toBe("");
256
- });
257
-
258
- it("should return the correct value when the cookie contains =", () => {
259
- jest.spyOn(document, "cookie", "get").mockReturnValueOnce("custom_cookie=abc=123");
260
- expect(utils.getCookie("custom_cookie")).toBe("abc=123");
261
- });
265
+ it("should return the value of the specified cookie", () => {
266
+ jest.spyOn(document, "cookie", "get").mockReturnValueOnce("session=abc123");
267
+ expect(utils.getCookie("session")).toBe("abc123");
268
+ });
269
+
270
+ it("should return null if the cookie is not found", () => {
271
+ jest.spyOn(document, "cookie", "get").mockReturnValueOnce("session=abc123");
272
+ expect(utils.getCookie("user_id")).toBeUndefined();
273
+ });
274
+
275
+ it("should return the correct value when multiple cookies are set", () => {
276
+ jest.spyOn(document, "cookie", "get").mockReturnValueOnce("session=abc123; user_id=789; user_name=John");
277
+ expect(utils.getCookie("user_id")).toBe("789");
278
+ });
279
+
280
+ it("should return null if cookie with the specified name has no value", () => {
281
+ jest.spyOn(document, "cookie", "get").mockReturnValueOnce("session=; user_id=789");
282
+ expect(utils.getCookie("session")).toBe("");
283
+ });
284
+
285
+ it("should return the correct value when the cookie contains =", () => {
286
+ jest.spyOn(document, "cookie", "get").mockReturnValueOnce("custom_cookie=abc=123");
287
+ expect(utils.getCookie("custom_cookie")).toBe("abc=123");
288
+ });
262
289
  });
263
290
 
264
291
  describe("getUserAccount", () => {
265
- it("should return an object with user id and name when all cookies are present", () => {
266
- jest.spyOn(document, "cookie", "get").mockReturnValue("session=abc123; user_id=789; user_name=John");
267
- expect(utils.getUserAccount()).toEqual({ id: "789", name: "John" });
268
- });
269
-
270
- it("should return null if session cookie is missing", () => {
271
- jest.spyOn(document, "cookie", "get").mockReturnValue("user_id=789; user_name=John");
272
- expect(utils.getUserAccount()).toBeNull();
273
- });
274
-
275
- it("should return null if user_id cookie is missing", () => {
276
- jest.spyOn(document, "cookie", "get").mockReturnValue("session=abc123; user_name=John");
277
- expect(utils.getUserAccount()).toBeNull();
278
- });
279
-
280
- it("should return null if user_name cookie is missing", () => {
281
- jest.spyOn(document, "cookie", "get").mockReturnValue("session=abc123; user_id=789");
282
- expect(utils.getUserAccount()).toBeNull();
283
- });
284
-
285
- it("should return null if all cookies are missing", () => {
286
- expect(utils.getUserAccount()).toBeNull();
287
- });
292
+ it("should return an object with user id and name when all cookies are present", () => {
293
+ jest.spyOn(document, "cookie", "get").mockReturnValue("session=abc123; user_id=789; user_name=John");
294
+ expect(utils.getUserAccount()).toEqual({ id: "789", name: "John" });
295
+ });
296
+
297
+ it("should return null if session cookie is missing", () => {
298
+ jest.spyOn(document, "cookie", "get").mockReturnValue("user_id=789; user_name=John");
299
+ expect(utils.getUserAccount()).toBeNull();
300
+ });
301
+
302
+ it("should return null if user_id cookie is missing", () => {
303
+ jest.spyOn(document, "cookie", "get").mockReturnValue("session=abc123; user_name=John");
304
+ expect(utils.getUserAccount()).toBeNull();
305
+ });
306
+
307
+ it("should return null if user_name cookie is missing", () => {
308
+ jest.spyOn(document, "cookie", "get").mockReturnValue("session=abc123; user_id=789");
309
+ expect(utils.getUserAccount()).toBeNull();
310
+ });
311
+
312
+ it("should return null if all cookies are missing", () => {
313
+ expect(utils.getUserAccount()).toBeNull();
314
+ });
288
315
  });