@panoramax/web-viewer 4.0.3 → 4.1.0

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 (103) hide show
  1. package/CHANGELOG.md +40 -1
  2. package/build/index.css +9 -9
  3. package/build/index.css.map +1 -1
  4. package/build/index.js +640 -456
  5. package/build/index.js.map +1 -1
  6. package/build/photo.html +1 -1
  7. package/build/viewer.html +3 -3
  8. package/build/widgets.html +1 -1
  9. package/config/jest/mocks.js +9 -1
  10. package/docs/03_URL_settings.md +21 -0
  11. package/docs/09_Develop.md +6 -0
  12. package/docs/images/comparative_3drender.jpg +0 -0
  13. package/docs/index.md +13 -0
  14. package/docs/reference/components/core/Editor.md +18 -0
  15. package/docs/reference/components/core/PhotoViewer.md +1 -0
  16. package/docs/reference/components/core/Viewer.md +1 -0
  17. package/docs/reference/components/menus/MapLegend.md +17 -0
  18. package/docs/reference/components/menus/MiniPictureLegend.md +15 -0
  19. package/docs/reference/components/menus/PictureLegend.md +17 -0
  20. package/docs/reference/components/ui/AnnotationsSwitch.md +15 -0
  21. package/docs/reference/components/ui/Button.md +1 -1
  22. package/docs/reference/components/ui/CopyButton.md +1 -1
  23. package/docs/reference/components/ui/LinkButton.md +1 -1
  24. package/docs/reference/components/ui/Map.md +18 -2
  25. package/docs/reference/components/ui/MapMore.md +6 -2
  26. package/docs/reference/components/ui/SemanticsEditor.md +87 -0
  27. package/docs/reference/components/ui/widgets/Legend.md +5 -4
  28. package/docs/reference/utils/URLHandler.md +7 -0
  29. package/docs/reference.md +3 -1
  30. package/docs/tutorials/aerial_imagery.md +13 -11
  31. package/mkdocs.yml +3 -1
  32. package/package.json +7 -7
  33. package/public/photo.html +1 -1
  34. package/public/viewer.html +3 -3
  35. package/public/widgets.html +32 -0
  36. package/src/components/core/Basic.css +2 -0
  37. package/src/components/core/Basic.js +3 -1
  38. package/src/components/core/CoverageMap.js +6 -0
  39. package/src/components/core/Editor.css +1 -0
  40. package/src/components/core/Editor.js +58 -7
  41. package/src/components/core/PhotoViewer.css +5 -10
  42. package/src/components/core/PhotoViewer.js +55 -20
  43. package/src/components/core/Viewer.css +9 -2
  44. package/src/components/core/Viewer.js +62 -33
  45. package/src/components/layout/BottomDrawer.js +2 -1
  46. package/src/components/layout/Tabs.js +4 -0
  47. package/src/components/menus/AnnotationsList.js +13 -9
  48. package/src/components/menus/MapBackground.js +8 -3
  49. package/src/components/menus/MapFilters.js +11 -2
  50. package/src/components/menus/MapLayers.js +3 -2
  51. package/src/components/menus/MapLegend.js +28 -4
  52. package/src/components/menus/MiniPictureLegend.js +74 -0
  53. package/src/components/menus/PictureLegend.js +88 -33
  54. package/src/components/menus/PictureMetadata.js +49 -17
  55. package/src/components/menus/PlayerOptions.js +3 -3
  56. package/src/components/menus/Share.js +3 -3
  57. package/src/components/menus/index.js +5 -4
  58. package/src/components/styles.js +11 -0
  59. package/src/components/ui/AnnotationsSwitch.js +169 -0
  60. package/src/components/ui/Button.js +1 -1
  61. package/src/components/ui/CopyButton.js +1 -1
  62. package/src/components/ui/LinkButton.js +1 -1
  63. package/src/components/ui/Map.css +4 -0
  64. package/src/components/ui/Map.js +17 -5
  65. package/src/components/ui/MapMore.js +61 -25
  66. package/src/components/ui/Photo.css +11 -2
  67. package/src/components/ui/Photo.js +6 -3
  68. package/src/components/ui/SemanticsEditor.js +157 -0
  69. package/src/components/ui/index.js +2 -1
  70. package/src/components/ui/widgets/GeoSearch.js +3 -2
  71. package/src/components/ui/widgets/Legend.js +69 -14
  72. package/src/components/ui/widgets/MapFiltersButton.js +3 -3
  73. package/src/components/ui/widgets/MapLayersButton.js +3 -3
  74. package/src/components/ui/widgets/OSMEditors.js +2 -2
  75. package/src/components/ui/widgets/PictureLegendActions.js +24 -42
  76. package/src/components/ui/widgets/Player.js +3 -3
  77. package/src/components/ui/widgets/Zoom.js +4 -2
  78. package/src/translations/ar.json +1 -0
  79. package/src/translations/da.json +3 -2
  80. package/src/translations/de.json +64 -13
  81. package/src/translations/en.json +5 -1
  82. package/src/translations/eo.json +32 -2
  83. package/src/translations/fr.json +7 -1
  84. package/src/translations/it.json +33 -2
  85. package/src/translations/nl.json +53 -11
  86. package/src/translations/zh_Hant.json +29 -2
  87. package/src/utils/API.js +17 -1
  88. package/src/utils/InitParameters.js +46 -4
  89. package/src/utils/URLHandler.js +9 -1
  90. package/src/utils/map.js +24 -1
  91. package/src/utils/semantics.js +53 -1
  92. package/src/utils/services.js +16 -0
  93. package/src/utils/widgets.js +20 -0
  94. package/tests/components/core/Editor.test.js +1 -1
  95. package/tests/components/core/__snapshots__/PhotoViewer.test.js.snap +18 -6
  96. package/tests/components/core/__snapshots__/Viewer.test.js.snap +15 -3
  97. package/tests/components/ui/Photo.test.js +1 -0
  98. package/tests/components/ui/__snapshots__/Map.test.js.snap +164 -0
  99. package/tests/utils/InitParameters.test.js +27 -0
  100. package/tests/utils/map.test.js +12 -0
  101. package/tests/utils/semantics.test.js +34 -5
  102. package/docs/reference/components/ui/HashTags.md +0 -15
  103. package/src/components/ui/HashTags.js +0 -98
