arrmatura 6.3.1 → 6.3.2
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/README.md +6 -10
- package/dist/index.js +2 -0
- package/dist/index.js.map +7 -0
- package/docs/manual.md +84 -66
- package/index.ts +5 -4
- package/package.json +10 -4
- package/src/core/{Entitron.ts → Arrmatron.ts} +147 -136
- package/src/core/Component.ts +44 -21
- package/src/core/ManifestNode.ts +36 -25
- package/src/core/resolveExpression.ts +9 -4
- package/src/registry/composition.ts +9 -8
- package/src/registry/conditionals.ts +10 -8
- package/src/registry/connector.ts +47 -0
- package/src/registry/elementary.ts +7 -6
- package/src/registry/fragment.ts +7 -6
- package/src/registry/index.ts +21 -25
- package/src/registry/iterations.ts +30 -18
- package/src/registry/root.ts +8 -6
- package/src/registry/routing.ts +6 -5
- package/src/registry/selection.ts +11 -9
- package/src/registry/slot.ts +8 -7
- package/src/utils/FingerprintMashine.ts +128 -0
- package/src/utils/applyStateChangedToImpl.ts +18 -0
- package/src/utils/compileExpression.ts +136 -29
- package/src/utils/objectFingerprint.spec.ts +70 -0
- package/types.ts +38 -29
|
@@ -1,45 +1,44 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import type { Delta, Fn, Hash, Proc, Uid } from "ultimus/types";
|
|
4
|
-
import type { IEntitron, IManifestNode, IComponent, IPlatform } from "arrmatura/types";
|
|
1
|
+
import { capitalize, identity } from "ultimus";
|
|
2
|
+
import type { Delta, Fn, Hash, LogEntry, Proc, Uid } from "ultimus/types";
|
|
5
3
|
|
|
6
|
-
type
|
|
4
|
+
import type { IArrmatron, IManifestNode, IComponent, IPlatform } from "../../types";
|
|
5
|
+
import { objectFingerprint } from "../utils/FingerprintMashine";
|
|
6
|
+
import { applyStateChangedToImpl } from "../utils/applyStateChangedToImpl";
|
|
7
|
+
import { stringify } from "../utils/stringify";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
|
-
* The
|
|
10
|
-
*
|
|
11
|
-
* It creates a own component instance,
|
|
12
|
-
* manages its state and behavior,
|
|
13
|
-
* and ties it up with others.
|
|
10
|
+
* The Arrmatron is the essential concept of the framework.
|
|
11
|
+
* It creates a component instance, manages its state and keeps in touch with others.
|
|
14
12
|
*/
|
|
15
|
-
export class
|
|
16
|
-
readonly component: IComponent;
|
|
17
|
-
|
|
18
|
-
readonly
|
|
19
|
-
readonly scope: IEntitron;
|
|
20
|
-
isDone = false;
|
|
13
|
+
export class Arrmatron<T extends IManifestNode = IManifestNode> implements IArrmatron {
|
|
14
|
+
readonly $component: IComponent;
|
|
15
|
+
readonly root: IArrmatron;
|
|
16
|
+
readonly scope: Arrmatron;
|
|
21
17
|
|
|
18
|
+
$children?: Map<Uid, Arrmatron>;
|
|
19
|
+
isDone = false;
|
|
22
20
|
#isInited = false;
|
|
23
|
-
#
|
|
21
|
+
#fprints: Hash = {};
|
|
24
22
|
#initialState: Hash = {};
|
|
25
|
-
#listeners?: Set<(c:
|
|
26
|
-
#defered?:
|
|
23
|
+
#listeners?: Set<(c: Arrmatron) => void>;
|
|
24
|
+
#defered?: Array<(c: IArrmatron) => void> = [];
|
|
27
25
|
#weak?: Map<string, number>;
|
|
28
|
-
#refs?: Hash<
|
|
26
|
+
#refs?: Hash<IArrmatron>;
|
|
29
27
|
#propFnMap?: Map<string, Proc>;
|
|
30
28
|
|
|
31
|
-
constructor(readonly platform: IPlatform, protected readonly manifest: T, private _parent?:
|
|
29
|
+
constructor(readonly platform: IPlatform, protected readonly manifest: T, private _parent?: Arrmatron, scope?: Arrmatron) {
|
|
32
30
|
this.scope = scope ?? this;
|
|
33
31
|
this.root = scope?.root ?? this;
|
|
34
32
|
this.#initialState = this.manifest.resolveInitialProps(this);
|
|
35
|
-
this
|
|
33
|
+
this.$component = this.createComponent(this.#initialState);
|
|
36
34
|
|
|
37
35
|
if (this.refId) {
|
|
36
|
+
// this.log('addReference', this.refId)
|
|
38
37
|
this.scope.addReference(this.refId, this);
|
|
39
38
|
}
|
|
40
39
|
}
|
|
41
40
|
|
|
42
|
-
get parent():
|
|
41
|
+
get parent(): IArrmatron | undefined {
|
|
43
42
|
return this._parent;
|
|
44
43
|
}
|
|
45
44
|
|
|
@@ -59,64 +58,67 @@ export class Entitron<T extends IManifestNode = IManifestNode> implements IEntit
|
|
|
59
58
|
// --- State
|
|
60
59
|
|
|
61
60
|
// updates component state
|
|
62
|
-
up(
|
|
63
|
-
if (
|
|
61
|
+
up(delta?: Delta | Promise<any> | null | void | unknown, force = false): void {
|
|
62
|
+
if (!delta || this.isDone) {
|
|
64
63
|
return;
|
|
65
64
|
}
|
|
66
65
|
|
|
67
|
-
if (
|
|
68
|
-
this.
|
|
66
|
+
if (delta instanceof Promise) {
|
|
67
|
+
const racer = this.raceCondition(`set`);
|
|
68
|
+
delta.then((val) => racer(() => this.up(val)));
|
|
69
69
|
return;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
const changes = new Map<string, unknown>();
|
|
73
|
-
Object.entries(
|
|
73
|
+
Object.entries(delta).forEach(([k, v]) => {
|
|
74
74
|
if (v instanceof Promise) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
this
|
|
78
|
-
|
|
75
|
+
const isSpreading = !k || k.startsWith("...");
|
|
76
|
+
const racer = this.raceCondition(`set:${k}`);
|
|
77
|
+
void v.then((val) => racer(() => this.up(isSpreading ? val : { [k]: val })));
|
|
78
|
+
|
|
79
|
+
} else if (k && typeof v !== "undefined") {
|
|
80
|
+
if (k[0] === "$") {
|
|
81
|
+
if (this.$component[k] !== v) {
|
|
82
|
+
changes.set(k.slice(1), v);
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
|
|
86
|
+
const fprint = objectFingerprint(v);
|
|
87
|
+
if (!(k in this.#fprints) || fprint !== this.#fprints[k]) {
|
|
88
|
+
this.#fprints[k] = fprint;
|
|
89
|
+
changes.set(k, v);
|
|
90
|
+
// } else {
|
|
91
|
+
// if (v && typeof v === "object" && v !== this.get(k)) {
|
|
92
|
+
|
|
93
|
+
// this.log('same skip', k, '\n ', v, '==', this.get(k), '\n ', this.#fprints[k], '==', fprint)
|
|
94
|
+
// }
|
|
95
|
+
}
|
|
96
|
+
}
|
|
79
97
|
}
|
|
80
98
|
});
|
|
81
99
|
|
|
82
100
|
if (changes.size || force) {
|
|
83
|
-
|
|
101
|
+
applyStateChangedToImpl(changes, this.$component);
|
|
102
|
+
this.touch();
|
|
84
103
|
}
|
|
85
104
|
}
|
|
86
105
|
|
|
87
|
-
private upAsync(promise: Promise<any>, key: string): void {
|
|
88
|
-
const racer = this.raceCondition(`set:${key}`);
|
|
89
|
-
if (key[0] !== "*") {
|
|
90
|
-
const akey = key.replace("Promise", "");
|
|
91
|
-
void promise.then((val) => racer(() => this.up({ [akey]: val })));
|
|
92
|
-
} else {
|
|
93
|
-
void promise.then((r: Delta) => racer(() => this.up(r)));
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
private stateChanged(changes: Map<string, unknown>) {
|
|
98
|
-
if (this.component.stateChanged) {
|
|
99
|
-
this.component.stateChanged(changes);
|
|
100
|
-
} else {
|
|
101
|
-
changes.forEach((v, k) => {
|
|
102
|
-
const setter = this.component[`set${capitalize(k)}`];
|
|
103
|
-
if (typeof setter === "function") {
|
|
104
|
-
setter.call(this.component, v);
|
|
105
|
-
} else {
|
|
106
|
-
this.component[k] = v;
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
this.touch();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
106
|
// do recontent and notify all
|
|
114
107
|
touch() {
|
|
115
108
|
this.recontent();
|
|
116
109
|
this.notify();
|
|
117
110
|
}
|
|
118
111
|
|
|
119
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Retrieves the component value associated with the given property ID.
|
|
114
|
+
* Dot-sepated pathes to properties are supported.
|
|
115
|
+
* Supported getters like `get<Name>()`.
|
|
116
|
+
* use '@' prefix to access platform resources.
|
|
117
|
+
* Functional values are bounded and cached.
|
|
118
|
+
*
|
|
119
|
+
* @param {string} propId - The ID of the property.
|
|
120
|
+
* @return {unknown} The value associated with the property ID.
|
|
121
|
+
*/
|
|
120
122
|
get(propId: string): unknown {
|
|
121
123
|
const map = this.#propFnMap ?? (this.#propFnMap = new Map());
|
|
122
124
|
if (map.has(propId)) {
|
|
@@ -124,15 +126,17 @@ export class Entitron<T extends IManifestNode = IManifestNode> implements IEntit
|
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
let fn: Fn | null = null;
|
|
127
|
-
const impl = this
|
|
129
|
+
const impl = this.$component;
|
|
128
130
|
const instant = impl[propId];
|
|
131
|
+
|
|
129
132
|
if (instant && typeof instant === "function") {
|
|
130
133
|
const bound = instant.bind(impl);
|
|
131
134
|
fn = () => bound;
|
|
135
|
+
} else if (impl.__getProperty) {
|
|
136
|
+
fn = () => (impl.__getProperty as (p: string) => unknown)(propId);
|
|
132
137
|
} else {
|
|
133
138
|
const [pk, ...path] = propId.split(".");
|
|
134
|
-
if (pk
|
|
135
|
-
path.unshift(pk.slice(1));
|
|
139
|
+
if (pk === "R") {
|
|
136
140
|
const val = this.platform.getResource(path) ?? null;
|
|
137
141
|
fn = () => val;
|
|
138
142
|
} else {
|
|
@@ -159,7 +163,7 @@ export class Entitron<T extends IManifestNode = IManifestNode> implements IEntit
|
|
|
159
163
|
}
|
|
160
164
|
|
|
161
165
|
// add listener to componet state changes
|
|
162
|
-
subscribe(handler: (src:
|
|
166
|
+
subscribe(handler: (src: Arrmatron) => any): Proc {
|
|
163
167
|
const listeners = this.#listeners ?? (this.#listeners = new Set());
|
|
164
168
|
listeners.add(handler);
|
|
165
169
|
return () => {
|
|
@@ -171,62 +175,60 @@ export class Entitron<T extends IManifestNode = IManifestNode> implements IEntit
|
|
|
171
175
|
|
|
172
176
|
// emits action event to this component
|
|
173
177
|
emit(key: string, data: Delta): void {
|
|
178
|
+
if (this.isDone) return;
|
|
179
|
+
|
|
174
180
|
try {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
+
if (key.endsWith(")")) {
|
|
182
|
+
const [refId, target] = key.split("(")[0].split(".");
|
|
183
|
+
const ref = this.getByRef(refId);
|
|
184
|
+
if (!ref) {
|
|
185
|
+
throw new Error(`No such reference: ${refId}`);
|
|
186
|
+
}
|
|
181
187
|
|
|
182
|
-
|
|
183
|
-
if (!ref) {
|
|
184
|
-
throw new Error(`No such reference: ${refId}`);
|
|
185
|
-
}
|
|
188
|
+
const impl = ref.$component;
|
|
186
189
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
190
|
+
const method = impl[target];
|
|
191
|
+
if (!(typeof method === "function")) {
|
|
192
|
+
throw new Error(`Not a method: ${refId}.${target}()`);
|
|
193
|
+
}
|
|
191
194
|
|
|
192
|
-
|
|
193
|
-
ref.up({ [target.slice(1)]: data });
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
195
|
+
const result = method.call(impl, data);
|
|
196
196
|
|
|
197
|
-
|
|
198
|
-
const method = impl[target] ?? impl[`on${capitalize(target)}`];
|
|
197
|
+
// this.log(`-> ${refId}.${target}(data)`, result, data, impl);
|
|
199
198
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
199
|
+
ref.up(result);
|
|
200
|
+
} else {
|
|
201
|
+
let [refId, target] = key.split(".");
|
|
202
|
+
if (!target) {
|
|
203
|
+
target = refId;
|
|
204
|
+
refId = "this";
|
|
205
|
+
}
|
|
206
206
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
207
|
+
const ref = this.getByRef(refId);
|
|
208
|
+
if (!ref) {
|
|
209
|
+
throw new Error(`No such reference: ${refId}`);
|
|
210
|
+
}
|
|
210
211
|
|
|
211
|
-
|
|
212
|
+
ref.up(target === "*" ? data : { [target]: data });
|
|
212
213
|
|
|
213
|
-
|
|
214
|
+
// this.log(`-> ${refId}.${target} = `, data);
|
|
215
|
+
}
|
|
214
216
|
|
|
215
|
-
ref.up(result);
|
|
216
217
|
} catch (ex) {
|
|
217
218
|
this.logError(`emit ${key}:`, ex);
|
|
218
219
|
}
|
|
219
220
|
return;
|
|
220
221
|
}
|
|
222
|
+
// --- life-cycle.
|
|
221
223
|
|
|
222
|
-
// Done
|
|
224
|
+
// Done hook.
|
|
223
225
|
done() {
|
|
224
226
|
if (this.isDone) {
|
|
225
227
|
return;
|
|
226
228
|
}
|
|
227
229
|
this.isDone = true;
|
|
228
230
|
|
|
229
|
-
this
|
|
231
|
+
this.$component.done?.(this);
|
|
230
232
|
|
|
231
233
|
this.children?.forEach((c) => c.done());
|
|
232
234
|
|
|
@@ -237,22 +239,14 @@ export class Entitron<T extends IManifestNode = IManifestNode> implements IEntit
|
|
|
237
239
|
this._parent = undefined;
|
|
238
240
|
}
|
|
239
241
|
|
|
240
|
-
//
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
// map of chidren of its node
|
|
246
|
-
get contentManifests() {
|
|
247
|
-
return this.manifest.getSubNodes(this.platform);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// actual scope for re-content
|
|
251
|
-
get recontentScope() {
|
|
252
|
-
return this.scope;
|
|
242
|
+
// register callback to be called on done
|
|
243
|
+
defer(fn: (c: IArrmatron) => void) {
|
|
244
|
+
if (fn && typeof fn === "function") {
|
|
245
|
+
(this.#defered ?? (this.#defered = [])).push(fn);
|
|
246
|
+
}
|
|
253
247
|
}
|
|
254
248
|
|
|
255
|
-
settleAsChild() {
|
|
249
|
+
private settleAsChild() {
|
|
256
250
|
if (!this.#isInited) {
|
|
257
251
|
this.#isInited = true;
|
|
258
252
|
|
|
@@ -260,12 +254,29 @@ export class Entitron<T extends IManifestNode = IManifestNode> implements IEntit
|
|
|
260
254
|
|
|
261
255
|
this.up(this.#initialState, true);
|
|
262
256
|
|
|
263
|
-
this.up(this
|
|
257
|
+
this.up(this.$component.init?.(this));
|
|
264
258
|
} else {
|
|
265
259
|
void this.up(this.manifest.resolveProps(this), true);
|
|
266
260
|
}
|
|
267
261
|
}
|
|
268
262
|
|
|
263
|
+
// --- Content.
|
|
264
|
+
|
|
265
|
+
// map of children contexts
|
|
266
|
+
get children(): Map<Uid, IArrmatron> | undefined {
|
|
267
|
+
return this.$children;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// map of chidren of its node
|
|
271
|
+
get contentManifests() {
|
|
272
|
+
return this.manifest.getSubNodes(this.platform);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// actual scope for re-content
|
|
276
|
+
get recontentScope() {
|
|
277
|
+
return this.scope;
|
|
278
|
+
}
|
|
279
|
+
|
|
269
280
|
// perform children instantiation and updates
|
|
270
281
|
private recontent() {
|
|
271
282
|
const nodes = this.contentManifests;
|
|
@@ -278,10 +289,10 @@ export class Entitron<T extends IManifestNode = IManifestNode> implements IEntit
|
|
|
278
289
|
}
|
|
279
290
|
});
|
|
280
291
|
|
|
281
|
-
const children = new Map<Uid,
|
|
292
|
+
const children = new Map<Uid, Arrmatron>();
|
|
282
293
|
|
|
283
294
|
nodes?.forEach((node: IManifestNode, uid: Uid) => {
|
|
284
|
-
const e = this
|
|
295
|
+
const e = this.$children?.get(uid) ?? node.createArrmatron(this.platform, this, this.recontentScope);
|
|
285
296
|
children.set(e.uid, e);
|
|
286
297
|
});
|
|
287
298
|
|
|
@@ -299,7 +310,7 @@ export class Entitron<T extends IManifestNode = IManifestNode> implements IEntit
|
|
|
299
310
|
}
|
|
300
311
|
|
|
301
312
|
// gets any context instance in current or upper scope by key
|
|
302
|
-
getByRef(refId: string):
|
|
313
|
+
getByRef(refId: string): IArrmatron | undefined {
|
|
303
314
|
if (!refId) return undefined;
|
|
304
315
|
|
|
305
316
|
if (refId === "this") {
|
|
@@ -309,15 +320,14 @@ export class Entitron<T extends IManifestNode = IManifestNode> implements IEntit
|
|
|
309
320
|
if (local) {
|
|
310
321
|
return local;
|
|
311
322
|
}
|
|
312
|
-
|
|
313
|
-
return ref as Entitron;
|
|
323
|
+
return this.parent?.getByRef(refId);
|
|
314
324
|
}
|
|
315
325
|
|
|
316
|
-
addReference(refId: string, c:
|
|
326
|
+
private addReference(refId: string, c: IArrmatron) {
|
|
317
327
|
(this.#refs ?? (this.#refs = {}))[refId] = c;
|
|
318
328
|
}
|
|
319
329
|
|
|
320
|
-
// ---
|
|
330
|
+
// --- Connectors -------------------------
|
|
321
331
|
|
|
322
332
|
private initConnectors() {
|
|
323
333
|
if (this.manifest.connectors) {
|
|
@@ -325,7 +335,7 @@ export class Entitron<T extends IManifestNode = IManifestNode> implements IEntit
|
|
|
325
335
|
const [srcIds, targetPropName] = key.split("|");
|
|
326
336
|
const [refId, sourcePropName] = srcIds.split(".");
|
|
327
337
|
|
|
328
|
-
const ref = this.
|
|
338
|
+
const ref = this.parent?.getByRef(refId) as Arrmatron;
|
|
329
339
|
if (!ref) {
|
|
330
340
|
this.logError(`Connect: No such ref: ${refId}`);
|
|
331
341
|
continue;
|
|
@@ -336,26 +346,35 @@ export class Entitron<T extends IManifestNode = IManifestNode> implements IEntit
|
|
|
336
346
|
continue;
|
|
337
347
|
}
|
|
338
348
|
|
|
339
|
-
const propName = sourcePropName[0] === '@' ? sourcePropName.slice(1) : sourcePropName;
|
|
340
349
|
const applicator = targetPropName ? (rr: any) => ({ [targetPropName]: rr }) : identity;
|
|
341
350
|
|
|
342
|
-
this.defer(
|
|
343
|
-
|
|
351
|
+
this.defer((() => {
|
|
352
|
+
let popre;
|
|
353
|
+
return ref.subscribe(async (source: Arrmatron) => {
|
|
344
354
|
try {
|
|
345
|
-
const
|
|
355
|
+
const val = await source.get(sourcePropName);
|
|
356
|
+
const fingerprint = objectFingerprint(val);
|
|
357
|
+
|
|
358
|
+
if (popre !== undefined && popre === fingerprint) return;
|
|
359
|
+
|
|
360
|
+
popre = fingerprint;
|
|
361
|
+
const r = applicator(pipes(this, val));
|
|
346
362
|
this.up(r as Delta);
|
|
347
363
|
} catch (ex) {
|
|
348
364
|
source.logError("Notify ", ex);
|
|
349
365
|
}
|
|
350
366
|
})
|
|
367
|
+
})()
|
|
351
368
|
);
|
|
352
369
|
|
|
353
370
|
// hot subscription
|
|
354
|
-
Object.assign(this.#initialState, applicator(pipes(ref, ref.get(
|
|
371
|
+
Object.assign(this.#initialState, applicator(pipes(ref, ref.get(sourcePropName))));
|
|
355
372
|
}
|
|
356
373
|
}
|
|
357
374
|
}
|
|
358
375
|
|
|
376
|
+
// --- Routines -------------------------
|
|
377
|
+
|
|
359
378
|
raceCondition(key: string) {
|
|
360
379
|
const COUNTERS = this.#weak ?? (this.#weak = new Map<string, number>());
|
|
361
380
|
let counter = 1 + (COUNTERS.get(key) ?? 0);
|
|
@@ -368,13 +387,6 @@ export class Entitron<T extends IManifestNode = IManifestNode> implements IEntit
|
|
|
368
387
|
};
|
|
369
388
|
}
|
|
370
389
|
|
|
371
|
-
// register callback to be called on done
|
|
372
|
-
defer(fn: EProcedure) {
|
|
373
|
-
if (fn && typeof fn === "function") {
|
|
374
|
-
(this.#defered ?? (this.#defered = [])).push(fn);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
390
|
log(val: any, ...args: unknown[]) {
|
|
379
391
|
this.platform.log({
|
|
380
392
|
level: "log",
|
|
@@ -395,9 +407,8 @@ export class Entitron<T extends IManifestNode = IManifestNode> implements IEntit
|
|
|
395
407
|
});
|
|
396
408
|
}
|
|
397
409
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
return this.platform.getResource(key);
|
|
410
|
+
toast = (t: LogEntry) => {
|
|
411
|
+
this.platform.toast(t, this);
|
|
401
412
|
}
|
|
402
413
|
|
|
403
414
|
toString(): string {
|
package/src/core/Component.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type { Delta, Hash, LogEntry } from "ultimus/types";
|
|
2
|
+
|
|
3
|
+
import type { IComponent, IArrmatron } from "../../types";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Base Ancestor for custom components.
|
|
@@ -7,61 +8,83 @@ import type { IComponent, IEntitron } from "../../types";
|
|
|
7
8
|
export abstract class Component implements IComponent {
|
|
8
9
|
[key: string]: unknown;
|
|
9
10
|
|
|
10
|
-
constructor(_: Hash, readonly ctx:
|
|
11
|
+
constructor(_: Hash, private readonly $ctx: IArrmatron) {
|
|
11
12
|
//no-op
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
get platform() {
|
|
15
|
-
return this
|
|
16
|
+
return this.$ctx.platform;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
get isDone() {
|
|
19
|
-
return this
|
|
20
|
+
return this.$ctx.isDone;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
// hook on done
|
|
23
|
-
done(_:
|
|
24
|
+
done(_: IArrmatron): void {
|
|
24
25
|
//no-op
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
// hook on init
|
|
28
29
|
// returned value will be used to update state
|
|
29
|
-
init(_:
|
|
30
|
+
init(_: IArrmatron): Delta | null | undefined | unknown {
|
|
30
31
|
return undefined;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
//
|
|
34
|
+
// get from ctx state
|
|
35
|
+
get(key: string) {
|
|
36
|
+
return this.$ctx.get(key);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// update ctx state
|
|
34
40
|
up(d: Delta) {
|
|
35
|
-
return this
|
|
41
|
+
return this.$ctx.up(d);
|
|
36
42
|
}
|
|
43
|
+
|
|
37
44
|
touch() {
|
|
38
|
-
return this
|
|
45
|
+
return this.$ctx.touch();
|
|
39
46
|
}
|
|
47
|
+
|
|
40
48
|
// emit action event to another component by key
|
|
41
49
|
emit(key: string, data: Delta) {
|
|
42
|
-
return this
|
|
50
|
+
return this.$ctx.emit(key, data);
|
|
43
51
|
}
|
|
44
52
|
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
// register callback to be called on done
|
|
54
|
+
defer(fn: () => void) {
|
|
55
|
+
this.$ctx.defer(fn);
|
|
48
56
|
}
|
|
49
57
|
|
|
50
58
|
// register callback to be called on done
|
|
51
|
-
|
|
52
|
-
|
|
59
|
+
defineCalculatedProperty(key, fn: (...args: any[]) => any, deps?: string[]) {
|
|
60
|
+
let depKey = "";
|
|
61
|
+
let depValue = undefined;
|
|
62
|
+
Object.defineProperty(this, key, {
|
|
63
|
+
get() {
|
|
64
|
+
const args: any[] = deps?.length ? deps.map((k) => this.$ctx.get(k)) : [];
|
|
65
|
+
const newKey = args.join(':');
|
|
66
|
+
if (depKey !== newKey) {
|
|
67
|
+
depKey = newKey;
|
|
68
|
+
depValue = fn.apply(this, args);
|
|
69
|
+
}
|
|
70
|
+
return depValue;
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
toast(entry: LogEntry) {
|
|
76
|
+
this.$ctx.toast(entry);
|
|
53
77
|
}
|
|
54
78
|
|
|
55
79
|
log(val: unknown, ...args: unknown[]) {
|
|
56
|
-
this
|
|
80
|
+
this.$ctx.log(val, ...args);
|
|
57
81
|
}
|
|
58
82
|
|
|
59
83
|
logError(val: unknown, ...args: unknown[]) {
|
|
60
|
-
this
|
|
84
|
+
this.$ctx.logError(val, ...args);
|
|
61
85
|
}
|
|
62
86
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
void this.emit("toasters.send", typeof message === "string" ? { message } : message);
|
|
87
|
+
toString() {
|
|
88
|
+
return this.$ctx.toString();
|
|
66
89
|
}
|
|
67
90
|
}
|