@geogirafe/lib-geoportal 1.0.2259547422 → 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/extlayers/component.js +2 -2
- package/components/treeview/treeviewtheme/component.js +2 -4
- 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/main.d.ts +2 -2
- package/tools/main.js +2 -2
- 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
|
@@ -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;
|
|
@@ -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
|
@@ -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';
|
|
@@ -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`.
|