@@ -5,7 +5,7 @@ import {
5
5
  // List of supported parameters
6
6
  const MANAGED_PARAMETERS = [
7
7
  "speed", "nav", "focus", "pic", "xyz", "map",
8
- "background", "users", "pic_score", "s"
8
+ "background", "users", "pic_score", "s", "xywh",
9
9
  ].concat(Object.values(MAP_FILTERS_JS2URL));
10
10
 
11
11
  // Events to listen on parent and PSV
@@ -45,6 +45,14 @@ export default class URLHandler extends EventTarget {
45
45
  }
46
46
  }
47
47
 
48
+ /**
49
+ * Call this function to stop listening to global events.
50
+ * @memberof Panoramax.utils.URLHandler#
51
+ */
52
+ destroy() {
53
+ window.removeEventListener("popstate", this._onURLChange);
54
+ }
55
+
48
56
  /**
49
57
  * Compute next values to insert in URL
50
58
  * @returns {object} Query parameters
package/src/utils/map.js CHANGED
@@ -95,6 +95,17 @@ export function getThumbGif(lang) {
95
95
  return thumbGif;
96
96
  }
97
97
 
98
+ export function isNullCoordinates(c) {
99
+ return (
100
+ c === null
101
+ || c === undefined
102
+ || (Array.isArray(c) && (
103
+ (c.length === 2 && c[0] === 0 && c[1] === 0)
104
+ || c.length < 2
105
+ ))
106
+ );
107
+ }
108
+
98
109
  /**
99
110
  * Is given layer a label layer.
100
111
  *
@@ -220,6 +231,11 @@ export function combineStyles(parent, options) {
220
231
  style.layers.push(capitalLayer);
221
232
  }
222
233
 
234
+ // Fix for maxzoom on IGN tiles
235
+ if(style?.sources?.plan_ign) {
236
+ style.sources.plan_ign.maxzoom = 18;
237
+ }
238
+
223
239
  return style;
224
240
  }
225
241
 
@@ -408,7 +424,14 @@ export function linkMapAndPhoto(parent) {
408
424
  }
409
425
  }
410
426
  else {
411
- parent.map.displayPictureMarker(e.detail.lon, e.detail.lat, parent.psv.getXY().x, e.detail.first);
427
+ parent.map.displayPictureMarker(
428
+ e.detail.lon,
429
+ e.detail.lat,
430
+ parent.psv.getXY().x,
431
+ e.detail.first && !isNullCoordinates(parent._initParams?.getMapPostInit()?.center), // Skip centering if precise coordinates are set from URL
432
+ e.detail.picId
433
+ );
434
+
412
435
  if(parent?.isMapWide?.()) {
413
436
  parent?.mini?.removeAttribute("collapsed");
414
437
  }
@@ -79,7 +79,7 @@ const KNOWN_PREFIXES = {
79
79
  * @private
80
80
  */
