@lionweb/class-core 0.7.0-beta.9 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -1
- package/README.md +6 -6
- package/dist/LionCore_builtins.g.d.ts +2 -2
- package/dist/LionCore_builtins.g.d.ts.map +1 -1
- package/dist/LionCore_builtins.g.js +1 -1
- package/dist/LionCore_builtins.g.js.map +1 -1
- package/dist/base-types.d.ts +7 -11
- package/dist/base-types.d.ts.map +1 -1
- package/dist/base-types.js +3 -28
- package/dist/base-types.js.map +1 -1
- package/dist/convenience.d.ts +10 -9
- package/dist/convenience.d.ts.map +1 -1
- package/dist/convenience.js +12 -11
- package/dist/convenience.js.map +1 -1
- package/dist/deltas/appliers.d.ts +5 -4
- package/dist/deltas/appliers.d.ts.map +1 -1
- package/dist/deltas/appliers.js +369 -163
- package/dist/deltas/appliers.js.map +1 -1
- package/dist/deltas/compositor.d.ts +51 -0
- package/dist/deltas/compositor.d.ts.map +1 -0
- package/dist/deltas/compositor.js +95 -0
- package/dist/deltas/compositor.js.map +1 -0
- package/dist/deltas/index.d.ts +2 -1
- package/dist/deltas/index.d.ts.map +1 -1
- package/dist/deltas/index.js +2 -1
- package/dist/deltas/index.js.map +1 -1
- package/dist/deltas/inverters.d.ts.map +1 -1
- package/dist/deltas/inverters.js +91 -28
- package/dist/deltas/inverters.js.map +1 -1
- package/dist/deltas/receivers.d.ts +39 -0
- package/dist/deltas/receivers.d.ts.map +1 -0
- package/dist/deltas/receivers.js +60 -0
- package/dist/deltas/receivers.js.map +1 -0
- package/dist/deltas/serialization/deserializer.g.d.ts.map +1 -1
- package/dist/deltas/serialization/deserializer.g.js +167 -59
- package/dist/deltas/serialization/deserializer.g.js.map +1 -1
- package/dist/deltas/serialization/serializer-helpers.d.ts +1 -1
- package/dist/deltas/serialization/serializer-helpers.d.ts.map +1 -1
- package/dist/deltas/serialization/serializer-helpers.js +3 -4
- package/dist/deltas/serialization/serializer-helpers.js.map +1 -1
- package/dist/deltas/serialization/serializer.g.d.ts +2 -2
- package/dist/deltas/serialization/serializer.g.d.ts.map +1 -1
- package/dist/deltas/serialization/serializer.g.js +186 -47
- package/dist/deltas/serialization/serializer.g.js.map +1 -1
- package/dist/deltas/serialization/types.g.d.ts +156 -43
- package/dist/deltas/serialization/types.g.d.ts.map +1 -1
- package/dist/deltas/types.g.d.ts +155 -55
- package/dist/deltas/types.g.d.ts.map +1 -1
- package/dist/deltas/types.g.js +170 -57
- package/dist/deltas/types.g.js.map +1 -1
- package/dist/deserializer.d.ts +6 -6
- package/dist/deserializer.d.ts.map +1 -1
- package/dist/deserializer.js +16 -27
- package/dist/deserializer.js.map +1 -1
- package/dist/duplicator.d.ts.map +1 -1
- package/dist/duplicator.js.map +1 -1
- package/dist/factory.d.ts +10 -2
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +25 -16
- package/dist/factory.js.map +1 -1
- package/dist/id-mapping.d.ts +31 -3
- package/dist/id-mapping.d.ts.map +1 -1
- package/dist/id-mapping.js +46 -14
- package/dist/id-mapping.js.map +1 -1
- package/dist/serializer.d.ts.map +1 -1
- package/dist/textualizer.d.ts +1 -1
- package/dist/textualizer.d.ts.map +1 -1
- package/dist/textualizer.js +1 -1
- package/dist/textualizer.js.map +1 -1
- package/dist/value-managers/annotations.d.ts +5 -0
- package/dist/value-managers/annotations.d.ts.map +1 -1
- package/dist/value-managers/annotations.js +31 -3
- package/dist/value-managers/annotations.js.map +1 -1
- package/dist/value-managers/base.d.ts +2 -2
- package/dist/value-managers/base.d.ts.map +1 -1
- package/dist/value-managers/base.js +4 -4
- package/dist/value-managers/base.js.map +1 -1
- package/dist/value-managers/containments.d.ts +4 -1
- package/dist/value-managers/containments.d.ts.map +1 -1
- package/dist/value-managers/containments.js +151 -20
- package/dist/value-managers/containments.js.map +1 -1
- package/dist/value-managers/properties.d.ts.map +1 -1
- package/dist/value-managers/properties.js +1 -0
- package/dist/value-managers/properties.js.map +1 -1
- package/dist/value-managers/references.d.ts +3 -0
- package/dist/value-managers/references.d.ts.map +1 -1
- package/dist/value-managers/references.js +26 -8
- package/dist/value-managers/references.js.map +1 -1
- package/package.json +34 -37
- package/src/LionCore_builtins.g.ts +2 -2
- package/src/base-types.ts +6 -32
- package/src/convenience.ts +15 -14
- package/src/deltas/appliers.ts +377 -177
- package/src/deltas/compositor.ts +107 -0
- package/src/deltas/index.ts +3 -1
- package/src/deltas/inverters.ts +108 -36
- package/src/deltas/receivers.ts +92 -0
- package/src/deltas/serialization/deserializer.g.ts +185 -64
- package/src/deltas/serialization/serializer-helpers.ts +3 -10
- package/src/deltas/serialization/serializer.g.ts +242 -63
- package/src/deltas/serialization/types.g.ts +190 -51
- package/src/deltas/types.g.ts +190 -51
- package/src/deserializer.ts +32 -43
- package/src/duplicator.ts +1 -1
- package/src/factory.ts +31 -17
- package/src/id-mapping.ts +43 -5
- package/src/textualizer.ts +1 -1
- package/src/value-managers/annotations.ts +28 -2
- package/src/value-managers/base.ts +4 -4
- package/src/value-managers/containments.ts +144 -20
- package/src/value-managers/properties.ts +1 -0
- package/src/value-managers/references.ts +28 -8
- package/dist/deltas/handlers.d.ts +0 -17
- package/dist/deltas/handlers.d.ts.map +0 -1
- package/dist/deltas/handlers.js +0 -43
- package/dist/deltas/handlers.js.map +0 -1
- package/src/deltas/handlers.ts +0 -64
package/src/deserializer.ts
CHANGED
|
@@ -16,12 +16,11 @@
|
|
|
16
16
|
// SPDX-License-Identifier: Apache-2.0
|
|
17
17
|
|
|
18
18
|
import {
|
|
19
|
-
|
|
19
|
+
builtinPropertyValueDeserializer,
|
|
20
20
|
Classifier,
|
|
21
21
|
Containment,
|
|
22
22
|
defaultSimplisticHandler,
|
|
23
23
|
Enumeration,
|
|
24
|
-
Language,
|
|
25
24
|
MemoisingSymbolTable,
|
|
26
25
|
PrimitiveType,
|
|
27
26
|
Property,
|
|
@@ -31,9 +30,10 @@ import {
|
|
|
31
30
|
unresolved
|
|
32
31
|
} from "@lionweb/core"
|
|
33
32
|
import { LionWebId, LionWebJsonChunk, LionWebJsonNode } from "@lionweb/json"
|
|
34
|
-
import { byIdMap } from "@lionweb/ts-utils"
|
|
33
|
+
import { byIdMap, keepDefineds } from "@lionweb/ts-utils"
|
|
35
34
|
|
|
36
|
-
import {
|
|
35
|
+
import { DeltaReceiver, IdMapping, ILanguageBase, INodeBase } from "./index.js"
|
|
36
|
+
import { combinedLanguageBaseLookupFor } from "./factory.js"
|
|
37
37
|
import { NodesToInstall } from "./linking.js"
|
|
38
38
|
|
|
39
39
|
|
|
@@ -43,26 +43,12 @@ import { NodesToInstall } from "./linking.js"
|
|
|
43
43
|
export type Deserializer<T> = (
|
|
44
44
|
serializationChunk: LionWebJsonChunk,
|
|
45
45
|
dependentNodes?: INodeBase[],
|
|
46
|
+
idMapping?: IdMapping,
|
|
46
47
|
propertyValueDeserializer?: PropertyValueDeserializer,
|
|
47
48
|
problemHandler?: SimplisticHandler
|
|
48
49
|
) => T;
|
|
49
50
|
|
|
50
51
|
|
|
51
|
-
const languageBaseLookupFor = (languageBases: ILanguageBase[]) =>
|
|
52
|
-
(language: Language) => {
|
|
53
|
-
const languageBase = languageBases.find((languageBase) => languageBase.language === language);
|
|
54
|
-
if (languageBase === undefined) {
|
|
55
|
-
throw new Error(`language ${language.name} (with key=${language.key} and version=${language.version}) not known`);
|
|
56
|
-
}
|
|
57
|
-
return languageBase;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const factoryLookupFor = (languageBases: ILanguageBase[], handleDelta?: DeltaHandler) => {
|
|
61
|
-
const lookup = languageBaseLookupFor(languageBases);
|
|
62
|
-
return (language: Language) => lookup(language).factory(handleDelta);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
52
|
/**
|
|
67
53
|
* A quasi-tuple of the roots (of type {@link INodeBase}) of a model,
|
|
68
54
|
* and its {@link IdMapping} instance.
|
|
@@ -73,24 +59,24 @@ export type RootsWithIdMapping = { roots: INodeBase[], idMapping: IdMapping };
|
|
|
73
59
|
/**
|
|
74
60
|
* @return a {@link Deserializer} function for the given languages (given as {@link ILanguageBase}s) that returns a {@link RootsWithIdMapping}.
|
|
75
61
|
* @param languageBases the {@link ILanguageBase}s for (at least) all the languages used in the {@link LionWebJsonChunk} to deserialize, minus LionCore M3 and built-ins.
|
|
76
|
-
* @param
|
|
62
|
+
* @param receiveDelta an optional {@link DeltaReceiver} that will be injected in all {@link INodeBase nodes} created.
|
|
77
63
|
*/
|
|
78
|
-
export const nodeBaseDeserializerWithIdMapping = (languageBases: ILanguageBase[],
|
|
64
|
+
export const nodeBaseDeserializerWithIdMapping = (languageBases: ILanguageBase[], receiveDelta?: DeltaReceiver): Deserializer<RootsWithIdMapping> => {
|
|
79
65
|
|
|
80
66
|
const symbolTable = new MemoisingSymbolTable(languageBases.map(({language}) => language));
|
|
81
|
-
const languageBaseFor =
|
|
82
|
-
const factoryFor = factoryLookupFor(languageBases, handleDelta);
|
|
67
|
+
const languageBaseFor = combinedLanguageBaseLookupFor(languageBases);
|
|
83
68
|
|
|
84
69
|
return (
|
|
85
|
-
serializationChunk
|
|
86
|
-
dependentNodes
|
|
87
|
-
|
|
88
|
-
|
|
70
|
+
serializationChunk,
|
|
71
|
+
dependentNodes = [],
|
|
72
|
+
idMapping,
|
|
73
|
+
propertyValueDeserializer = builtinPropertyValueDeserializer,
|
|
74
|
+
problemsHandler = defaultSimplisticHandler
|
|
89
75
|
): RootsWithIdMapping => {
|
|
90
76
|
|
|
91
77
|
const nodesToInstall: NodesToInstall[] = [];
|
|
92
78
|
|
|
93
|
-
const createNode = ({id, classifier: classifierMetaPointer, properties, containments, references, annotations}: LionWebJsonNode) => {
|
|
79
|
+
const createNode = ({id, classifier: classifierMetaPointer, properties, containments, references, annotations}: LionWebJsonNode): (INodeBase | undefined) => {
|
|
94
80
|
const languageMessage = `language ${classifierMetaPointer.language} (${classifierMetaPointer.version})`;
|
|
95
81
|
const classifier = symbolTable.entityMatching(classifierMetaPointer);
|
|
96
82
|
if (classifier === undefined || !(classifier instanceof Classifier)) {
|
|
@@ -98,7 +84,7 @@ export const nodeBaseDeserializerWithIdMapping = (languageBases: ILanguageBase[]
|
|
|
98
84
|
return undefined;
|
|
99
85
|
}
|
|
100
86
|
|
|
101
|
-
const node =
|
|
87
|
+
const node = languageBaseFor(classifier.language).factory(receiveDelta)(classifier, id);
|
|
102
88
|
|
|
103
89
|
properties.forEach(({property: propertyMetaPointer, value}) => {
|
|
104
90
|
const feature = symbolTable.featureMatching(classifierMetaPointer, propertyMetaPointer);
|
|
@@ -152,15 +138,17 @@ export const nodeBaseDeserializerWithIdMapping = (languageBases: ILanguageBase[]
|
|
|
152
138
|
};
|
|
153
139
|
|
|
154
140
|
const nodesById = byIdMap(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
141
|
+
keepDefineds(
|
|
142
|
+
serializationChunk
|
|
143
|
+
.nodes
|
|
144
|
+
.map(createNode)
|
|
145
|
+
)
|
|
158
146
|
);
|
|
159
147
|
|
|
160
|
-
const dependentNodesById = byIdMap(dependentNodes)
|
|
148
|
+
const dependentNodesById = byIdMap(dependentNodes);
|
|
161
149
|
|
|
162
|
-
const lookupNodeById = (id: LionWebId) =>
|
|
163
|
-
nodesById[id] ?? dependentNodesById[id];
|
|
150
|
+
const lookupNodeById = (id: LionWebId): (INodeBase | undefined) =>
|
|
151
|
+
nodesById[id] ?? dependentNodesById[id] ?? idMapping?.tryFromId(id);
|
|
164
152
|
|
|
165
153
|
nodesToInstall.forEach(([node, feature, ids]) => {
|
|
166
154
|
if (feature instanceof Containment) {
|
|
@@ -217,16 +205,17 @@ export const nodeBaseDeserializerWithIdMapping = (languageBases: ILanguageBase[]
|
|
|
217
205
|
/**
|
|
218
206
|
* @return a {@link Deserializer} function for the languages (given as {@link ILanguageBase}s) that returns the roots (of type {@link INodeBase}) of the deserialized model.
|
|
219
207
|
* @param languageBases the {@link ILanguageBase}s for (at least) all the languages used in the {@link LionWebJsonChunk} to deserialize, minus LionCore M3 and built-ins.
|
|
220
|
-
* @param
|
|
208
|
+
* @param receiveDelta an optional {@link DeltaReceiver} that will be injected in all {@link INodeBase nodes} created.
|
|
221
209
|
*/
|
|
222
|
-
export const nodeBaseDeserializer = (languageBases: ILanguageBase[],
|
|
223
|
-
const deserializerWithIdMapping = nodeBaseDeserializerWithIdMapping(languageBases,
|
|
210
|
+
export const nodeBaseDeserializer = (languageBases: ILanguageBase[], receiveDelta?: DeltaReceiver): Deserializer<INodeBase[]> => {
|
|
211
|
+
const deserializerWithIdMapping = nodeBaseDeserializerWithIdMapping(languageBases, receiveDelta);
|
|
224
212
|
return (
|
|
225
|
-
serializationChunk
|
|
226
|
-
dependentNodes
|
|
227
|
-
|
|
228
|
-
|
|
213
|
+
serializationChunk,
|
|
214
|
+
dependentNodes,
|
|
215
|
+
idMapping,
|
|
216
|
+
propertyValueDeserializer = builtinPropertyValueDeserializer,
|
|
217
|
+
problemsHandler = defaultSimplisticHandler
|
|
229
218
|
): INodeBase[] =>
|
|
230
|
-
deserializerWithIdMapping(serializationChunk, dependentNodes, propertyValueDeserializer, problemsHandler).roots
|
|
219
|
+
deserializerWithIdMapping(serializationChunk, dependentNodes, idMapping, propertyValueDeserializer, problemsHandler).roots
|
|
231
220
|
}
|
|
232
221
|
|
package/src/duplicator.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// SPDX-FileCopyrightText: 2025 TRUMPF Laser SE and other contributors
|
|
16
16
|
// SPDX-License-Identifier: Apache-2.0
|
|
17
17
|
|
|
18
|
-
import {
|
|
18
|
+
import { Containment, Feature, idOf, Link, Property, Reference, SingleRef, unresolved } from "@lionweb/core"
|
|
19
19
|
import { LionWebId } from "@lionweb/json"
|
|
20
20
|
import { asArray } from "@lionweb/ts-utils"
|
|
21
21
|
|
package/src/factory.ts
CHANGED
|
@@ -15,35 +15,49 @@
|
|
|
15
15
|
// SPDX-FileCopyrightText: 2025 TRUMPF Laser SE and other contributors
|
|
16
16
|
// SPDX-License-Identifier: Apache-2.0
|
|
17
17
|
|
|
18
|
+
import { Language } from "@lionweb/core"
|
|
18
19
|
import { lazyMapGet } from "@lionweb/ts-utils"
|
|
19
20
|
import { ILanguageBase, NodeBaseFactory } from "./base-types.js"
|
|
20
|
-
import {
|
|
21
|
+
import { DeltaReceiver } from "./deltas/index.js"
|
|
22
|
+
|
|
21
23
|
|
|
22
24
|
/**
|
|
23
|
-
* @return a {@link
|
|
25
|
+
* @return a function that looks up the {@link ILanguageBase language base} for the {@link Language language} passed to it,
|
|
26
|
+
* from among the given language bases.
|
|
27
|
+
* The returned function throws when the language wasn't among the languages the given bases were for.
|
|
28
|
+
* The lookup is hashmap-backed, so efficient.
|
|
24
29
|
*/
|
|
25
|
-
export const
|
|
30
|
+
export const combinedLanguageBaseLookupFor = (languageBases: ILanguageBase[]): ((language: Language) => ILanguageBase) => {
|
|
26
31
|
// create lookup map:
|
|
27
|
-
const
|
|
32
|
+
const languageKey2version2base: { [key: string]: { [version: string]: ILanguageBase } } = {}
|
|
28
33
|
languageBases.forEach((languageBase) => {
|
|
29
34
|
const {key, version} = languageBase.language
|
|
30
|
-
const
|
|
31
|
-
lazyMapGet(
|
|
32
|
-
// (Note: don't destructure factory from languageBase, as that will unbind it as "this"!)
|
|
35
|
+
const version2base = lazyMapGet(languageKey2version2base, key, () => ({}))
|
|
36
|
+
lazyMapGet(version2base, version, () => languageBase)
|
|
33
37
|
})
|
|
34
38
|
|
|
35
|
-
return (
|
|
36
|
-
const {key, version, name} =
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
39
|
-
throw new Error(`language ${name} with key=${key} not
|
|
39
|
+
return (language) => {
|
|
40
|
+
const {key, version, name} = language
|
|
41
|
+
const version2base = languageKey2version2base[key]
|
|
42
|
+
if (version2base === undefined) {
|
|
43
|
+
throw new Error(`language ${name} with key=${key} not registered`)
|
|
40
44
|
}
|
|
41
|
-
const
|
|
42
|
-
if (
|
|
43
|
-
const candidateVersions = Object.keys(
|
|
44
|
-
throw new Error(`language ${name} with key=${key} and version=${version} not
|
|
45
|
+
const base = version2base[version]
|
|
46
|
+
if (base === undefined) {
|
|
47
|
+
const candidateVersions = Object.keys(version2base)
|
|
48
|
+
throw new Error(`language ${name} with key=${key} and version=${version} not registered${candidateVersions.length > 0 ? `- candidate version${candidateVersions.length > 1 ? `s` : ``}: ${candidateVersions.join(", ")}` : ``}`)
|
|
45
49
|
}
|
|
46
|
-
return
|
|
50
|
+
return base
|
|
47
51
|
}
|
|
48
52
|
}
|
|
49
53
|
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @return a {@link NodeBaseFactory factory function} that works for all given {@link ILanguageBase language bases}.
|
|
57
|
+
*/
|
|
58
|
+
export const combinedFactoryFor = (languageBases: ILanguageBase[], receiveDelta?: DeltaReceiver): NodeBaseFactory => {
|
|
59
|
+
const baseOf = combinedLanguageBaseLookupFor(languageBases)
|
|
60
|
+
return (classifier, id) =>
|
|
61
|
+
baseOf(classifier.language).factory(receiveDelta)(classifier, id)
|
|
62
|
+
}
|
|
63
|
+
|
package/src/id-mapping.ts
CHANGED
|
@@ -20,6 +20,10 @@ import { LionWebId } from "@lionweb/json"
|
|
|
20
20
|
|
|
21
21
|
import { INodeBase } from "./index.js"
|
|
22
22
|
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Type def. for a (hash-)map {@link LionWebId ID} → {@link INodeBase}.
|
|
26
|
+
*/
|
|
23
27
|
type NodesById = { [id: LionWebId]: INodeBase }
|
|
24
28
|
|
|
25
29
|
|
|
@@ -30,31 +34,65 @@ type NodesById = { [id: LionWebId]: INodeBase }
|
|
|
30
34
|
*/
|
|
31
35
|
export class IdMapping {
|
|
32
36
|
|
|
33
|
-
nodesById: NodesById;
|
|
37
|
+
private nodesById: NodesById;
|
|
34
38
|
constructor(nodesById: NodesById) {
|
|
35
39
|
this.nodesById = {...nodesById};
|
|
36
40
|
}
|
|
37
41
|
// TODO consider using an instance of Map<Id, INodeBase> instead
|
|
38
42
|
|
|
39
|
-
|
|
43
|
+
/**
|
|
44
|
+
* @return the {@link INodeBase node} with the given {@link LionWebId `id`}, or
|
|
45
|
+
* @throws an Error if no node with the given ID was registered.
|
|
46
|
+
*/
|
|
47
|
+
fromId = (id: LionWebId): INodeBase => {
|
|
40
48
|
if (!(id in this.nodesById)) {
|
|
41
49
|
throw new Error(`node with id=${id} not in ID mapping`);
|
|
42
50
|
}
|
|
43
51
|
return this.nodesById[id];
|
|
44
52
|
}
|
|
45
53
|
|
|
54
|
+
/**
|
|
55
|
+
* @return the {@link INodeBase node} with the given {@link LionWebId `id`},
|
|
56
|
+
* or `undefined` if no node with the given ID was registered.
|
|
57
|
+
*/
|
|
46
58
|
tryFromId = (id: LionWebId): (INodeBase | undefined) =>
|
|
47
59
|
this.nodesById[id];
|
|
48
60
|
|
|
61
|
+
/**
|
|
62
|
+
* @return the {@link INodeBase node} referenced from the given {@link LionWebId ID},
|
|
63
|
+
* or `unresolved` if `unresolved` was passed in or no node with the given ID was registered.
|
|
64
|
+
*/
|
|
49
65
|
fromRefId = (idOrUnresolved: IdOrUnresolved): SingleRef<INodeBase> =>
|
|
50
|
-
idOrUnresolved ===
|
|
51
|
-
?
|
|
66
|
+
idOrUnresolved === unresolved
|
|
67
|
+
? unresolved
|
|
52
68
|
: (this.nodesById[idOrUnresolved] ?? unresolved);
|
|
53
69
|
|
|
54
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Updates this {@link IdMapping} with the given `node` *and all its descendants* (recursively).
|
|
72
|
+
*/
|
|
73
|
+
updateWith= (node: INodeBase) => {
|
|
55
74
|
this.nodesById[node.id] = node;
|
|
56
75
|
node.children // recurse into all children
|
|
57
76
|
.forEach((child) => this.updateWith(child));
|
|
77
|
+
// TODO figure out when it's really necessary to call this, as it's potentially *very* expensive
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Re-initializes this {@link IdMapping ID mapping} with the given nodes-by-ID.
|
|
82
|
+
* This completely removes all registrations of nodes,
|
|
83
|
+
* and should only be used by components which are in complete control of the nodes being passed to this method.
|
|
84
|
+
*/
|
|
85
|
+
reinitializeWith = (nodesById: NodesById) => {
|
|
86
|
+
this.nodesById = nodesById;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Merges the given `that` {@link IdMapping} **in**to `this`.
|
|
91
|
+
*/
|
|
92
|
+
mergeIn = (that: IdMapping) => {
|
|
93
|
+
for (const id in that.nodesById) {
|
|
94
|
+
this.nodesById[id] = that.nodesById[id];
|
|
95
|
+
}
|
|
58
96
|
}
|
|
59
97
|
|
|
60
98
|
}
|
package/src/textualizer.ts
CHANGED
|
@@ -54,7 +54,7 @@ export const asTreeTextWith = (identificationFor: (node: INodeBase) => string):
|
|
|
54
54
|
const valueManager = node.getPropertyValueManager(feature)
|
|
55
55
|
const displayValue = (() => {
|
|
56
56
|
if (!valueManager.isSet()) {
|
|
57
|
-
return
|
|
57
|
+
return `<not set>`
|
|
58
58
|
}
|
|
59
59
|
const value = valueManager.getDirectly()
|
|
60
60
|
if (feature.type === LionCore_builtinsBase.INSTANCE.String) {
|
|
@@ -17,11 +17,12 @@
|
|
|
17
17
|
|
|
18
18
|
import { action, observable } from "mobx"
|
|
19
19
|
|
|
20
|
-
import { INodeBase
|
|
20
|
+
import { INodeBase } from "../base-types.js"
|
|
21
21
|
import { checkIndex, ValueManager } from "./base.js"
|
|
22
22
|
import {
|
|
23
23
|
AnnotationAddedDelta,
|
|
24
24
|
AnnotationDeletedDelta,
|
|
25
|
+
AnnotationMovedAndReplacedInSameParentDelta,
|
|
25
26
|
AnnotationMovedFromOtherParentDelta,
|
|
26
27
|
AnnotationMovedInSameParentDelta,
|
|
27
28
|
AnnotationReplacedDelta
|
|
@@ -66,7 +67,7 @@ export class AnnotationsValueManager extends ValueManager {
|
|
|
66
67
|
newAnnotation.attachTo(this.container, null);
|
|
67
68
|
return false;
|
|
68
69
|
} else {
|
|
69
|
-
const oldIndex =
|
|
70
|
+
const oldIndex = oldParent.annotationsValueManager.removeDirectly(newAnnotation);
|
|
70
71
|
newAnnotation.attachTo(this.container, null);
|
|
71
72
|
return [oldParent, oldIndex];
|
|
72
73
|
}
|
|
@@ -119,6 +120,31 @@ export class AnnotationsValueManager extends ValueManager {
|
|
|
119
120
|
this.emitDelta(() => new AnnotationReplacedDelta(this.container, index, replacedAnnotation, newAnnotation));
|
|
120
121
|
}
|
|
121
122
|
|
|
123
|
+
/**
|
|
124
|
+
* @return the moved and replaced annotations, as an array tuple.
|
|
125
|
+
*/
|
|
126
|
+
@action moveAndReplaceAtIndexDirectly(oldIndex: number, newIndex: number): [INodeBase, INodeBase] | undefined {
|
|
127
|
+
checkIndex(oldIndex, this.annotations.length, false);
|
|
128
|
+
checkIndex(newIndex, this.annotations.length, false);
|
|
129
|
+
if (oldIndex !== newIndex) {
|
|
130
|
+
const movedAnnotation = this.annotations[oldIndex];
|
|
131
|
+
const replacedAnnotation = this.annotations[newIndex];
|
|
132
|
+
this.annotations[newIndex] = movedAnnotation;
|
|
133
|
+
this.annotations.splice(oldIndex, 1);
|
|
134
|
+
replacedAnnotation.detach();
|
|
135
|
+
return [movedAnnotation, replacedAnnotation];
|
|
136
|
+
}
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@action moveAndReplaceAtIndex(oldIndex: number, newIndex: number) {
|
|
141
|
+
const participants = this.moveAndReplaceAtIndexDirectly(oldIndex, newIndex);
|
|
142
|
+
if (participants !== undefined) {
|
|
143
|
+
const [movedAnnotation, replacedAnnotation] = participants;
|
|
144
|
+
this.emitDelta(() => new AnnotationMovedAndReplacedInSameParentDelta(this.container, oldIndex, newIndex, replacedAnnotation, movedAnnotation));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
122
148
|
@action removeDirectly(annotationToRemove: INodeBase): number {
|
|
123
149
|
const index = this.annotations.indexOf(annotationToRemove);
|
|
124
150
|
if (index > -1) {
|
|
@@ -29,12 +29,12 @@ export abstract class ValueManager {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* Emits a delta if a {@link
|
|
33
|
-
* @param deltaThunk a thunk that generates the delta, and is only called when a delta
|
|
32
|
+
* Emits a delta if a {@link DeltaReceiver} is registered with the container.
|
|
33
|
+
* @param deltaThunk a thunk that generates the delta, and is only called when a delta receiver is registered with the container.
|
|
34
34
|
*/
|
|
35
35
|
emitDelta(deltaThunk: () => IDelta) {
|
|
36
|
-
if (this.container.
|
|
37
|
-
this.container.
|
|
36
|
+
if (this.container.receiveDelta) {
|
|
37
|
+
this.container.receiveDelta(deltaThunk());
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -18,8 +18,18 @@
|
|
|
18
18
|
import { Containment } from "@lionweb/core"
|
|
19
19
|
import { action, observable } from "mobx"
|
|
20
20
|
|
|
21
|
-
import { INodeBase
|
|
22
|
-
import {
|
|
21
|
+
import { INodeBase } from "../base-types.js"
|
|
22
|
+
import {
|
|
23
|
+
ChildAddedDelta,
|
|
24
|
+
ChildDeletedDelta,
|
|
25
|
+
ChildMovedAndReplacedFromOtherContainmentDelta,
|
|
26
|
+
ChildMovedAndReplacedFromOtherContainmentInSameParentDelta,
|
|
27
|
+
ChildMovedAndReplacedInSameContainmentDelta,
|
|
28
|
+
ChildMovedFromOtherContainmentDelta,
|
|
29
|
+
ChildMovedFromOtherContainmentInSameParentDelta,
|
|
30
|
+
ChildMovedInSameContainmentDelta,
|
|
31
|
+
ChildReplacedDelta
|
|
32
|
+
} from "../deltas/index.js"
|
|
23
33
|
import { checkIndex, FeatureValueManager } from "./base.js"
|
|
24
34
|
|
|
25
35
|
|
|
@@ -68,11 +78,47 @@ export abstract class SingleContainmentValueManager<T extends INodeBase> extends
|
|
|
68
78
|
@action addDirectly(newChild: T) {
|
|
69
79
|
const oldChild = this.getDirectly();
|
|
70
80
|
if (oldChild !== undefined) {
|
|
71
|
-
throw new Error(`replacing a child using addDirectly on a value manager for a single-valued containment isn't allowed`); //
|
|
81
|
+
throw new Error(`replacing a child using addDirectly on a value manager for a single-valued containment isn't allowed`); // TODO unit test this
|
|
72
82
|
}
|
|
73
83
|
this.child.set(newChild);
|
|
74
84
|
}
|
|
75
85
|
|
|
86
|
+
@action replaceWith(movedChild: T) {
|
|
87
|
+
const replacedChild = this.getDirectly();
|
|
88
|
+
if (replacedChild === undefined) {
|
|
89
|
+
// not a proper replace, but an add-set => delegate to regular setter (— unfortunately, through necessarily “unfolding the hierarchy”):
|
|
90
|
+
if (this instanceof OptionalSingleContainmentValueManager) {
|
|
91
|
+
this.set(movedChild);
|
|
92
|
+
}
|
|
93
|
+
if (this instanceof RequiredSingleContainmentValueManager) {
|
|
94
|
+
this.set(movedChild);
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
if (replacedChild === movedChild) {
|
|
98
|
+
// do nothing: nothing's changed
|
|
99
|
+
} else {
|
|
100
|
+
if (movedChild.parent === undefined) {
|
|
101
|
+
this.emitDelta(() => new ChildReplacedDelta(this.container, this.feature, 0, replacedChild, movedChild));
|
|
102
|
+
} else {
|
|
103
|
+
if (movedChild.parent === this.container) {
|
|
104
|
+
if (movedChild.containment === this.containment) {
|
|
105
|
+
this.emitDelta(() => new ChildMovedAndReplacedInSameContainmentDelta(this.container, this.containment, 0, 0, movedChild, replacedChild));
|
|
106
|
+
} else {
|
|
107
|
+
const oldIndex = removeFromContainment(replacedChild);
|
|
108
|
+
this.emitDelta(() => new ChildMovedAndReplacedFromOtherContainmentInSameParentDelta(this.container, movedChild.containment!, oldIndex, this.containment, 0, movedChild, replacedChild));
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
const oldIndex = removeFromContainment(replacedChild);
|
|
112
|
+
this.emitDelta(() => new ChildMovedAndReplacedFromOtherContainmentDelta(this.container, this.containment, 0, movedChild, movedChild.parent!, movedChild.containment!, oldIndex, replacedChild));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
this.setDirectly(movedChild);
|
|
116
|
+
movedChild.attachTo(this.container, this.containment);
|
|
117
|
+
replacedChild.detach();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
76
122
|
}
|
|
77
123
|
|
|
78
124
|
|
|
@@ -95,8 +141,8 @@ export class OptionalSingleContainmentValueManager<T extends INodeBase> extends
|
|
|
95
141
|
} else {
|
|
96
142
|
if (newChild.parent && newChild.containment) {
|
|
97
143
|
const oldParent = newChild.parent;
|
|
98
|
-
|
|
99
|
-
this.emitDelta(() => new
|
|
144
|
+
removeFromContainment(newChild);
|
|
145
|
+
this.emitDelta(() => new ChildMovedFromOtherContainmentDelta(oldParent, newChild.containment!, 0, this.container, this.feature, 0, newChild));
|
|
100
146
|
} else {
|
|
101
147
|
this.emitDelta(() => new ChildAddedDelta(this.container, this.feature, 0, newChild));
|
|
102
148
|
}
|
|
@@ -112,13 +158,14 @@ export class OptionalSingleContainmentValueManager<T extends INodeBase> extends
|
|
|
112
158
|
if (oldChild === newChild) {
|
|
113
159
|
// do nothing: nothing's changed
|
|
114
160
|
} else {
|
|
161
|
+
// FIXME this could emit 2 deltas where it should be a single ChildReplaced-delta
|
|
115
162
|
if (oldChild.parent && oldChild.containment && oldChild.parent === this.container && oldChild.containment === this.feature) {
|
|
116
163
|
// FIXME oldChild.parent COULD be this.container
|
|
117
164
|
this.emitDelta(() => new ChildDeletedDelta(this.container, this.feature, 0, oldChild));
|
|
118
165
|
}
|
|
119
166
|
oldChild.detach();
|
|
120
167
|
if (newChild.parent && newChild.containment) {
|
|
121
|
-
|
|
168
|
+
removeFromContainment(newChild);
|
|
122
169
|
}
|
|
123
170
|
this.setDirectly(newChild);
|
|
124
171
|
newChild.attachTo(this.container, this.feature);
|
|
@@ -154,8 +201,8 @@ export class RequiredSingleContainmentValueManager<T extends INodeBase> extends
|
|
|
154
201
|
} else {
|
|
155
202
|
if (newChild.parent && newChild.containment) {
|
|
156
203
|
const oldParent = newChild.parent;
|
|
157
|
-
|
|
158
|
-
this.emitDelta(() => new
|
|
204
|
+
removeFromContainment(newChild);
|
|
205
|
+
this.emitDelta(() => new ChildMovedFromOtherContainmentDelta(oldParent, newChild.containment!, 0, this.container, this.feature, 0, newChild));
|
|
159
206
|
} else {
|
|
160
207
|
this.emitDelta(() => new ChildAddedDelta(this.container, this.feature, 0, newChild));
|
|
161
208
|
}
|
|
@@ -175,7 +222,7 @@ export class RequiredSingleContainmentValueManager<T extends INodeBase> extends
|
|
|
175
222
|
}
|
|
176
223
|
oldChild.detach();
|
|
177
224
|
if (newChild.parent && newChild.containment) {
|
|
178
|
-
|
|
225
|
+
removeFromContainment(newChild);
|
|
179
226
|
}
|
|
180
227
|
this.setDirectly(newChild);
|
|
181
228
|
newChild.attachTo(this.container, this.feature);
|
|
@@ -222,21 +269,36 @@ export abstract class MultiContainmentValueManager<T extends INodeBase> extends
|
|
|
222
269
|
this.children.splice(index, 0, newChild);
|
|
223
270
|
}
|
|
224
271
|
|
|
225
|
-
@action insertAtIndex(newChild: T,
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
272
|
+
@action insertAtIndex(newChild: T, newIndex: number) {
|
|
273
|
+
if (newChild.parent === undefined) {
|
|
274
|
+
this.insertAtIndexDirectly(newChild, newIndex);
|
|
275
|
+
newChild.attachTo(this.container, this.containment);
|
|
276
|
+
this.emitDelta(() => new ChildAddedDelta(this.container, this.containment, newIndex, newChild));
|
|
229
277
|
} else {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
278
|
+
if (newChild.parent === this.container) {
|
|
279
|
+
if (newChild.containment === this.containment) {
|
|
280
|
+
const oldIndex = this.children.indexOf(newChild);
|
|
281
|
+
this.moveDirectly(oldIndex, newIndex);
|
|
282
|
+
this.emitDelta(() => new ChildMovedInSameContainmentDelta(this.container, this.containment, oldIndex, newIndex, newChild));
|
|
283
|
+
} else {
|
|
284
|
+
const oldIndex = removeFromContainment(newChild);
|
|
285
|
+
checkIndex(newIndex, this.children.length, true);
|
|
286
|
+
this.insertAtIndexDirectly(newChild, newIndex);
|
|
287
|
+
this.emitDelta(() => new ChildMovedFromOtherContainmentInSameParentDelta(this.container, newChild.containment!, oldIndex, newChild, this.containment, newIndex));
|
|
288
|
+
newChild.attachTo(this.container, this.containment);
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
const oldIndex = removeFromContainment(newChild);
|
|
292
|
+
this.insertAtIndexDirectly(newChild, newIndex);
|
|
293
|
+
this.emitDelta(() => new ChildMovedFromOtherContainmentDelta(newChild.parent!, newChild.containment!, oldIndex, this.container, this.containment, newIndex, newChild));
|
|
294
|
+
newChild.attachTo(this.container, this.containment);
|
|
295
|
+
}
|
|
233
296
|
}
|
|
234
|
-
newChild.attachTo(this.container, this.containment);
|
|
235
297
|
}
|
|
236
298
|
|
|
237
299
|
@action removeDirectly(childToRemove: T): number {
|
|
238
300
|
const children = this.getDirectly();
|
|
239
|
-
const index = children.
|
|
301
|
+
const index = children.indexOf(childToRemove);
|
|
240
302
|
if (index > -1) {
|
|
241
303
|
children.splice(index, 1);
|
|
242
304
|
return index;
|
|
@@ -244,6 +306,11 @@ export abstract class MultiContainmentValueManager<T extends INodeBase> extends
|
|
|
244
306
|
return -1;
|
|
245
307
|
}
|
|
246
308
|
|
|
309
|
+
@action removeAtIndexDirectly(index: number) {
|
|
310
|
+
checkIndex(index, this.children.length, false);
|
|
311
|
+
this.getDirectly().splice(index, 1);
|
|
312
|
+
}
|
|
313
|
+
|
|
247
314
|
@action moveDirectly(oldIndex: number, newIndex: number): T | undefined {
|
|
248
315
|
checkIndex(oldIndex, this.children.length, false);
|
|
249
316
|
checkIndex(newIndex, this.children.length, false);
|
|
@@ -262,6 +329,39 @@ export abstract class MultiContainmentValueManager<T extends INodeBase> extends
|
|
|
262
329
|
}
|
|
263
330
|
}
|
|
264
331
|
|
|
332
|
+
@action replaceAtIndex(movedChild: T, newIndex: number) {
|
|
333
|
+
checkIndex(newIndex, this.children.length, false);
|
|
334
|
+
if (movedChild.parent === undefined) {
|
|
335
|
+
throw new Error(`a child-to-move (id=${movedChild.id}) must already be contained`);
|
|
336
|
+
}
|
|
337
|
+
const replacedChild = this.children[newIndex];
|
|
338
|
+
if (replacedChild === movedChild) {
|
|
339
|
+
// do nothing: nothing's changed
|
|
340
|
+
} else {
|
|
341
|
+
this.children.splice(newIndex, 1, movedChild);
|
|
342
|
+
if (replacedChild.parent !== undefined) {
|
|
343
|
+
const oldValueManager = replacedChild.parent.getContainmentValueManager(replacedChild.containment!);
|
|
344
|
+
const oldIndex = oldValueManager instanceof SingleContainmentValueManager
|
|
345
|
+
? 0
|
|
346
|
+
: (oldValueManager as MultiContainmentValueManager<INodeBase>).children.indexOf(replacedChild);
|
|
347
|
+
if (replacedChild.parent === movedChild.parent) {
|
|
348
|
+
if (replacedChild.containment === movedChild.containment) {
|
|
349
|
+
this.emitDelta(() => new ChildMovedAndReplacedInSameContainmentDelta(this.container, this.containment, oldIndex, newIndex, movedChild, replacedChild));
|
|
350
|
+
} else {
|
|
351
|
+
this.emitDelta(() => new ChildMovedAndReplacedFromOtherContainmentInSameParentDelta(this.container, replacedChild.containment!, oldIndex, this.containment, newIndex, movedChild, replacedChild));
|
|
352
|
+
}
|
|
353
|
+
} else {
|
|
354
|
+
this.emitDelta(() => new ChildMovedAndReplacedFromOtherContainmentDelta(this.container, this.containment, newIndex, movedChild, movedChild.parent!, movedChild.containment!, oldIndex, replacedChild));
|
|
355
|
+
}
|
|
356
|
+
replacedChild.detach();
|
|
357
|
+
} else {
|
|
358
|
+
// not a move+replace, but a regular replace instead:
|
|
359
|
+
this.emitDelta(() => new ChildReplacedDelta(this.container, this.containment, newIndex, replacedChild, movedChild));
|
|
360
|
+
}
|
|
361
|
+
movedChild.attachTo(this.container, this.containment);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
265
365
|
}
|
|
266
366
|
|
|
267
367
|
|
|
@@ -274,7 +374,7 @@ export class OptionalMultiContainmentValueManager<T extends INodeBase> extends M
|
|
|
274
374
|
|
|
275
375
|
@action remove(childToRemove: T) {
|
|
276
376
|
const children = this.getDirectly();
|
|
277
|
-
const index = children.
|
|
377
|
+
const index = children.indexOf(childToRemove);
|
|
278
378
|
if (index > -1) {
|
|
279
379
|
children.splice(index, 1);
|
|
280
380
|
childToRemove.detach();
|
|
@@ -302,7 +402,7 @@ export class RequiredMultiContainmentValueManager<T extends INodeBase> extends M
|
|
|
302
402
|
|
|
303
403
|
@action remove(childToRemove: T) {
|
|
304
404
|
const children = this.getDirectly();
|
|
305
|
-
const index = children.
|
|
405
|
+
const index = children.indexOf(childToRemove);
|
|
306
406
|
if (index > -1) {
|
|
307
407
|
if (children.length === 1) {
|
|
308
408
|
this.throwOnUnset();
|
|
@@ -315,3 +415,27 @@ export class RequiredMultiContainmentValueManager<T extends INodeBase> extends M
|
|
|
315
415
|
|
|
316
416
|
}
|
|
317
417
|
|
|
418
|
+
/**
|
|
419
|
+
* Removes the given child node from its containment, and returns the containment index it had before removal.
|
|
420
|
+
*/
|
|
421
|
+
const removeFromContainment = (child: INodeBase): number => {
|
|
422
|
+
if (child.parent === undefined) {
|
|
423
|
+
throw new Error(`can't remove an orphan from its parent`)
|
|
424
|
+
}
|
|
425
|
+
if (child.containment === undefined) {
|
|
426
|
+
throw new Error(`can't remove an orphan from its containing feature`)
|
|
427
|
+
}
|
|
428
|
+
if (child.containment === null) {
|
|
429
|
+
throw new Error(`can't remove an annotation`)
|
|
430
|
+
}
|
|
431
|
+
const valueManager = child.parent.getContainmentValueManager(child.containment)
|
|
432
|
+
if (valueManager instanceof SingleContainmentValueManager) {
|
|
433
|
+
valueManager.setDirectly(undefined)
|
|
434
|
+
return 0
|
|
435
|
+
} else if (valueManager instanceof MultiContainmentValueManager) {
|
|
436
|
+
return valueManager.removeDirectly(child)
|
|
437
|
+
} else {
|
|
438
|
+
throw new Error(`don't know how to remove a child that's contained through a value manager of type ${valueManager.constructor.name}`)
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
@@ -29,6 +29,7 @@ import { FeatureValueManager } from "./base.js"
|
|
|
29
29
|
export abstract class PropertyValueManager<T> extends FeatureValueManager<Property> {
|
|
30
30
|
|
|
31
31
|
private readonly observableValue: IObservableValue<T | undefined> = observable.box<T | undefined>(undefined, {deep: false});
|
|
32
|
+
// TODO could replace T with a sum types of the exact types we know a property's value can have
|
|
32
33
|
|
|
33
34
|
get property(): Property {
|
|
34
35
|
return this.feature;
|