@geogirafe/lib-geoportal 1.0.2251126674 → 1.0.2259617981
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.
- package/components/basemap/component.js +3 -4
- package/components/extlayers/component.js +2 -2
- package/components/map/component.js +6 -0
- package/components/treeview/treeviewtheme/component.js +2 -4
- package/models/basemaps/basemap.d.ts +1 -0
- package/models/basemaps/basemap.js +3 -0
- package/models/layers/layerwms.js +10 -0
- package/models/layers/layerwmts.js +10 -0
- package/package.json +1 -1
- package/templates/public/about.json +1 -1
- package/tools/configuration/girafeconfig.d.ts +1 -0
- package/tools/configuration/girafeconfig.js +1 -0
- package/tools/main.d.ts +3 -3
- package/tools/main.js +2 -2
- package/tools/share/serializers/activebasemapsserializer.d.ts +4 -1
- package/tools/share/serializers/activebasemapsserializer.js +33 -23
- package/tools/share/serializers/activebasemapsserializer.spec.js +86 -4
- package/tools/share/serializers/layerconfigserializer.d.ts +6 -1
- package/tools/share/serializers/layerconfigserializer.js +95 -13
- package/tools/share/serializers/layerconfigserializer.spec.js +75 -3
- package/tools/share/serializers/sharedtypes.d.ts +20 -0
- package/tools/state/brain/brain.base.spec.js +32 -3
- package/tools/state/brain/brain.js +33 -36
- package/tools/state/brain/decorators.d.ts +11 -0
- package/tools/state/brain/decorators.js +14 -0
- package/tools/state/brain/tools.d.ts +9 -1
- package/tools/state/brain/tools.js +23 -11
- package/tools/state/statemanager.spec.js +1 -1
|
@@ -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.
|
|
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.
|
|
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
|
}
|
|
@@ -15,7 +15,7 @@ class ExternalLayersComponent extends GirafeHTMLElement {
|
|
|
15
15
|
</style><style>
|
|
16
16
|
#panel{background:var(--bkg-color);height:100%;color:var(--text-color);flex-direction:column;padding:0 1rem;display:flex}#content{flex-direction:column;flex-grow:1;margin:0;display:flex}header{background-color:var(--bkg-color);margin:0;position:sticky;top:-5px}h4{margin-bottom:.5rem}ul{margin-top:0;margin-left:0;padding-left:2rem;line-height:1.3rem;list-style-type:disc}.clearable-input{background-color:var(--bkg-color);border:1px solid #cfd6dd;border-radius:4px;flex-direction:row;margin-bottom:.1rem;display:flex}.scan-button{float:right}.layers{border-top:1px solid #ccc;flex-direction:column;flex-grow:1;margin-top:1rem;padding-top:.5rem;display:flex;overflow:hidden}ul.results{border:none;margin-bottom:.5rem;padding:0;list-style:none;overflow-y:auto}.result{height:1.6rem;display:flex}.result>button>span{text-align:left;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}#panel>div{padding:1rem}#layers li{margin-top:.3rem;margin-bottom:.3rem}#layers li button{text-align:left;overflow-wrap:anywhere;text-wrap-style:balance}#file-selector{display:grid}#file,#file-label{grid-area:1/1}#file{opacity:0;z-index:2;cursor:pointer}#file::file-selector-button{cursor:pointer}#loading-icon{flex-grow:1;align-self:center;width:3rem}.title{margin-top:0}.filter-buttons{flex-direction:row;margin:2px;display:flex}.filter-buttons span{flex-grow:1;align-self:center}.clearable-input-field{width:90%;color:var(--text-color);background:0 0;border:0;outline:none;flex:auto;padding:0;font-size:1rem}.filter-icon,.delete-icon{width:16px;color:var(--text-color);padding:0 .5rem}.delete-button{cursor:pointer;background-color:#0000;border:none;padding:0}
|
|
17
17
|
</style>
|
|
18
|
-
<div id="panel"><header class="gg-tabs"><button class="${this.selectedTab === 'wms_wmts' ? 'gg-tab active' : 'gg-tab'}" onclick="${() => this.setSelectedTab('wms_wmts')}" i18n="wms_wmts"></button> <button class="${this.selectedTab === 'file' ? 'gg-tab active' : 'gg-tab'}" onclick="${() => this.setSelectedTab('file')}" i18n="local file"></button></header><section class="${(this.selectedTab === 'wms_wmts') ? 'group' : 'hidden'}"><h4 class="title" i18n="Suggestions" ?hidden="${this.hasPredefinedWmsWmtsSources}">Suggestions</h4><ul class="suggestions" ?hidden="${this.hasPredefinedWmsWmtsSources}">${this.predefinedWmsWmtsSources.map(source => uHtmlFor(source, source.label) `<li><a href="javascript:void(0)" onclick="${() => this.scanSource(source.url, source.type)}">${source.label}</a></li>`)}</ul><h4 class="title" i18n="Custom service URL">Custom service URL</h4><div class="clearable-input"><input id="url" class="gg-input clearable-input-field" oninput="${() => this.typeUrl()}"><br><button id="clear-url" class="delete-button hidden" onclick="${() => this.clearUrl()}"><img class="delete-icon" alt="delete-button-icon" tip="Reset filter" i18n="Reset filter" src="icons/trash.svg"></button></div><button i18n="Scan Source" class="gg-button scan-button" onclick="${() => this.scanSource()}">Scan Source</button></section><section class="${(this.selectedTab === 'file') ? 'group' : 'hidden'}"><h4 class="title" i18n="Choose local file">Choose local file</h4><div id="file-selector"><label for="file" id="file-label"><button class="gg-button" i18n="Select File"></button> <span>${this.fileDescription}</span></label> <input type="file" accept=".gpx,.kml,.geojson,.json" id="file" onchange="${() => this.render()}"><br></div><button id="load" i18n="Load File" class="gg-button" onclick="${() => this.loadFile()}"></button></section><section class="${(this.selectedTab === 'wms_wmts' && this.externalLayers.length > 0) || this.loading ? 'layers' : 'hidden'}"><h4 class="title" i18n="Layers (from Capabilities)">Layers (from Capabilities)</h4><div class="clearable-input"><img class="filter-icon" alt="filter-button-icon" src="icons/filter.svg"> <input id="layer-search-field" class="gg-input clearable-input-field" placeholder="${this.context.i18nManager.getTranslation('Filter external layers...')}" i18n="Filter external layers..." autocomplete="off" autocorrect="off" oninput="${(e) => this.filterLayers(e.target.value)}"> <button class="${this.isFiltered ? 'delete-button' : 'hidden'}" onclick="${() => this.clearFilter()}"><img class="delete-icon" alt="delete-button-icon" tip="Reset filter" i18n="Reset filter" src="icons/trash.svg"></button></div><div class="filter-buttons"><span>${Object.values(this.externalLayers).filter(layer => layer.isSelected).length.toString()} ${this.context.i18nManager.getTranslation('layers selected')} </span><button class="gg-button" tip="Select all filtered layers" onclick="${() => this.selectVisible()}">+</button> <button class="gg-button" tip="Unselect all filtered layers" onclick="${() => this.deselectVisible()}">-</button> <button class="gg-button" tip="Select none" onclick="${() => this.deselectAll()}">--</button></div><img id="loading-icon" alt="loading-icon" src="icons/loading.svg" class="${this.loading ? 'gg-spin' : 'hidden'}"><ul class="results">${this.filteredLayers.map(layer => uHtmlFor(layer, layer.treeItemId) `<li class="result"><button class="${layer.isSelected ? 'gg-icon-button gg-small gg-selected' : 'gg-icon-button gg-small gg-opacity'}" tip="Activate / Deactivate" onclick="${() => this.selectLayer(layer)}"><img class="${layer.isSelected ? 'gg-checkbox' : 'hidden'}" alt="Expand/Collapse button" src="icons/checked-full.svg"> <img class="${!layer.isSelected ? 'gg-checkbox' : 'hidden'}" alt="Expand/Collapse button" src="icons/checked-no.svg"></button> <button class="${layer.isSelected ? 'gg-icon-button gg-small gg-selected' : 'gg-icon-button gg-small gg-opacity'}" onclick="${() => this.selectLayer(layer)}"><span i18n="${layer.name}" class="gg-tree-label">${layer.name}</span></button></li>`)}</ul><button id="add" tip="Add selected" i18n="Add selected" class="gg-button" onclick="${() => this.addSelectedLayers()}">Add selected</button></section></div>`;
|
|
18
|
+
<div id="panel"><header class="gg-tabs"><button class="${this.selectedTab === 'wms_wmts' ? 'gg-tab active' : 'gg-tab'}" onclick="${() => this.setSelectedTab('wms_wmts')}" i18n="wms_wmts"></button> <button class="${this.selectedTab === 'file' ? 'gg-tab active' : 'gg-tab'}" onclick="${() => this.setSelectedTab('file')}" i18n="local file"></button></header><section class="${(this.selectedTab === 'wms_wmts') ? 'group' : 'hidden'}"><h4 class="title" i18n="Suggestions" ?hidden="${!this.hasPredefinedWmsWmtsSources}">Suggestions</h4><ul class="suggestions" ?hidden="${!this.hasPredefinedWmsWmtsSources}">${this.predefinedWmsWmtsSources.map(source => uHtmlFor(source, source.label) `<li><a href="javascript:void(0)" onclick="${() => this.scanSource(source.url, source.type)}">${source.label}</a></li>`)}</ul><h4 class="title" i18n="Custom service URL">Custom service URL</h4><div class="clearable-input"><input id="url" class="gg-input clearable-input-field" oninput="${() => this.typeUrl()}"><br><button id="clear-url" class="delete-button hidden" onclick="${() => this.clearUrl()}"><img class="delete-icon" alt="delete-button-icon" tip="Reset filter" i18n="Reset filter" src="icons/trash.svg"></button></div><button i18n="Scan Source" class="gg-button scan-button" onclick="${() => this.scanSource()}">Scan Source</button></section><section class="${(this.selectedTab === 'file') ? 'group' : 'hidden'}"><h4 class="title" i18n="Choose local file">Choose local file</h4><div id="file-selector"><label for="file" id="file-label"><button class="gg-button" i18n="Select File"></button> <span>${this.fileDescription}</span></label> <input type="file" accept=".gpx,.kml,.geojson,.json" id="file" onchange="${() => this.render()}"><br></div><button id="load" i18n="Load File" class="gg-button" onclick="${() => this.loadFile()}"></button></section><section class="${(this.selectedTab === 'wms_wmts' && this.externalLayers.length > 0) || this.loading ? 'layers' : 'hidden'}"><h4 class="title" i18n="Layers (from Capabilities)">Layers (from Capabilities)</h4><div class="clearable-input"><img class="filter-icon" alt="filter-button-icon" src="icons/filter.svg"> <input id="layer-search-field" class="gg-input clearable-input-field" placeholder="${this.context.i18nManager.getTranslation('Filter external layers...')}" i18n="Filter external layers..." autocomplete="off" autocorrect="off" oninput="${(e) => this.filterLayers(e.target.value)}"> <button class="${this.isFiltered ? 'delete-button' : 'hidden'}" onclick="${() => this.clearFilter()}"><img class="delete-icon" alt="delete-button-icon" tip="Reset filter" i18n="Reset filter" src="icons/trash.svg"></button></div><div class="filter-buttons"><span>${Object.values(this.externalLayers).filter(layer => layer.isSelected).length.toString()} ${this.context.i18nManager.getTranslation('layers selected')} </span><button class="gg-button" tip="Select all filtered layers" onclick="${() => this.selectVisible()}">+</button> <button class="gg-button" tip="Unselect all filtered layers" onclick="${() => this.deselectVisible()}">-</button> <button class="gg-button" tip="Select none" onclick="${() => this.deselectAll()}">--</button></div><img id="loading-icon" alt="loading-icon" src="icons/loading.svg" class="${this.loading ? 'gg-spin' : 'hidden'}"><ul class="results">${this.filteredLayers.map(layer => uHtmlFor(layer, layer.treeItemId) `<li class="result"><button class="${layer.isSelected ? 'gg-icon-button gg-small gg-selected' : 'gg-icon-button gg-small gg-opacity'}" tip="Activate / Deactivate" onclick="${() => this.selectLayer(layer)}"><img class="${layer.isSelected ? 'gg-checkbox' : 'hidden'}" alt="Expand/Collapse button" src="icons/checked-full.svg"> <img class="${!layer.isSelected ? 'gg-checkbox' : 'hidden'}" alt="Expand/Collapse button" src="icons/checked-no.svg"></button> <button class="${layer.isSelected ? 'gg-icon-button gg-small gg-selected' : 'gg-icon-button gg-small gg-opacity'}" onclick="${() => this.selectLayer(layer)}"><span i18n="${layer.name}" class="gg-tree-label">${layer.name}</span></button></li>`)}</ul><button id="add" tip="Add selected" i18n="Add selected" class="gg-button" onclick="${() => this.addSelectedLayers()}">Add selected</button></section></div>`;
|
|
19
19
|
};
|
|
20
20
|
isPanelVisible = false;
|
|
21
21
|
panelTitle = 'ext-layer-panel';
|
|
@@ -26,7 +26,7 @@ class ExternalLayersComponent extends GirafeHTMLElement {
|
|
|
26
26
|
return this.predefinedSources.filter((source) => source.type === 'WMS' || source.type === 'WMTS');
|
|
27
27
|
}
|
|
28
28
|
get hasPredefinedWmsWmtsSources() {
|
|
29
|
-
return this.predefinedWmsWmtsSources.length
|
|
29
|
+
return this.predefinedWmsWmtsSources.length > 0;
|
|
30
30
|
}
|
|
31
31
|
selectedTab = 'wms_wmts';
|
|
32
32
|
wmtsManager;
|
|
@@ -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.
|
|
@@ -39,10 +39,8 @@ header{border:none;flex-direction:row-reverse;margin-top:.5rem;margin-bottom:.5r
|
|
|
39
39
|
this.refreshRender(layer);
|
|
40
40
|
this.refreshRender(layer.parent);
|
|
41
41
|
});
|
|
42
|
-
this.subscribe('treeview.renderEnabled', (
|
|
43
|
-
|
|
44
|
-
this.refreshRender();
|
|
45
|
-
}
|
|
42
|
+
this.subscribe('treeview.renderEnabled', () => {
|
|
43
|
+
this.refreshRender();
|
|
46
44
|
});
|
|
47
45
|
}
|
|
48
46
|
connectedCallback() {
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
1
7
|
import LayerTimeFormatter from '../../tools/time/layertimeformatter.js';
|
|
2
8
|
import Layer from './layer.js';
|
|
9
|
+
import { BrainIgnore } from '../../tools/state/brain/decorators.js';
|
|
3
10
|
class LayerWms extends Layer {
|
|
4
11
|
/**
|
|
5
12
|
* This class is a used in the state of the application, which will be accessed behind a javascript proxy.
|
|
@@ -189,4 +196,7 @@ class LayerWms extends Layer {
|
|
|
189
196
|
return opts;
|
|
190
197
|
}
|
|
191
198
|
}
|
|
199
|
+
__decorate([
|
|
200
|
+
BrainIgnore
|
|
201
|
+
], LayerWms.prototype, "ogcServer", void 0);
|
|
192
202
|
export default LayerWms;
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { BrainIgnore } from '../../tools/state/brain/decorators.js';
|
|
1
8
|
import Layer from './layer.js';
|
|
2
9
|
class LayerWmts extends Layer {
|
|
3
10
|
/**
|
|
@@ -115,4 +122,7 @@ class LayerWmts extends Layer {
|
|
|
115
122
|
};
|
|
116
123
|
}
|
|
117
124
|
}
|
|
125
|
+
__decorate([
|
|
126
|
+
BrainIgnore
|
|
127
|
+
], LayerWmts.prototype, "ogcServer", void 0);
|
|
118
128
|
export default LayerWmts;
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"1.0.
|
|
1
|
+
{"version":"1.0.2259617981", "build":"2259617981", "date":"13/01/2026"}
|
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';
|
|
@@ -62,11 +62,11 @@ export type { default as ISnappingConfig } from './snap/isnapconfig.js';
|
|
|
62
62
|
export type { TProxy } from './state/brain/brain.js';
|
|
63
63
|
export { default as Brain } from './state/brain/brain.js';
|
|
64
64
|
export type { IBrainSerializable } from './state/brain/decorators.js';
|
|
65
|
-
export { ignoreCloneSymbol, serializeSymbol, BrainIgnoreClone, BrainSerialize, isBrainSerializable } from './state/brain/decorators.js';
|
|
65
|
+
export { ignoreSymbol, ignoreCloneSymbol, serializeSymbol, BrainIgnoreClone, BrainIgnore, BrainSerialize, isBrainSerializable } from './state/brain/decorators.js';
|
|
66
66
|
export { default as areEqual } from './state/brain/equality.js';
|
|
67
67
|
export type { Constructor, IBrainSerializer } from './state/brain/serialize.js';
|
|
68
68
|
export { default as BrainSerializer } from './state/brain/serialize.js';
|
|
69
|
-
export { deepFreeze, deepCloneCustomizer, deepClone, isPrimitive, isFunction, isVirtualProperty, isConstructor, isIgnoredProperty, isFlagedIgnoreClone, isTypeSupported, isIterator } from './state/brain/tools.js';
|
|
69
|
+
export { deepFreeze, deepCloneCustomizer, deepClone, isPrimitive, isFunction, isVirtualProperty, isConstructor, isIgnoredProperty, isFlagedIgnoreClone, isFlagedIgnore, isTypeSupported, isIterator } from './state/brain/tools.js';
|
|
70
70
|
export { default as ComponentManager } from './state/componentManager.js';
|
|
71
71
|
export type { CameraConfig } from './state/globe.js';
|
|
72
72
|
export { default as GlobeState } from './state/globe.js';
|
package/tools/main.js
CHANGED
|
@@ -49,10 +49,10 @@ export { default as SessionManager } from './share/sessionmanager.js';
|
|
|
49
49
|
export { default as ShareManager } from './share/sharemanager.js';
|
|
50
50
|
export { default as StateSerializer } from './share/stateserializer.js';
|
|
51
51
|
export { default as Brain } from './state/brain/brain.js';
|
|
52
|
-
export { ignoreCloneSymbol, serializeSymbol, BrainIgnoreClone, BrainSerialize, isBrainSerializable } from './state/brain/decorators.js';
|
|
52
|
+
export { ignoreSymbol, ignoreCloneSymbol, serializeSymbol, BrainIgnoreClone, BrainIgnore, BrainSerialize, isBrainSerializable } from './state/brain/decorators.js';
|
|
53
53
|
export { default as areEqual } from './state/brain/equality.js';
|
|
54
54
|
export { default as BrainSerializer } from './state/brain/serialize.js';
|
|
55
|
-
export { deepFreeze, deepCloneCustomizer, deepClone, isPrimitive, isFunction, isVirtualProperty, isConstructor, isIgnoredProperty, isFlagedIgnoreClone, isTypeSupported, isIterator } from './state/brain/tools.js';
|
|
55
|
+
export { deepFreeze, deepCloneCustomizer, deepClone, isPrimitive, isFunction, isVirtualProperty, isConstructor, isIgnoredProperty, isFlagedIgnoreClone, isFlagedIgnore, isTypeSupported, isIterator } from './state/brain/tools.js';
|
|
56
56
|
export { default as ComponentManager } from './state/componentManager.js';
|
|
57
57
|
export { default as GlobeState } from './state/globe.js';
|
|
58
58
|
export { default as GraphicalInterface } from './state/graphicalInterface.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
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
const sharedBasemap = {
|
|
23
|
+
id: basemap.id,
|
|
24
|
+
name: basemap.name,
|
|
25
|
+
opacity: basemap.opacity
|
|
26
|
+
};
|
|
27
|
+
sharedBasemaps.push(sharedBasemap);
|
|
29
28
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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).
|
|
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
|
|
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('
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
+
};
|
|
@@ -6,7 +6,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
6
6
|
};
|
|
7
7
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
8
8
|
import Brain from './brain';
|
|
9
|
-
import { BrainIgnoreClone } from './decorators';
|
|
9
|
+
import { BrainIgnore, BrainIgnoreClone } from './decorators';
|
|
10
10
|
let state;
|
|
11
11
|
let brain;
|
|
12
12
|
describe('State: base principles', () => {
|
|
@@ -377,7 +377,7 @@ describe('State: base principles', () => {
|
|
|
377
377
|
});
|
|
378
378
|
expect(counter).toBe(2);
|
|
379
379
|
});
|
|
380
|
-
it('Base principles: Do not clone objects with
|
|
380
|
+
it('Base principles: Do not clone objects with decorator @BrainIgnoreClone', () => {
|
|
381
381
|
class Pizza {
|
|
382
382
|
key;
|
|
383
383
|
owner;
|
|
@@ -406,7 +406,7 @@ describe('State: base principles', () => {
|
|
|
406
406
|
brain.getState().objectValue = pizza2;
|
|
407
407
|
expect(counter).toBe(2);
|
|
408
408
|
});
|
|
409
|
-
it('Base principles: Do not clone objects with
|
|
409
|
+
it('Base principles: Do not clone objects with decorator @BrainIgnoreClone, but listen to changes', () => {
|
|
410
410
|
class Pizza {
|
|
411
411
|
key;
|
|
412
412
|
owner;
|
|
@@ -434,6 +434,35 @@ describe('State: base principles', () => {
|
|
|
434
434
|
brain.getState().objectValue.owner.place = 'Bernwiller';
|
|
435
435
|
expect(counter).toBe(2);
|
|
436
436
|
});
|
|
437
|
+
it('Base principles: Do not create proxy for attributes with decorator @BrainIgnore', () => {
|
|
438
|
+
class Pizza {
|
|
439
|
+
key;
|
|
440
|
+
owner;
|
|
441
|
+
constructor(key, owner) {
|
|
442
|
+
this.key = key;
|
|
443
|
+
this.owner = owner;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
__decorate([
|
|
447
|
+
BrainIgnore
|
|
448
|
+
], Pizza.prototype, "owner", void 0);
|
|
449
|
+
const pizzeria = {
|
|
450
|
+
name: 'Noninna',
|
|
451
|
+
place: 'Aspach'
|
|
452
|
+
};
|
|
453
|
+
const pizza1 = new Pizza('pizza1', pizzeria);
|
|
454
|
+
state.objectValue = pizza1;
|
|
455
|
+
let counter = 1;
|
|
456
|
+
brain = new Brain(state, () => {
|
|
457
|
+
counter++;
|
|
458
|
+
});
|
|
459
|
+
const pizzaProxy = brain.getState().objectValue;
|
|
460
|
+
expect(pizzaProxy.__brainIsProxy).toBeTruthy();
|
|
461
|
+
const owner = pizzaProxy.owner;
|
|
462
|
+
expect(owner.__brainIsProxy).toBeFalsy();
|
|
463
|
+
owner.place = 'Bernwiller';
|
|
464
|
+
expect(counter).toBe(1);
|
|
465
|
+
});
|
|
437
466
|
it('Base principles: Handles multiple parents referencing the same object', () => {
|
|
438
467
|
const shared = { id: 42 };
|
|
439
468
|
state.objectValue = { child: shared };
|
|
@@ -20,20 +20,20 @@ export default class Brain {
|
|
|
20
20
|
this.externalCallback = callback;
|
|
21
21
|
}
|
|
22
22
|
callback(path, oldValue, newValue, parents) {
|
|
23
|
-
if (
|
|
24
|
-
// Immediate Callback
|
|
25
|
-
this.externalCallback(path, oldValue, newValue, parents);
|
|
26
|
-
}
|
|
27
|
-
else {
|
|
23
|
+
if (this.delayed) {
|
|
28
24
|
// Remember all callback infos
|
|
29
25
|
const infos = this.delayedCallbacks.get(path);
|
|
30
|
-
if (
|
|
31
|
-
this.delayedCallbacks.set(path, { oldValue: oldValue, newValue: newValue, parents: parents });
|
|
32
|
-
}
|
|
33
|
-
else {
|
|
26
|
+
if (infos) {
|
|
34
27
|
// Just set the new value
|
|
35
28
|
infos.newValue = newValue;
|
|
36
29
|
}
|
|
30
|
+
else {
|
|
31
|
+
this.delayedCallbacks.set(path, { oldValue: oldValue, newValue: newValue, parents: parents });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
// Immediate Callback
|
|
36
|
+
this.externalCallback(path, oldValue, newValue, parents);
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
/**
|
|
@@ -86,17 +86,21 @@ export default class Brain {
|
|
|
86
86
|
return this.mergeMinimalPathsCache.get(cacheKey);
|
|
87
87
|
}
|
|
88
88
|
const minimalPaths = new Set();
|
|
89
|
+
const parentsPaths = [];
|
|
90
|
+
for (const p of parents) {
|
|
91
|
+
parentsPaths.push(...p.__brainFullPaths);
|
|
92
|
+
}
|
|
89
93
|
for (const candidate of [...existingPaths, ...candidatePaths]) {
|
|
90
94
|
// Already present
|
|
91
95
|
if (minimalPaths.has(candidate)) {
|
|
92
96
|
continue;
|
|
93
97
|
}
|
|
94
98
|
// If the candidate hat a minimal path as prefix
|
|
95
|
-
if (
|
|
99
|
+
if (Array.from(minimalPaths).some((path) => candidate.startsWith(`${path}.`))) {
|
|
96
100
|
continue;
|
|
97
101
|
}
|
|
98
102
|
// If not prefixed by any parent
|
|
99
|
-
if (!this.isRightParent(candidate,
|
|
103
|
+
if (!this.isRightParent(candidate, parentsPaths)) {
|
|
100
104
|
continue;
|
|
101
105
|
}
|
|
102
106
|
minimalPaths.add(candidate);
|
|
@@ -105,30 +109,23 @@ export default class Brain {
|
|
|
105
109
|
this.mergeMinimalPathsCache.set(cacheKey, returnValue);
|
|
106
110
|
return returnValue;
|
|
107
111
|
}
|
|
108
|
-
isRightParent(candidate,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
+
isRightParent(candidate, parentsPaths) {
|
|
113
|
+
if (parentsPaths.length === 0 || parentsPaths[0].length === 0) {
|
|
114
|
+
// On the root
|
|
115
|
+
return true;
|
|
112
116
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
for (const parentPath of parentsPaths) {
|
|
118
|
-
if (parentPath.includes(`${candidate}.`)) {
|
|
119
|
-
circularReference = true;
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
else if (candidate.startsWith(`${parentPath}.`)) {
|
|
123
|
-
rightParent = true;
|
|
124
|
-
break;
|
|
125
|
-
}
|
|
117
|
+
for (const parentPath of parentsPaths) {
|
|
118
|
+
if (parentPath.includes(`${candidate}.`)) {
|
|
119
|
+
// Circular reference
|
|
120
|
+
return true;
|
|
126
121
|
}
|
|
127
|
-
if (
|
|
128
|
-
|
|
122
|
+
if (candidate.startsWith(`${parentPath}.`)) {
|
|
123
|
+
// Parent found
|
|
124
|
+
return true;
|
|
129
125
|
}
|
|
130
126
|
}
|
|
131
|
-
|
|
127
|
+
// Not a valid parent
|
|
128
|
+
return false;
|
|
132
129
|
}
|
|
133
130
|
getOrCreateProxyForValue(proxy, prop, value, childPaths) {
|
|
134
131
|
if (isPrimitive(value)) {
|
|
@@ -150,8 +147,8 @@ export default class Brain {
|
|
|
150
147
|
const fullPaths = valueProxy.__brainFullPaths;
|
|
151
148
|
if (merged.length !== fullPaths.length || merged.some((p, i) => p !== fullPaths[i])) {
|
|
152
149
|
fullPaths.splice(0, fullPaths.length, ...merged);
|
|
150
|
+
this.updateChildPathsRecursively(valueProxy, fullPaths);
|
|
153
151
|
}
|
|
154
|
-
this.updateChildPathsRecursively(valueProxy, fullPaths);
|
|
155
152
|
}
|
|
156
153
|
else {
|
|
157
154
|
// Create a new proxy
|
|
@@ -197,7 +194,7 @@ export default class Brain {
|
|
|
197
194
|
}
|
|
198
195
|
recalculateChildrenForArray(proxy, target, oldValue) {
|
|
199
196
|
if (!Array.isArray(target)) {
|
|
200
|
-
throw new
|
|
197
|
+
throw new TypeError('This method is only for arrays');
|
|
201
198
|
}
|
|
202
199
|
// Clean previous childs
|
|
203
200
|
proxy.__brainChildren.clear();
|
|
@@ -266,7 +263,7 @@ export default class Brain {
|
|
|
266
263
|
}
|
|
267
264
|
handleGetIgnored(target, prop) {
|
|
268
265
|
const value = target[prop];
|
|
269
|
-
if (
|
|
266
|
+
if (isIgnoredProperty(target, prop)) {
|
|
270
267
|
return value;
|
|
271
268
|
}
|
|
272
269
|
throw new Error(`Unknown ignored property: ${prop}`);
|
|
@@ -334,7 +331,7 @@ export default class Brain {
|
|
|
334
331
|
};
|
|
335
332
|
}
|
|
336
333
|
getHandler(proxy, target, prop) {
|
|
337
|
-
if (isIgnoredProperty(prop)) {
|
|
334
|
+
if (isIgnoredProperty(target, prop)) {
|
|
338
335
|
return this.handleGetIgnored(target, prop);
|
|
339
336
|
}
|
|
340
337
|
if (isVirtualProperty(prop)) {
|
|
@@ -388,7 +385,7 @@ export default class Brain {
|
|
|
388
385
|
let oldValue = target[prop];
|
|
389
386
|
const childPaths = proxy.__brainFullPaths.map((path) => this.getFullPath(path, prop));
|
|
390
387
|
this.cleanProxyForValue(proxy, prop, oldValue, childPaths);
|
|
391
|
-
if (isIgnoredProperty(prop)) {
|
|
388
|
+
if (isIgnoredProperty(target, prop)) {
|
|
392
389
|
// Ignored => No callback and no clone of the old version
|
|
393
390
|
// Just set the new value
|
|
394
391
|
target[prop] = newValue;
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A symbol used to mark properties that should be ignored during cloning operations.
|
|
3
|
+
*/
|
|
4
|
+
export declare const ignoreSymbol: unique symbol;
|
|
1
5
|
/**
|
|
2
6
|
* A symbol used to mark properties that should be ignored during cloning operations.
|
|
3
7
|
*/
|
|
@@ -13,6 +17,13 @@ export declare const serializeSymbol: unique symbol;
|
|
|
13
17
|
* @param propertyKey - The key of the property to be ignored during cloning.
|
|
14
18
|
*/
|
|
15
19
|
export declare function BrainIgnoreClone(target: any, propertyKey: string | symbol): void;
|
|
20
|
+
/**
|
|
21
|
+
* A decorator function that marks a property of a class to be ignored. No proxy will be created for this object.
|
|
22
|
+
*
|
|
23
|
+
* @param target - The target object (class prototype) where the property resides.
|
|
24
|
+
* @param propertyKey - The key of the property to be ignored.
|
|
25
|
+
*/
|
|
26
|
+
export declare function BrainIgnore(target: any, propertyKey: string | symbol): void;
|
|
16
27
|
/**
|
|
17
28
|
* A decorator function that marks a property of a class to be serializable.
|
|
18
29
|
*
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A symbol used to mark properties that should be ignored during cloning operations.
|
|
3
|
+
*/
|
|
4
|
+
export const ignoreSymbol = Symbol('brainIgnore');
|
|
1
5
|
/**
|
|
2
6
|
* A symbol used to mark properties that should be ignored during cloning operations.
|
|
3
7
|
*/
|
|
@@ -16,6 +20,16 @@ export function BrainIgnoreClone(target, propertyKey) {
|
|
|
16
20
|
target[ignoreCloneSymbol] ??= [];
|
|
17
21
|
target[ignoreCloneSymbol].push(propertyKey);
|
|
18
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* A decorator function that marks a property of a class to be ignored. No proxy will be created for this object.
|
|
25
|
+
*
|
|
26
|
+
* @param target - The target object (class prototype) where the property resides.
|
|
27
|
+
* @param propertyKey - The key of the property to be ignored.
|
|
28
|
+
*/
|
|
29
|
+
export function BrainIgnore(target, propertyKey) {
|
|
30
|
+
target[ignoreSymbol] ??= [];
|
|
31
|
+
target[ignoreSymbol].push(propertyKey);
|
|
32
|
+
}
|
|
19
33
|
/**
|
|
20
34
|
* A decorator function that marks a property of a class to be serializable.
|
|
21
35
|
*
|
|
@@ -62,7 +62,7 @@ export declare function isConstructor(prop: string): boolean;
|
|
|
62
62
|
* @param prop - The property name or symbol to check.
|
|
63
63
|
* @returns `true` if the property should be ignored, otherwise `false`.
|
|
64
64
|
*/
|
|
65
|
-
export declare function isIgnoredProperty(prop: string | symbol): boolean;
|
|
65
|
+
export declare function isIgnoredProperty(target: any, prop: string | symbol): boolean;
|
|
66
66
|
/**
|
|
67
67
|
* Checks if a property on a target object is flagged to be ignored during cloning.
|
|
68
68
|
*
|
|
@@ -71,6 +71,14 @@ export declare function isIgnoredProperty(prop: string | symbol): boolean;
|
|
|
71
71
|
* @returns `true` if the property is flagged to be ignored, otherwise `false`.
|
|
72
72
|
*/
|
|
73
73
|
export declare function isFlagedIgnoreClone(target: any, prop?: string | number): any;
|
|
74
|
+
/**
|
|
75
|
+
* Checks if a property on a target object is flagged to be totally ignored by brain.
|
|
76
|
+
*
|
|
77
|
+
* @param target - The object containing the property.
|
|
78
|
+
* @param prop - The property name or index to check.
|
|
79
|
+
* @returns `true` if the property is flagged to be ignored, otherwise `false`.
|
|
80
|
+
*/
|
|
81
|
+
export declare function isFlagedIgnore(target: any, prop?: string | number): any;
|
|
74
82
|
/**
|
|
75
83
|
* Determines if a value is of a supported type.
|
|
76
84
|
* Unsupported types include `WeakMap`, `WeakSet`, `Map`, and `Set`.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { cloneDeepWith } from 'lodash-es';
|
|
2
|
-
import { ignoreCloneSymbol } from './decorators.js';
|
|
2
|
+
import { ignoreCloneSymbol, ignoreSymbol } from './decorators.js';
|
|
3
3
|
/**
|
|
4
4
|
* Recursively freezes an object and all its properties to make it immutable.
|
|
5
5
|
* This function also handles circular references by using a `WeakSet` to track visited objects.
|
|
@@ -17,15 +17,15 @@ export function deepFreeze(obj, visitedObjects = new WeakSet()) {
|
|
|
17
17
|
}
|
|
18
18
|
visitedObjects.add(obj);
|
|
19
19
|
Object.freeze(obj);
|
|
20
|
-
Object.getOwnPropertyNames(obj)
|
|
21
|
-
if (!isIgnoredProperty(prop) && !isFlagedIgnoreClone(obj, prop)) {
|
|
20
|
+
for (const prop of Object.getOwnPropertyNames(obj)) {
|
|
21
|
+
if (!isIgnoredProperty(obj, prop) && !isFlagedIgnoreClone(obj, prop)) {
|
|
22
22
|
// Do not freeze ignored objects
|
|
23
23
|
const value = obj[prop];
|
|
24
24
|
if (typeof value === 'object' && value !== null) {
|
|
25
25
|
deepFreeze(value, visitedObjects);
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
-
}
|
|
28
|
+
}
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
31
|
* Customizer function for `cloneDeepWith` to handle specific cloning rules.
|
|
@@ -39,14 +39,10 @@ export function deepFreeze(obj, visitedObjects = new WeakSet()) {
|
|
|
39
39
|
* @returns The value to use for the cloned property, or `undefined` to use the default cloning behavior.
|
|
40
40
|
*/
|
|
41
41
|
export function deepCloneCustomizer(value, prop, target) {
|
|
42
|
-
if (prop?.toString().startsWith('_')) {
|
|
42
|
+
if (isFlagedIgnore(value, prop) || isFlagedIgnoreClone(target, prop) || prop?.toString().startsWith('_')) {
|
|
43
43
|
// Do not clone : just copy the reference
|
|
44
44
|
return value;
|
|
45
45
|
}
|
|
46
|
-
if (isFlagedIgnoreClone(target, prop)) {
|
|
47
|
-
// Do not clone marked properties
|
|
48
|
-
return value;
|
|
49
|
-
}
|
|
50
46
|
// Else : do nothing special, the default cloneDeep will be used.
|
|
51
47
|
}
|
|
52
48
|
/**
|
|
@@ -108,8 +104,14 @@ export function isConstructor(prop) {
|
|
|
108
104
|
* @param prop - The property name or symbol to check.
|
|
109
105
|
* @returns `true` if the property should be ignored, otherwise `false`.
|
|
110
106
|
*/
|
|
111
|
-
export function isIgnoredProperty(prop) {
|
|
112
|
-
|
|
107
|
+
export function isIgnoredProperty(target, prop) {
|
|
108
|
+
if (typeof prop === 'symbol') {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
if (prop.startsWith('_') && !isVirtualProperty(prop)) {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
return isFlagedIgnore(target, prop);
|
|
113
115
|
}
|
|
114
116
|
/**
|
|
115
117
|
* Checks if a property on a target object is flagged to be ignored during cloning.
|
|
@@ -121,6 +123,16 @@ export function isIgnoredProperty(prop) {
|
|
|
121
123
|
export function isFlagedIgnoreClone(target, prop) {
|
|
122
124
|
return target?.[ignoreCloneSymbol]?.includes(prop);
|
|
123
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Checks if a property on a target object is flagged to be totally ignored by brain.
|
|
128
|
+
*
|
|
129
|
+
* @param target - The object containing the property.
|
|
130
|
+
* @param prop - The property name or index to check.
|
|
131
|
+
* @returns `true` if the property is flagged to be ignored, otherwise `false`.
|
|
132
|
+
*/
|
|
133
|
+
export function isFlagedIgnore(target, prop) {
|
|
134
|
+
return target?.[ignoreSymbol]?.includes(prop);
|
|
135
|
+
}
|
|
124
136
|
/**
|
|
125
137
|
* Determines if a value is of a supported type.
|
|
126
138
|
* Unsupported types include `WeakMap`, `WeakSet`, `Map`, and `Set`.
|