81
81
  export function decodeBasicTag(tag) {
82
- const firstEqual = (tag || "").indexOf("=");
82
+ const firstEqual = (tag || "").lastIndexOf("=");
83
83
  if(firstEqual < 0) { return null; }
84
84
  return {
85
85
  key: decodeKey(tag.substring(0, firstEqual)),
@@ -99,6 +99,7 @@ export function decodeKey(key = "") {
99
99
 
100
100
  const [, prefix, subkey, qualifies ] = match;
101
101
  return {
102
+ key: key,
102
103
  prefix: prefix || "",
103
104
  subkey,
104
105
  qualifies: decodeBasicTag(qualifies),
@@ -106,6 +107,57 @@ export function decodeKey(key = "") {
106
107
  }
107
108
 
108
109
 
110
+ /**
111
+ * Transforms a string containing a list of tags in a ready-to-use parsed object list.
112
+ * @param {string} str The string to read (each tag separated by newline `\n`)
113
+ * @returns {object[]} List of API-formatted tags
114
+ */
115
+ export function parseSemanticsString(str) {
116
+ const parsedTags = str.split("\n").map(t => {
117
+ const parts = decodeBasicTag(t);
118
+ if(
119
+ parts
120
+ && parts.key.key.length <= 256
121
+ && parts.value.length <= 2048
122
+ ) {
123
+ return { key: parts.key.key, value: parts.value };
124
+ } else { return null; }
125
+ });
126
+
127
+ if(parsedTags.findIndex(v => !v) >= 0) {
128
+ if(str.trim().length === 0 && parsedTags.length === 1) { return []; }
129
+ throw new Error("Invalid tags");
130
+ }
131
+
132
+ return parsedTags;
133
+ }
134
+
135
+
136
+ /**
137
+ * Computes the difference between two set of API tags.
138
+ * API expects a delta between old & new, so result contains only
139
+ * added and deleted tags.
140
+ * @param {object[]} prev The previous set of tags
141
+ * @param {object[]} next The new set of tags
142
+ * @returns {object[]} The new set of tags, with only added/deleted tags
143
+ */
144
+ export function computeDiffTags(prev = [], next = []) {
145
+ const res = [];
146
+
147
+ // Look for new values
148
+ (next || [])
149
+ .filter(nt => prev.find(pt => pt.key == nt.key && pt.value == nt.value) === undefined)
150
+ .forEach(t => res.push({key: t.key, value: t.value, action: "add"}));
151
+
152
+ // Look for prev values missing in next
153
+ (prev || [])
154
+ .filter(pt => next.find(nt => pt.key == nt.key && pt.value == nt.value) === undefined)
155
+ .forEach(t => res.push({key: t.key, value: t.value, action: "delete"}));
156
+
157
+ return res;
158
+ }
159
+
160
+
109
161
  /**
110
162
  * Transforms raw API semantics properties into ready-to-display container.
111
163
  * @param {object[]} tags The API semantics tags
@@ -4,6 +4,14 @@
4
4
  */
5
5
 
6
6
 
7
+ /**
8
+ * Panoramax website URL
9
+ * @returns {string} The website URL
10
+ */
11
+ export function PanoramaxWebsiteURL() {
12
+ return "https://panoramax.fr";
13
+ }
14
+
7
15
  /**
8
16
  * Panoramax Presets URL
9
17
  * @returns {string} The presets URL
@@ -12,6 +20,14 @@ export function PanoramaxPresetsURL() {
12
20
  return "https://presets.panoramax.fr";
13
21
  }
14
22
 
23
+ /**
24
+ * Panoramax meta-catalog URL
25
+ * @returns {string} The metacatalog URL
26
+ */
27
+ export function PanoramaxMetaCatalogURL() {
28
+ return "https://explore.panoramax.fr";
29
+ }
30
+
15
31
  /**
16
32
  * Temaki icons URL
17
33
  * @param {string} [iconName] The icon name to insert in URL
@@ -70,3 +70,23 @@ export function listenForMenuClosure(me, callback) {
70
70
  me._parent.legend?.addEventListener("click", () => callback());
71
71
  });
72
72
  }
73
+
74
+ /**
75
+ * Wait for parent availability
76
+ * @private
77
+ */
78
+ export function onceParentAvailable(comp) {
79
+ if(comp._parent) {
80
+ return Promise.resolve(comp._parent);
81
+ }
82
+ else {
83
+ return new Promise(resolve => {
84
+ const itv = setInterval(() => {
85
+ if(comp._parent) {
86
+ clearInterval(itv);
87
+ resolve(comp._parent);
88
+ }
89
+ }, 100);
90
+ });
91
+ }
92
+ }
@@ -15,6 +15,6 @@ describe("getClassName", () => {
15
15
 
16
16
  describe("getSubComponentsNames", () => {
17
17
  it("works", () => {
18
- expect(comp.getSubComponentsNames()).toEqual(["loader", "api", "map", "psv"]);
18
+ expect(comp.getSubComponentsNames()).toEqual(["loader", "api", "map", "psv", "grid"]);
19
19
  });
20
20
  });
@@ -10,6 +10,13 @@ exports[`_initWidgets should handle widgets if width is not small 1`] = `
10
10
  slot="top"
11
11
  />,
12
12
  ],
13
+ Array [
14
+ <pnx-annotations-switch
15
+ class="pnx-only-psv pnx-print-hidden"
16
+ size="xl"
17
+ slot="top"
18
+ />,
19
+ ],
13
20
  Array [
14
21
  <pnx-widget-zoom
15
22
  class="pnx-print-hidden"
@@ -23,12 +30,6 @@ exports[`_initWidgets should handle widgets if width is not small 1`] = `
23
30
  slot="top-left"
24
31
  />,
25
32
  ],
26
- Array [
27
- <pnx-hashtags
28
- class="pnx-only-psv pnx-print-hidden"
29
- slot="top-right"
30
- />,
31
- ],
32
33
  ],
33
34
  "results": Array [
34
35
  Object {
@@ -61,6 +62,13 @@ exports[`_initWidgets should handle widgets if width is small 1`] = `
61
62
  slot="top"
62
63
  />,
63
64
  ],
65
+ Array [
66
+ <pnx-annotations-switch
67
+ class="pnx-only-psv pnx-print-hidden"
68
+ size="xl"
69
+ slot="top"
70
+ />,
71
+ ],
64
72
  Array [
65
73
  <pnx-bottom-drawer
66
74
  slot="bottom"
@@ -78,6 +86,10 @@ exports[`_initWidgets should handle widgets if width is small 1`] = `
78
86
  "type": "return",
79
87
  "value": undefined,
80
88
  },
89
+ Object {
90
+ "type": "return",
91
+ "value": undefined,
92
+ },
81
93
  ],
82
94
  }
83
95
  `;
@@ -17,13 +17,14 @@ exports[`_initWidgets should handle widgets if width is not small 1`] = `
17
17
  />,
18
18
  ],
19
19
  Array [
20
- <pnx-hashtags
20
+ <pnx-widget-player
21
21
  class="pnx-only-psv pnx-print-hidden"
22
- slot="top-right"
22
+ size="xl"
23
+ slot="top"
23
24
  />,
24
25
  ],
25
26
  Array [
26
- <pnx-widget-player
27
+ <pnx-annotations-switch
27
28
  class="pnx-only-psv pnx-print-hidden"
28
29
  size="xl"
29
30
  slot="top"
@@ -105,6 +106,13 @@ exports[`_initWidgets should handle widgets if width is small 1`] = `
105
106
  slot="top"
106
107
  />,
107
108
  ],
109
+ Array [
110
+ <pnx-annotations-switch
111
+ class="pnx-only-psv pnx-print-hidden"
112
+ size="xl"
113
+ slot="top"
114
+ />,
115
+ ],
108
116
  Array [
109
117
  <pnx-widget-geosearch
110
118
  class="pnx-only-map pnx-print-hidden"
@@ -150,6 +158,10 @@ exports[`_initWidgets should handle widgets if width is small 1`] = `
150
158
  "type": "return",
151
159
  "value": undefined,
152
160
  },
161
+ Object {
162
+ "type": "return",
163
+ "value": undefined,
164
+ },
153
165
  ],
154
166
  }
