@geogirafe/lib-geoportal 1.0.2250856094 → 1.0.2259547422

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.
@@ -115,7 +115,7 @@ export default class GeoGirafeApi extends GirafeHTMLElement {
115
115
  }
116
116
  injectConfigMetaTags() {
117
117
  const location = new URL(import.meta.url);
118
- if (import.meta.env.DEV) {
118
+ if (import.meta?.env?.DEV) {
119
119
  location.pathname = location.pathname.replace('/src/api', '');
120
120
  }
121
121
  const origin = `${location.origin}${location.pathname.substring(0, location.pathname.lastIndexOf('/'))}`;
@@ -18,12 +18,11 @@ class BasemapComponent extends GirafeHTMLElement {
18
18
  if (basemap.opacity == 0)
19
19
  return;
20
20
  const newBasemapProjection = basemap.projection ?? this.context.configManager.Config.map.srid;
21
- const opacityDisabled = basemap.opacity == -1;
22
21
  const activeBasemaps = [...this.state.activeBasemaps];
23
- if (opacityDisabled) {
22
+ if (basemap.opacityDisabled) {
24
23
  // Normal basemap change (no opacity basemap)
25
24
  this.state.projection = newBasemapProjection;
26
- const idx = activeBasemaps.findIndex((activeBasemap) => activeBasemap.opacity == -1);
25
+ const idx = activeBasemaps.findIndex((activeBasemap) => activeBasemap.opacityDisabled);
27
26
  if (idx >= 0) {
28
27
  activeBasemaps.splice(idx, 1);
29
28
  }
@@ -51,7 +50,7 @@ class BasemapComponent extends GirafeHTMLElement {
51
50
  }
52
51
  changeBasemapOpacity(basemap, e) {
53
52
  e.stopPropagation();
54
- if (basemap.opacity == -1) {
53
+ if (basemap.opacityDisabled) {
55
54
  console.warn(`Trying to set Opacity on Basemap '${basemap.name}' which does not allow it`);
56
55
  return;
57
56
  }
@@ -885,6 +885,12 @@ export default class MapComponent extends GirafeHTMLElement {
885
885
  throw new TypeError('Unknown basemap type');
886
886
  }
887
887
  }
888
+ // Apply default opacity
889
+ for (const basemap of basemaps) {
890
+ if (!basemap.opacityDisabled) {
891
+ this.onChangeBasemapOpacity(basemap);
892
+ }
893
+ }
888
894
  }
889
895
  /**
890
896
  * This method checks for the presence of an initial selection originating from a shared state.
@@ -8,5 +8,6 @@ declare class Basemap {
8
8
  layersList: BaseLayer[];
9
9
  constructor(elem: GMFBackgroundLayer, opacity?: number);
10
10
  get projection(): string | undefined;
11
+ get opacityDisabled(): boolean;
11
12
  }
12
13
  export default Basemap;
@@ -16,5 +16,8 @@ class Basemap {
16
16
  const layer = this.layersList.find((l) => l instanceof LayerVectorTiles);
17
17
  return layer?.projection;
18
18
  }
19
+ get opacityDisabled() {
20
+ return this.opacity === -1;
21
+ }
19
22
  }
20
23
  export default Basemap;
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "GeoGirafe PSC",
6
6
  "url": "https://doc.geomapfish.dev"
7
7
  },
8
- "version": "1.0.2250856094",
8
+ "version": "1.0.2259547422",
9
9
  "type": "module",
10
10
  "engines": {
11
11
  "node": ">=20.19.0"
@@ -1 +1 @@
1
- {"version":"1.0.2250856094", "build":"2250856094", "date":"08/01/2026"}
1
+ {"version":"1.0.2259547422", "build":"2259547422", "date":"13/01/2026"}
@@ -84,6 +84,7 @@ declare class GirafeConfig {
84
84
  };
85
85
  share?: {
86
86
  service: 'gmf' | 'geogirafe' | null;
87
+ preferNames: boolean;
87
88
  createUrl: string;
88
89
  getUrl?: string;
89
90
  };
@@ -162,6 +162,7 @@ class GirafeConfig {
162
162
  }
163
163
  return {
164
164
  service: config.share?.service ?? 'gmf',
165
+ preferNames: config.share?.preferNames ?? false,
165
166
  createUrl: config.share?.createUrl,
166
167
  getUrl: config.share?.getUrl
167
168
  };
@@ -300,7 +301,7 @@ class GirafeConfig {
300
301
  locale: config.general.locale ?? GirafeConfig.DEFAULT_LOCALE,
301
302
  // NOTE REG: Small hack specific to Vite: When running in debug mode, we force the logLevel to debug.
302
303
  // Otherwise we will always have to manually activate it.
303
- logLevel: import.meta.env.DEV ? 'debug' : (config.general.logLevel ?? 'warn')
304
+ logLevel: import.meta?.env?.DEV ? 'debug' : (config.general.logLevel ?? 'warn')
304
305
  };
305
306
  }
306
307
  initGmfOauth(config) {
@@ -61,7 +61,7 @@ ${stack}
61
61
  if (error?.stack) {
62
62
  try {
63
63
  // NOTE REG: Specific to Vite: When running in debug mode, we do not need to calculate the original stacktrace
64
- stack = import.meta.env.DEV ? error.stack : await this.getOriginalStackTrace(error);
64
+ stack = import.meta?.env?.DEV ? error.stack : await this.getOriginalStackTrace(error);
65
65
  }
66
66
  catch (e) {
67
67
  // If an error accurs during the calculation of the stacktrace, we just ignore it
package/tools/main.d.ts CHANGED
@@ -54,7 +54,7 @@ export { default as LayersConfigSerializer } from './share/serializers/layerconf
54
54
  export { default as MapPositionSerializer } from './share/serializers/mappositionserializer.js';
55
55
  export type { SharedInitialSelection } from './share/serializers/selectionserializer.js';
56
56
  export { default as SelectionSerializer } from './share/serializers/selectionserializer.js';
57
- export type { SharedFilter, SharedLayer, SharedInternalTheme, SharedInternalGroup, SharedInternalLayer, SharedExternalTheme, SharedExternalLayer } from './share/serializers/sharedtypes.js';
57
+ export type { SharedFilter, SharedLayer, SharedInternalTheme, SharedInternalGroup, SharedInternalLayer, SharedExternalTheme, SharedExternalLayer, SharedBasemap } from './share/serializers/sharedtypes.js';
58
58
  export { default as SessionManager } from './share/sessionmanager.js';
59
59
  export { default as ShareManager } from './share/sharemanager.js';
60
60
  export { default as StateSerializer } from './share/stateserializer.js';
@@ -5,6 +5,9 @@ export default class ActiveBasemapsSerializer implements IBrainSerializer<Basema
5
5
  private readonly context;
6
6
  constructor(context: IGirafeContext);
7
7
  private get state();
8
- brainSerialize(basemaps: Basemap[]): string;
8
+ private get preferNames();
9
+ brainSerialize(activeBasemaps: Basemap[]): string;
9
10
  brainDeserialize(serializedString: string): void;
11
+ private serialize;
12
+ private deserialize;
10
13
  }
@@ -1,5 +1,3 @@
1
- import { applyOpacityToLayers } from '../../utils/utils.js';
2
- import { DEFAULT_OPACITY } from '../../themes/themes-config.js';
3
1
  export default class ActiveBasemapsSerializer {
4
2
  context;
5
3
  constructor(context) {
@@ -8,31 +6,43 @@ export default class ActiveBasemapsSerializer {
8
6
  get state() {
9
7
  return this.context.stateManager.state;
10
8
  }
11
- brainSerialize(basemaps) {
12
- const serializedBasemaps = basemaps
13
- .map((basemap) => `${basemap.id.toString()}=${basemap.opacity.toString()}`)
14
- .join(';');
15
- console.debug(`Serialized Basemaps to ${serializedBasemaps}`);
16
- return serializedBasemaps;
9
+ get preferNames() {
10
+ return this.context.configManager.Config.share?.preferNames ?? false;
11
+ }
12
+ brainSerialize(activeBasemaps) {
13
+ return this.serialize(activeBasemaps);
17
14
  }
18
15
  brainDeserialize(serializedString) {
19
- console.debug(`Deserializing Basemaps from ${serializedString}`);
20
- const basemapIdsWithOpacities = Object.fromEntries(serializedString.split(';').map((idAndOpacity) => idAndOpacity.split('=').map(Number)));
21
- const basemapIds = Object.keys(basemapIdsWithOpacities).map(Number);
22
- const basemaps = Object.values(this.state.basemaps).filter((b) => basemapIds.includes(b.id));
16
+ const activeBasemaps = this.deserialize(serializedString);
17
+ this.state.activeBasemaps = activeBasemaps;
18
+ }
19
+ serialize(basemaps) {
20
+ const sharedBasemaps = [];
23
21
  for (const basemap of basemaps) {
24
- basemap.opacity = basemapIdsWithOpacities[basemap.id] ?? DEFAULT_OPACITY;
25
- applyOpacityToLayers(basemap.opacity === -1 ? 1 : basemap.opacity, basemap.layersList);
26
- }
27
- if (basemaps.length > 0) {
28
- this.state.activeBasemaps = basemaps;
22
+ const sharedBasemap = {
23
+ id: basemap.id,
24
+ name: basemap.name,
25
+ opacity: basemap.opacity
26
+ };
27
+ sharedBasemaps.push(sharedBasemap);
29
28
  }
30
- else {
31
- console.debug('No Basemaps could be deserialized => not changing state.activeBasemaps');
32
- }
33
- if (basemaps.length != basemapIds.length) {
34
- const missingIds = basemapIds.filter((id) => basemaps.findIndex((basemap) => basemap.id == id) == -1); //NOSONAR(typescript:S7754) It IS about the Indexes
35
- throw new Error(`Some basemaps could not be deserialized: ${missingIds.join(',')}.`);
29
+ return JSON.stringify(sharedBasemaps);
30
+ }
31
+ deserialize(str) {
32
+ const sharedBasemaps = JSON.parse(str);
33
+ const basemaps = [];
34
+ for (const sharedBasemap of sharedBasemaps) {
35
+ const basemap = this.preferNames
36
+ ? Object.values(this.state.basemaps).find((b) => b.name === sharedBasemap.name)
37
+ : Object.values(this.state.basemaps).find((b) => b.id === sharedBasemap.id);
38
+ if (basemap) {
39
+ basemap.opacity = sharedBasemap.opacity;
40
+ basemaps.push(basemap);
41
+ }
42
+ else {
43
+ console.error(`The basemap ${sharedBasemap.name} could not be deserialized`);
44
+ }
36
45
  }
46
+ return basemaps;
37
47
  }
38
48
  }
@@ -26,7 +26,7 @@ describe('ActiveBasemapsSerializer.serialize', () => {
26
26
  })
27
27
  ];
28
28
  const serialized = serializer.brainSerialize(basemaps);
29
- expect(serialized).toBe('1=-1');
29
+ expect(serialized).toEqual('[{"id":1,"name":"OpenStreetMap","opacity":-1}]');
30
30
  });
31
31
  });
32
32
  describe('ActiveBasemapsSerializer.deserialize', () => {
@@ -40,13 +40,14 @@ describe('ActiveBasemapsSerializer.deserialize', () => {
40
40
  basemaps.forEach((basemap) => {
41
41
  context.stateManager.state.basemaps[basemap.id] = basemap;
42
42
  });
43
- serializer.brainDeserialize('1=-1');
43
+ serializer.brainDeserialize('[{"id":1,"name":"OpenStreetMap","opacity":-1}]');
44
44
  const state = context.stateManager.state;
45
45
  expect(state.activeBasemaps).toBeInstanceOf((Array));
46
46
  expect(state.activeBasemaps[0].id).toBe(basemaps[0].id);
47
47
  expect(state.activeBasemaps[0].opacity).toBe(-1);
48
+ expect(state.activeBasemaps[0].opacityDisabled).toBeTruthy();
48
49
  });
49
- it('deserializing with non existing IDs should throw an Error', () => {
50
+ it('should deserialize a valid basemap even if another one cannot be found', () => {
50
51
  const basemaps = [
51
52
  new Basemap({
52
53
  id: 1,
@@ -56,6 +57,87 @@ describe('ActiveBasemapsSerializer.deserialize', () => {
56
57
  basemaps.forEach((basemap) => {
57
58
  context.stateManager.state.basemaps[basemap.id] = basemap;
58
59
  });
59
- expect(() => serializer.brainDeserialize('1=-1;2=0;42=0.45')).toThrow(new Error(`Some basemaps could not be deserialized: 2,42.`));
60
+ serializer.brainDeserialize('[{"id":1,"name":"OpenStreetMap","opacity":-1},{"id":2,"name":"NotExisting","opacity":0.5}]');
61
+ const state = context.stateManager.state;
62
+ expect(state.activeBasemaps).toBeInstanceOf((Array));
63
+ expect(state.activeBasemaps[0].id).toBe(basemaps[0].id);
64
+ expect(state.activeBasemaps[0].opacity).toBe(-1);
65
+ expect(state.activeBasemaps[0].opacityDisabled).toBeTruthy();
66
+ });
67
+ it('should deserialize a valid basemap with the right opacity', () => {
68
+ const basemaps = [
69
+ new Basemap({
70
+ id: 1,
71
+ name: 'OpenStreetMap'
72
+ })
73
+ ];
74
+ basemaps.forEach((basemap) => {
75
+ context.stateManager.state.basemaps[basemap.id] = basemap;
76
+ });
77
+ serializer.brainDeserialize('[{"id":1,"name":"OpenStreetMap","opacity":0.45}]');
78
+ const state = context.stateManager.state;
79
+ expect(state.activeBasemaps).toBeInstanceOf((Array));
80
+ expect(state.activeBasemaps[0].id).toBe(basemaps[0].id);
81
+ expect(state.activeBasemaps[0].opacity).toBe(0.45);
82
+ expect(state.activeBasemaps[0].opacityDisabled).toBeFalsy();
83
+ });
84
+ });
85
+ describe('ActiveBasemapsSerializer.deserialize (preferNames)', () => {
86
+ beforeAll(() => {
87
+ context.configManager.Config.share.preferNames = true;
88
+ });
89
+ afterAll(() => {
90
+ context.configManager.Config.share.preferNames = false;
91
+ });
92
+ it('should deserialize a valid basemap id and set it as activeBasemap (preferNames)', () => {
93
+ const basemaps = [
94
+ new Basemap({
95
+ id: 1,
96
+ name: 'OpenStreetMap'
97
+ })
98
+ ];
99
+ basemaps.forEach((basemap) => {
100
+ context.stateManager.state.basemaps[basemap.id] = basemap;
101
+ });
102
+ serializer.brainDeserialize('[{"id":999,"name":"OpenStreetMap","opacity":-1}]');
103
+ const state = context.stateManager.state;
104
+ expect(state.activeBasemaps).toBeInstanceOf((Array));
105
+ expect(state.activeBasemaps[0].id).toBe(basemaps[0].id);
106
+ expect(state.activeBasemaps[0].opacity).toBe(-1);
107
+ expect(state.activeBasemaps[0].opacityDisabled).toBeTruthy();
108
+ });
109
+ it('should deserialize a valid basemap even if another one cannot be found (preferNames)', () => {
110
+ const basemaps = [
111
+ new Basemap({
112
+ id: 1,
113
+ name: 'OpenStreetMap'
114
+ })
115
+ ];
116
+ basemaps.forEach((basemap) => {
117
+ context.stateManager.state.basemaps[basemap.id] = basemap;
118
+ });
119
+ serializer.brainDeserialize('[{"id":999,"name":"OpenStreetMap","opacity":-1},{"id":888,"name":"NotExisting","opacity":0.5}]');
120
+ const state = context.stateManager.state;
121
+ expect(state.activeBasemaps).toBeInstanceOf((Array));
122
+ expect(state.activeBasemaps[0].id).toBe(basemaps[0].id);
123
+ expect(state.activeBasemaps[0].opacity).toBe(-1);
124
+ expect(state.activeBasemaps[0].opacityDisabled).toBeTruthy();
125
+ });
126
+ it('should deserialize a valid basemap with the right opacity (preferNames)', () => {
127
+ const basemaps = [
128
+ new Basemap({
129
+ id: 1,
130
+ name: 'OpenStreetMap'
131
+ })
132
+ ];
133
+ basemaps.forEach((basemap) => {
134
+ context.stateManager.state.basemaps[basemap.id] = basemap;
135
+ });
136
+ serializer.brainDeserialize('[{"id":999,"name":"OpenStreetMap","opacity":0.45}]');
137
+ const state = context.stateManager.state;
138
+ expect(state.activeBasemaps).toBeInstanceOf((Array));
139
+ expect(state.activeBasemaps[0].id).toBe(basemaps[0].id);
140
+ expect(state.activeBasemaps[0].opacity).toBe(0.45);
141
+ expect(state.activeBasemaps[0].opacityDisabled).toBeFalsy();
60
142
  });
61
143
  });
@@ -7,6 +7,7 @@ export default class LayersConfigSerializer implements IBrainSerializer<LayersCo
7
7
  private readonly context;
8
8
  constructor(context: IGirafeContext);
9
9
  private get state();
10
+ private get preferNames();
10
11
  brainSerialize(layersConfig: LayersConfig): string;
11
12
  protected serialize(layers: BaseLayer[]): string;
12
13
  brainDeserialize(str: string): void;
@@ -16,6 +17,7 @@ export default class LayersConfigSerializer implements IBrainSerializer<LayersCo
16
17
  private getExternalSerializedTheme;
17
18
  private getExternalSerializedLayer;
18
19
  private getInternalSerializedGroupOrTheme;
20
+ private getInternalLayerType;
19
21
  private getInternalSerializedLayer;
20
22
  private getDeserializedLayerTree;
21
23
  private deserializeInternalObject;
@@ -24,8 +26,11 @@ export default class LayersConfigSerializer implements IBrainSerializer<LayersCo
24
26
  private deserializeInternalLayer;
25
27
  private checkUnknownLayers;
26
28
  private removeUnnecessaryChilds;
29
+ private reorderChildren;
27
30
  private findBaseLayerById;
28
- private findLayerRecursive;
31
+ private findLayerRecursiveById;
32
+ private findBaseLayerByName;
33
+ private findLayerRecursiveByName;
29
34
  private deserializeExternalObject;
30
35
  private deserializeExternalTheme;
31
36
  private deserializeExternalWmsLayer;
@@ -7,6 +7,11 @@ import ThemeLayerExternal from '../../../models/layers/themelayerexternal.js';
7
7
  import LayerWmsExternal from '../../../models/layers/layerwmsexternal.js';
8
8
  import LayerWmtsExternal from '../../../models/layers/layerwmtsexternal.js';
9
9
  import ServerOgc from '../../../models/serverogc.js';
10
+ import LayerWmts from '../../../models/layers/layerwmts.js';
11
+ import LayerVectorTiles from '../../../models/layers/layervectortiles.js';
12
+ import LayerCog from '../../../models/layers/layercog.js';
13
+ import LayerOsm from '../../../models/layers/layerosm.js';
14
+ import LayerXYZ from '../../../models/layers/layerxyz.js';
10
15
  export default class LayersConfigSerializer {
11
16
  context;
12
17
  constructor(context) {
@@ -15,6 +20,9 @@ export default class LayersConfigSerializer {
15
20
  get state() {
16
21
  return this.context.stateManager.state;
17
22
  }
23
+ get preferNames() {
24
+ return this.context.configManager.Config.share?.preferNames ?? false;
25
+ }
18
26
  brainSerialize(layersConfig) {
19
27
  return this.serialize(layersConfig.layersList);
20
28
  }
@@ -104,6 +112,7 @@ export default class LayersConfigSerializer {
104
112
  const originalTheme = this.context.themesHelper.findBaseLayerById(group.id);
105
113
  const sharedChildren = [];
106
114
  const removedChildrenIds = [];
115
+ const removedChildrenNames = [];
107
116
  for (const originalChild of originalTheme.children) {
108
117
  const index = group.children.findIndex((el) => el.id === originalChild.id);
109
118
  if (index >= 0) {
@@ -114,6 +123,7 @@ export default class LayersConfigSerializer {
114
123
  else {
115
124
  // Element is not in the list any more, and therefore should not be shared or restored
116
125
  removedChildrenIds.push(originalChild.id);
126
+ removedChildrenNames.push(originalChild.name);
117
127
  }
118
128
  }
119
129
  return {
@@ -123,9 +133,39 @@ export default class LayersConfigSerializer {
123
133
  isExpanded: Number(group.isExpanded),
124
134
  timeRestriction: isTimeAwareLayer(group) ? group.timeRestriction : undefined,
125
135
  children: sharedChildren,
126
- excludedChildrenIds: removedChildrenIds
136
+ excludedChildrenIds: removedChildrenIds,
137
+ name: group.name,
138
+ type: this.getInternalLayerType(group),
139
+ excludedChildrenNames: removedChildrenNames
127
140
  };
128
141
  }
142
+ getInternalLayerType(layer) {
143
+ if (layer instanceof ThemeLayer) {
144
+ return 'theme';
145
+ }
146
+ if (layer instanceof GroupLayer) {
147
+ return 'group';
148
+ }
149
+ if (layer instanceof LayerWms) {
150
+ return 'wms';
151
+ }
152
+ if (layer instanceof LayerWmts) {
153
+ return 'wmts';
154
+ }
155
+ if (layer instanceof LayerVectorTiles) {
156
+ return 'vt';
157
+ }
158
+ if (layer instanceof LayerCog) {
159
+ return 'cog';
160
+ }
161
+ if (layer instanceof LayerOsm) {
162
+ return 'osm';
163
+ }
164
+ if (layer instanceof LayerXYZ) {
165
+ return 'xyz';
166
+ }
167
+ throw new Error(`The layer ${layer.name} has an unknown type and cannot be shared.`);
168
+ }
129
169
  getInternalSerializedLayer(layer) {
130
170
  return {
131
171
  id: layer.id,
@@ -137,7 +177,9 @@ export default class LayersConfigSerializer {
137
177
  filter: layer instanceof LayerWms && this.context.layerManager.isLayerWithFilter(layer)
138
178
  ? layer.filter
139
179
  : undefined,
140
- timeRestriction: isTimeAwareLayer(layer) ? layer.timeRestriction : undefined
180
+ timeRestriction: isTimeAwareLayer(layer) ? layer.timeRestriction : undefined,
181
+ name: layer.name,
182
+ type: this.getInternalLayerType(layer)
141
183
  };
142
184
  }
143
185
  getDeserializedLayerTree(sharedLayers) {
@@ -146,7 +188,12 @@ export default class LayersConfigSerializer {
146
188
  let layer;
147
189
  if ('id' in sharedLayer) {
148
190
  // Id attribute found => we are on an internal layer
149
- layer = this.findBaseLayerById(sharedLayer.id);
191
+ if (this.preferNames && sharedLayer.name) {
192
+ layer = this.findBaseLayerByName(sharedLayer.name, sharedLayer.type);
193
+ }
194
+ else {
195
+ layer = this.findBaseLayerById(sharedLayer.id);
196
+ }
150
197
  if (layer) {
151
198
  this.deserializeInternalObject(layer, sharedLayer);
152
199
  }
@@ -228,7 +275,9 @@ export default class LayersConfigSerializer {
228
275
  let reorder = false;
229
276
  for (let i = originalLayer.children.length - 1; i >= 0; i--) {
230
277
  const child = originalLayer.children[i];
231
- const serializedChild = sharedLayer.children.find((l) => l.id == child.id);
278
+ const serializedChild = this.preferNames
279
+ ? sharedLayer.children.find((l) => l.name == child.name)
280
+ : sharedLayer.children.find((l) => l.id == child.id);
232
281
  if (serializedChild) {
233
282
  this.deserializeInternalObject(child, serializedChild);
234
283
  }
@@ -236,7 +285,9 @@ export default class LayersConfigSerializer {
236
285
  // This child exists in the original layer, but not in the shared state.
237
286
  // => If it is present in the x list, it was explicitely removed
238
287
  // And we can remove it from the current object
239
- const explicitlyRemoved = sharedLayer.excludedChildrenIds.find((id) => id == child.id);
288
+ const explicitlyRemoved = this.preferNames
289
+ ? sharedLayer.excludedChildrenNames.find((name) => name == child.name)
290
+ : sharedLayer.excludedChildrenIds.find((id) => id == child.id);
240
291
  if (explicitlyRemoved) {
241
292
  originalLayer.children.splice(i, 1);
242
293
  console.debug(`Layer ${child.name} was removed from initial state`);
@@ -253,23 +304,26 @@ export default class LayersConfigSerializer {
253
304
  }
254
305
  }
255
306
  if (reorder) {
256
- console.debug(`Reordering childs for layer ${originalLayer.name}`);
257
- let order = 1;
258
- for (const child of originalLayer.children) {
259
- child.order = order++;
260
- }
307
+ this.reorderChildren(originalLayer);
308
+ }
309
+ }
310
+ reorderChildren(originalLayer) {
311
+ console.debug(`Reordering childs for layer ${originalLayer.name}`);
312
+ let order = 1;
313
+ for (const child of originalLayer.children) {
314
+ child.order = order++;
261
315
  }
262
316
  }
263
317
  findBaseLayerById(layerId) {
264
318
  for (const theme of Object.values(this.state.themes._allThemes)) {
265
- const layer = this.findLayerRecursive(theme, layerId);
319
+ const layer = this.findLayerRecursiveById(theme, layerId);
266
320
  if (layer) {
267
321
  return layer;
268
322
  }
269
323
  }
270
324
  return null;
271
325
  }
272
- findLayerRecursive(layer, layerId) {
326
+ findLayerRecursiveById(layer, layerId) {
273
327
  if (layer.id === layerId) {
274
328
  // When deserializing the layer, we clone it,
275
329
  // otherwise the following operation will also
@@ -280,7 +334,35 @@ export default class LayersConfigSerializer {
280
334
  // Else, we call recursively on the children
281
335
  if (layer instanceof GroupLayer || layer instanceof ThemeLayer) {
282
336
  for (const childLayer of layer.children) {
283
- const foundChild = this.findLayerRecursive(childLayer, layerId);
337
+ const foundChild = this.findLayerRecursiveById(childLayer, layerId);
338
+ if (foundChild) {
339
+ return foundChild;
340
+ }
341
+ }
342
+ }
343
+ return null;
344
+ }
345
+ findBaseLayerByName(layerName, layerType) {
346
+ for (const theme of Object.values(this.state.themes._allThemes)) {
347
+ const layer = this.findLayerRecursiveByName(theme, layerName, layerType);
348
+ if (layer) {
349
+ return layer;
350
+ }
351
+ }
352
+ return null;
353
+ }
354
+ findLayerRecursiveByName(layer, layerName, layerType) {
355
+ if (layer.name === layerName && this.getInternalLayerType(layer) === layerType) {
356
+ // When deserializing the layer, we clone it,
357
+ // otherwise the following operation will also
358
+ // affect the layer referenced in other themes
359
+ const foundLayer = layer.clone();
360
+ return foundLayer;
361
+ }
362
+ // Else, we call recursively on the children
363
+ if (layer instanceof GroupLayer || layer instanceof ThemeLayer) {
364
+ for (const childLayer of layer.children) {
365
+ const foundChild = this.findLayerRecursiveByName(childLayer, layerName, layerType);
284
366
  if (foundChild) {
285
367
  return foundChild;
286
368
  }
@@ -74,10 +74,16 @@ function getTestData(options = {}) {
74
74
  checked: options.isGroupChecked ? 1 : 0,
75
75
  isExpanded: options.isGroupExpanded ? 1 : 0,
76
76
  children: [],
77
- excludedChildrenIds: options.addSecondMissingLayer ? [22] : []
77
+ excludedChildrenIds: options.addSecondMissingLayer ? [22] : [],
78
+ name: 'Group 1',
79
+ type: 'group',
80
+ excludedChildrenNames: options.addSecondMissingLayer ? ['Layer WMTS 2'] : []
78
81
  }
79
82
  ],
80
- excludedChildrenIds: options.addSecondMissingGroup ? [12] : []
83
+ excludedChildrenIds: options.addSecondMissingGroup ? [12] : [],
84
+ name: 'test-theme',
85
+ type: 'theme',
86
+ excludedChildrenNames: options.addSecondMissingGroup ? ['Group 2'] : []
81
87
  }
82
88
  ];
83
89
  if (options.addWmtsLayer) {
@@ -87,7 +93,9 @@ function getTestData(options = {}) {
87
93
  checked: 0,
88
94
  isExpanded: 0,
89
95
  opacity: options.opacity ?? 1,
90
- swiped: options.swiped ?? 'no'
96
+ swiped: options.swiped ?? 'no',
97
+ name: 'Layer WMTS 1',
98
+ type: 'wmts'
91
99
  });
92
100
  }
93
101
  return {
@@ -269,6 +277,70 @@ describe('LayersConfigSerializer.deserialize', () => {
269
277
  expect(serialized).toEqual(data.controlValue);
270
278
  });
271
279
  });
280
+ describe('LayersConfigSerializer.deserialize (preferNames)', () => {
281
+ beforeAll(() => {
282
+ context.configManager.Config.share.preferNames = true;
283
+ });
284
+ afterAll(() => {
285
+ context.configManager.Config.share.preferNames = false;
286
+ });
287
+ it('should return serialized data for a GroupLayer (id, order) (preferNames)', () => {
288
+ const data = getTestData();
289
+ serializer.brainDeserialize(data.controlValue);
290
+ const layersConfig = context.stateManager.state.layers;
291
+ const serialized = serializer.brainSerialize(layersConfig);
292
+ expect(serialized).toEqual(data.controlValue);
293
+ });
294
+ it('should return serialized data for a GroupLayer (isExpanded) (preferNames)', () => {
295
+ const data = getTestData({ isGroupExpanded: true });
296
+ serializer.brainDeserialize(data.controlValue);
297
+ const layersConfig = context.stateManager.state.layers;
298
+ const serialized = serializer.brainSerialize(layersConfig);
299
+ expect(serialized).toEqual(data.controlValue);
300
+ });
301
+ it('should return serialized data for a GroupLayer with children (preferNames)', () => {
302
+ const data = getTestData({ addWmtsLayer: true });
303
+ serializer.brainDeserialize(data.controlValue);
304
+ const layersConfig = context.stateManager.state.layers;
305
+ const serialized = serializer.brainSerialize(layersConfig);
306
+ expect(serialized).toEqual(data.controlValue);
307
+ });
308
+ it('should return serialized data for a GroupLayer with children (opacity) (preferNames)', () => {
309
+ const data = getTestData({ addWmtsLayer: true, opacity: 0.5 });
310
+ serializer.brainDeserialize(data.controlValue);
311
+ const layersConfig = context.stateManager.state.layers;
312
+ const serialized = serializer.brainSerialize(layersConfig);
313
+ expect(serialized).toEqual(data.controlValue);
314
+ });
315
+ it('should return serialized data for a GroupLayer with children (swiped left) (preferNames)', () => {
316
+ const data = getTestData({ addWmtsLayer: true, swiped: 'left' });
317
+ serializer.brainDeserialize(data.controlValue);
318
+ const layersConfig = context.stateManager.state.layers;
319
+ const serialized = serializer.brainSerialize(layersConfig);
320
+ expect(serialized).toEqual(data.controlValue);
321
+ });
322
+ it('should return serialized data for a GroupLayer with children (swiped right) (preferNames)', () => {
323
+ const data = getTestData({ addWmtsLayer: true, swiped: 'left' });
324
+ serializer.brainDeserialize(data.controlValue);
325
+ const layersConfig = context.stateManager.state.layers;
326
+ const serialized = serializer.brainSerialize(layersConfig);
327
+ expect(serialized).toEqual(data.controlValue);
328
+ });
329
+ it('should serialize a missing original group correctly (explicitely removed) (preferNames)', () => {
330
+ const data = getTestData({ addSecondMissingGroup: true });
331
+ serializer.brainDeserialize(data.controlValue);
332
+ const layersConfig = context.stateManager.state.layers;
333
+ const serialized = serializer.brainSerialize(layersConfig);
334
+ expect(serialized).toEqual(data.controlValue);
335
+ });
336
+ it('should serialize a missing original layer correctly (explicitely removed) (preferNames)', () => {
337
+ const data = getTestData({ addWmtsLayer: true, addSecondMissingLayer: true });
338
+ serializer.brainDeserialize(data.controlValue);
339
+ const layersConfig = context.stateManager.state.layers;
340
+ const serialized = serializer.brainSerialize(layersConfig);
341
+ expect(serialized).toEqual(data.controlValue);
342
+ });
343
+ });
272
344
  describe('LayersConfigSerializer.serialize external', () => {
273
345
  it('should return serialized data for an external theme', () => {
274
346
  const data = getExternalTestData();
@@ -20,6 +20,16 @@ export type SharedInternalTheme = {
20
20
  * Or if it was explicitly removed from the user, and then we won't have to display it again
21
21
  */
22
22
  excludedChildrenIds: number[];
23
+ /**
24
+ * When configuring the share with "preferNames=true", the name of the layer will be used to generate the sahred state
25
+ * instead of the ID. But in this case, we also have to share the type os the layer, because tha layer name can be
26
+ * the same for a them, a group, a WMS-Layer, a WMTSLayer, etc...
27
+ * Also note that the "excludedChildrenNames" options has some limitations : if we have 2 layers of different types with the same name
28
+ * And if one of it is exceluded, both will be excluded.
29
+ */
30
+ name: string;
31
+ type: string;
32
+ excludedChildrenNames: string[];
23
33
  };
24
34
  export type SharedInternalGroup = {
25
35
  id: number;
@@ -29,6 +39,9 @@ export type SharedInternalGroup = {
29
39
  timeRestriction?: string;
30
40
  children: SharedInternalLayer[];
31
41
  excludedChildrenIds: number[];
42
+ name: string;
43
+ type: string;
44
+ excludedChildrenNames: string[];
32
45
  };
33
46
  export type SharedInternalLayer = {
34
47
  id: number;
@@ -39,6 +52,8 @@ export type SharedInternalLayer = {
39
52
  opacity?: number;
40
53
  swiped?: 'left' | 'right' | 'no';
41
54
  filter?: SharedFilter;
55
+ name: string;
56
+ type: string;
42
57
  };
43
58
  export type SharedExternalTheme = {
44
59
  name: string;
@@ -64,3 +79,8 @@ export type SharedExternalLayer = {
64
79
  opacity?: number;
65
80
  swiped?: 'left' | 'right' | 'no';
66
81
  };
82
+ export type SharedBasemap = {
83
+ id: number;
84
+ name: string;
85
+ opacity: number;
86
+ };