155
167
  `;
@@ -10,6 +10,7 @@ const createParent = () => ({
10
10
  _t: { pnx: {} },
11
11
  api: {
12
12
  _getFetchOptions: jest.fn(),
13
+ _getPSVWithCredentials: () => false,
13
14
  getPictureMetadataUrl: (picId, seqId) => `https://geovisio.fr/api/collections/${seqId}/items/${picId}`
14
15
  },
15
16
  });
@@ -736,5 +736,169 @@ Array [
736
736
  "visibility",
737
737
  "none",
738
738
  ],
739
+ Array [
740
+ "geovisio_blabla_pictures",
741
+ "circle-sort-key",
742
+ Array [
743
+ "case",
744
+ Array [
745
+ "==",
746
+ Array [
747
+ "get",
748
+ "hidden",
749
+ ],
750
+ true,
751
+ ],
752
+ 90,
753
+ 10,
754
+ ],
755
+ ],
756
+ Array [
757
+ "geovisio_pictures",
758
+ "circle-sort-key",
759
+ Array [
760
+ "case",
761
+ Array [
762
+ "==",
763
+ Array [
764
+ "get",
765
+ "hidden",
766
+ ],
767
+ true,
768
+ ],
769
+ 90,
770
+ 10,
771
+ ],
772
+ ],
773
+ Array [
774
+ "geovisio_blabla_pictures_symbols",
775
+ "icon-image",
776
+ Array [
777
+ "case",
778
+ Array [
779
+ "==",
780
+ Array [
781
+ "get",
782
+ "id",
783
+ ],
784
+ undefined,
785
+ ],
786
+ "",
787
+ Array [
788
+ "==",
789
+ Array [
790
+ "get",
791
+ "type",
792
+ ],
793
+ "equirectangular",
794
+ ],
795
+ "pnx-arrow-360",
796
+ "pnx-arrow-flat",
797
+ ],
798
+ ],
799
+ Array [
800
+ "geovisio_blabla_pictures_symbols",
801
+ "symbol-sort-key",
802
+ Array [
803
+ "case",
804
+ Array [
805
+ "==",
806
+ Array [
807
+ "get",
808
+ "hidden",
809
+ ],
810
+ true,
811
+ ],
812
+ 90,
813
+ 10,
814
+ ],
815
+ ],
816
+ Array [
817
+ "geovisio_pictures_symbols",
818
+ "icon-image",
819
+ Array [
820
+ "case",
821
+ Array [
822
+ "==",
823
+ Array [
824
+ "get",
825
+ "id",
826
+ ],
827
+ undefined,
828
+ ],
829
+ "",
830
+ Array [
831
+ "==",
832
+ Array [
833
+ "get",
834
+ "type",
835
+ ],
836
+ "equirectangular",
837
+ ],
838
+ "pnx-arrow-360",
839
+ "pnx-arrow-flat",
840
+ ],
841
+ ],
842
+ Array [
843
+ "geovisio_pictures_symbols",
844
+ "symbol-sort-key",
845
+ Array [
846
+ "case",
847
+ Array [
848
+ "==",
849
+ Array [
850
+ "get",
851
+ "hidden",
852
+ ],
853
+ true,
854
+ ],
855
+ 90,
856
+ 10,
857
+ ],
858
+ ],
859
+ Array [
860
+ "geovisio_blabla_sequences",
861
+ "line-sort-key",
862
+ Array [
863
+ "case",
864
+ Array [
865
+ "==",
866
+ Array [
867
+ "get",
868
+ "hidden",
869
+ ],
870
+ true,
871
+ ],
872
+ 90,
873
+ 10,
874
+ ],
875
+ ],
876
+ Array [
877
+ "geovisio_blabla_sequences",
878
+ "line-cap",
879
+ "square",
880
+ ],
881
+ Array [
882
+ "geovisio_sequences",
883
+ "line-sort-key",
884
+ Array [
885
+ "case",
886
+ Array [
887
+ "==",
888
+ Array [
889
+ "get",
890
+ "hidden",
891
+ ],
892
+ true,
893
+ ],
894
+ 90,
895
+ 10,
896
+ ],
897
+ ],
898
+ Array [
899
+ "geovisio_sequences",
900
+ "line-cap",
901
+ "square",
902
+ ],
739
903
  ]
740
904
  `;
@@ -66,6 +66,7 @@ describe("InitParameters", () => {
66
66
  geocoder: true,
67
67
  widgets: true,
68
68
  forceFocus: true,
69
+ keyboardShortcuts: true,
69
70
  });
70
71
  expect(initParams._psvInit).toEqual({});
71
72
  expect(initParams._psvAny).toEqual({
@@ -111,11 +112,13 @@ describe("InitParameters", () => {
111
112
  geocoder: true,
112
113
  widgets: true,
113
114
  forceFocus: true,
115
+ keyboardShortcuts: true,
114
116
  });
115
117
  });
116
118
 
117
119
  it("uses browserStorage parameters if no URL parameter is available", () => {
118
120
  componentAttrs.map.raster = {};
121
+ componentAttrs.picture = undefined;
119
122
  const initParams = new InitParameters(componentAttrs, undefined, browserStorage);
120
123
  expect(initParams._mapAny).toEqual({
121
124
  theme: "qualityscore",
@@ -128,6 +131,20 @@ describe("InitParameters", () => {
128
131
  });
129
132
  });
130
133
 
134
+ it("uses browserStorage parameters if no URL parameter is available, except map coords if picture is set", () => {
135
+ componentAttrs.map.raster = {};
136
+ const initParams = new InitParameters(componentAttrs, undefined, browserStorage);
137
+ expect(initParams._mapAny).toEqual({
138
+ theme: "qualityscore",
139
+ background: "aerial",
140
+ center: [0,0],
141
+ zoom: 19,
142
+ pitch: undefined,
143
+ bearing: undefined,
144
+ users: "user1,user2",
145
+ });
146
+ });
147
+
131
148
  it("should sanitize objects correctly", () => {
132
149
  const initParams = new InitParameters(componentAttrs, urlParams);
133
150
  const obj = { a: 1, b: undefined, c: 3 };
@@ -161,6 +178,7 @@ describe("InitParameters", () => {
161
178
  geocoder: true,
162
179
  widgets: true,
163
180
  forceFocus: true,
181
+ keyboardShortcuts: true,
164
182
  });
165
183
  });
166
184
 
@@ -243,6 +261,15 @@ describe("InitParameters", () => {
243
261
  expect(initParams._mapAny.background).toBe("streets");
244
262
  expect(console.warn).toHaveBeenCalledWith("Parameter background can't be 'aerial' as no aerial imagery is available");
245
263
  });
264
+
265
+ it("should handle keyboardShortcuts=false", () => {
266
+ componentAttrs["keyboard-shortcuts"] = "false";
267
+ const initParams = new InitParameters(componentAttrs, urlParams);
268
+ expect(initParams._mapInit.keyboard).toBe(false);
269
+ expect(initParams._parentPostInit.keyboardShortcuts).toBe(false);
270
+ expect(initParams._psvInit.keyboard).toBe(false);
271
+ expect(initParams._psvInit.keyboardActions).toEqual({});
272
+ });
246
273
  });
247
274
 
248
275
  describe("getMapPositionFromString", () => {
@@ -11,6 +11,18 @@ describe("getThumbGif", () => {
11
11
  });
12
12
  });
13
13
 
14
+ describe("isNullCoordinates", () => {
15
+ it.each([
16
+ [undefined, true],
17
+ [null, true],
18
+ [[0,0], true],
19
+ [[48.7, -1.5], false],
20
+ [[], true],
21
+ ])("works with %s", (input, expected) => {
22
+ expect(map.isNullCoordinates(input)).toBe(expected);
23
+ });
24
+ });
25
+
14
26
  describe("isLabelLayer", () => {
15
27
  it("works with just text-field", () => {
16
28
  const layer = { type: "symbol", layout: { "text-field": "Label" } };
@@ -1,4 +1,6 @@
1
- import { decodeKey, decodeBasicTag, groupByPrefix } from "../../src/utils/semantics";
1
+ import {
2
+ decodeKey, decodeBasicTag, computeDiffTags, groupByPrefix
3
+ } from "../../src/utils/semantics";
2
4
 
3
5
  describe("decodeBasicTag", () => {
4
6
  test("should return null if no equal sign is present", () => {
@@ -7,14 +9,14 @@ describe("decodeBasicTag", () => {
7
9
 
8
10
  test("should correctly decode a tag with an equal sign", () => {
9
11
  expect(decodeBasicTag("key=value")).toEqual({
10
- key: { prefix: "", subkey: "key", qualifies: null },
12
+ key: { key: "key", prefix: "", subkey: "key", qualifies: null },
11
13
  value: "value"
12
14
  });
13
15
  });
14
16
 
15
17
  test("should correctly decode a tag with a prefix", () => {
16
18
  expect(decodeBasicTag("osm|key=value")).toEqual({
17
- key: { prefix: "osm", subkey: "key", qualifies: null },
19
+ key: { key: "osm|key", prefix: "osm", subkey: "key", qualifies: null },
18
20
  value: "value"
19
21
  });
20
22
  });
@@ -23,6 +25,7 @@ describe("decodeBasicTag", () => {
23
25
  describe("decodeKey", () => {
24
26
  test("should return default structure if key does not match regex", () => {
25
27
  expect(decodeKey("panoKey")).toEqual({
28
+ key: "panoKey",
26
29
  prefix: "",
27
30
  subkey: "panoKey",
28
31
  qualifies: null
@@ -31,6 +34,7 @@ describe("decodeKey", () => {
31
34
 
32
35
  test("should correctly decode a key with a prefix", () => {
33
36
  expect(decodeKey("osm|traffic_sign")).toEqual({
37
+ key: "osm|traffic_sign",
34
38
  prefix: "osm",
35
39
  subkey: "traffic_sign",
36
40
  qualifies: null
@@ -39,21 +43,46 @@ describe("decodeKey", () => {
39
43
 
40
44
  test("should correctly decode a key with qualifiers", () => {
41
45
  expect(decodeKey("detection_model[camera_mount=pole]")).toEqual({
46
+ key: "detection_model[camera_mount=pole]",
42
47
  prefix: "",
43
48
  subkey: "detection_model",
44
- qualifies: { key: { prefix: "", subkey: "camera_mount", qualifies: null }, value: "pole" }
49
+ qualifies: { key: { key: "camera_mount", prefix: "", subkey: "camera_mount", qualifies: null }, value: "pole" }
45
50
  });
46
51
  });
47
52
 
48
53
  test("should correctly decode a key with a prefix and qualifiers", () => {
49
54
  expect(decodeKey("osm|source[osm|traffic_sign=stop]")).toEqual({
55
+ key: "osm|source[osm|traffic_sign=stop]",
50
56
  prefix: "osm",
51
57
  subkey: "source",
52
- qualifies: { key: { prefix: "osm", subkey: "traffic_sign", qualifies: null }, value: "stop" }
58
+ qualifies: { key: { key: "osm|traffic_sign", prefix: "osm", subkey: "traffic_sign", qualifies: null }, value: "stop" }
53
59
  });
54
60
  });
55
61
  });
56
62
 
63
+ describe("computeDiffTags", () => {
64
+ it("should return new set of tags when no tag is missing in next", () => {
65
+ const prev = [{key: "tag1", value: "value1"}, {key: "tag2", value: "value2"}];
66
+ const next = [{key: "tag1", value: "value1"}, {key: "tag2", value: "value2"}, {key: "tag3", value: "value3"}];
67
+
68
+ expect(computeDiffTags(prev, next)).toEqual([{key: "tag3", value: "value3", action: "add"}]);
69
+ });
70
+
71
+ it("should return new set of tags with missing tags marked as delete when some tag is missing in next", () => {
72
+ const prev = [{key: "tag1", value: "value1"}, {key: "tag2", value: "value2"}];
73
+ const next = [{key: "tag1", value: "value1"}];
74
+
75
+ expect(computeDiffTags(prev, next)).toEqual([{key: "tag2", value: "value2", action: "delete"}]);
76
+ });
77
+
78
+ it("should return new set of tags with all tags marked as delete when all tags are missing in next", () => {
79
+ const prev = [{key: "tag1", value: "value1"}, {key: "tag2", value: "value2"}];
80
+ const next = [];
81
+
82
+ expect(computeDiffTags(prev, next)).toEqual([{key: "tag1", value: "value1", action: "delete"}, {key: "tag2", value: "value2", action: "delete"}]);
83
+ });
84
+ });
85
+
57
86
  describe("groupByPrefix", () => {
58
87
  test("should group tags by prefix", () => {
59
88
  const tags = [
@@ -1,15 +0,0 @@
1
- <a name="Panoramax.components.ui.HashTags"></a>
2
-
3
- ## Panoramax.components.ui.HashTags ⇐ <code>[lit.LitElement](https://lit.dev/docs/api/LitElement/)</code>
4
- **Kind**: static class of <code>Panoramax.components.ui</code>
5
- **Extends**: <code>[lit.LitElement](https://lit.dev/docs/api/LitElement/)</code>
6
- **Element**: pnx-hashtags
7
- <a name="new_Panoramax.components.ui.HashTags_new"></a>
8
-
9
- ### new HashTags()
10
- HashTags component shows the list of hashtags associated to a picture.
11
-
12
- **Example**
13
- ```html
14
- <pnx-hashtags ._parent=${viewer} />
15
- ```