arrmatura 6.2.1 → 6.3.1
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 +45 -0
- package/docs/glossary.md +19 -0
- package/docs/hello.md +46 -0
- package/docs/manual.md +294 -0
- package/index.ts +8 -7
- package/package.json +7 -5
- package/src/core/Component.ts +67 -0
- package/src/{ctx/Ctx.ts → core/Entitron.ts} +69 -87
- package/src/core/ManifestNode.ts +139 -0
- package/src/core/resolveExpression.ts +20 -0
- package/src/{core → registry}/composition.ts +17 -22
- package/src/{core → registry}/conditionals.ts +14 -19
- package/src/registry/elementary.ts +36 -0
- package/src/registry/fragment.ts +21 -0
- package/src/registry/index.ts +69 -0
- package/src/{core → registry}/iterations.ts +31 -30
- package/src/{core → registry}/root.ts +8 -8
- package/src/{core → registry}/routing.ts +7 -13
- package/src/{core → registry}/selection.ts +16 -16
- package/src/{core → registry}/slot.ts +9 -9
- package/src/utils/compileExpression.ts +16 -34
- package/src/utils/stringify.ts +3 -9
- package/types.ts +73 -0
- package/src/core/elementary.ts +0 -40
- package/src/core/fragment.ts +0 -21
- package/src/core/index.ts +0 -19
- package/src/ctx/CtxNode.ts +0 -159
|
@@ -1,58 +1,50 @@
|
|
|
1
1
|
import { areEqual, capitalize, identity } from "ultimus";
|
|
2
2
|
import { stringify } from "../utils/stringify";
|
|
3
3
|
import type { Delta, Fn, Hash, Proc, Uid } from "ultimus/types";
|
|
4
|
-
import type {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
ICtxNode,
|
|
8
|
-
IComponent,
|
|
9
|
-
IPlatform,
|
|
10
|
-
} from "arrmatura-api/types";
|
|
4
|
+
import type { IEntitron, IManifestNode, IComponent, IPlatform } from "arrmatura/types";
|
|
5
|
+
|
|
6
|
+
type EProcedure = (c: IEntitron) => void;
|
|
11
7
|
|
|
12
8
|
/**
|
|
13
|
-
* The
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
9
|
+
* The Entitron is the essential the framework concept.
|
|
10
|
+
*
|
|
11
|
+
* It creates a own component instance,
|
|
12
|
+
* manages its state and behavior,
|
|
13
|
+
* and ties it up with others.
|
|
17
14
|
*/
|
|
18
|
-
export class
|
|
15
|
+
export class Entitron<T extends IManifestNode = IManifestNode> implements IEntitron {
|
|
19
16
|
readonly component: IComponent;
|
|
20
|
-
$children?: Map<Uid,
|
|
21
|
-
readonly root:
|
|
22
|
-
readonly scope:
|
|
17
|
+
$children?: Map<Uid, IEntitron>;
|
|
18
|
+
readonly root: IEntitron;
|
|
19
|
+
readonly scope: IEntitron;
|
|
23
20
|
isDone = false;
|
|
24
21
|
|
|
25
22
|
#isInited = false;
|
|
26
23
|
#state: Hash = {};
|
|
27
24
|
#initialState: Hash = {};
|
|
28
|
-
#listeners?: Set<(c:
|
|
25
|
+
#listeners?: Set<(c: Entitron) => void>;
|
|
29
26
|
#defered?: EProcedure[] = [];
|
|
30
27
|
#weak?: Map<string, number>;
|
|
31
|
-
#refs?: Hash<
|
|
28
|
+
#refs?: Hash<IEntitron>;
|
|
32
29
|
#propFnMap?: Map<string, Proc>;
|
|
33
30
|
|
|
34
|
-
constructor(
|
|
35
|
-
readonly platform: IPlatform,
|
|
36
|
-
protected readonly node: T,
|
|
37
|
-
private _parent?: ICtx,
|
|
38
|
-
scope?: ICtx
|
|
39
|
-
) {
|
|
31
|
+
constructor(readonly platform: IPlatform, protected readonly manifest: T, private _parent?: IEntitron, scope?: IEntitron) {
|
|
40
32
|
this.scope = scope ?? this;
|
|
41
33
|
this.root = scope?.root ?? this;
|
|
42
|
-
this.#initialState = this.
|
|
34
|
+
this.#initialState = this.manifest.resolveInitialProps(this);
|
|
43
35
|
this.component = this.createComponent(this.#initialState);
|
|
44
36
|
|
|
45
37
|
if (this.refId) {
|
|
46
|
-
this.scope.
|
|
38
|
+
this.scope.addReference(this.refId, this);
|
|
47
39
|
}
|
|
48
40
|
}
|
|
49
41
|
|
|
50
|
-
get parent():
|
|
42
|
+
get parent(): IEntitron | undefined {
|
|
51
43
|
return this._parent;
|
|
52
44
|
}
|
|
53
45
|
|
|
54
46
|
get uid(): string {
|
|
55
|
-
return this.
|
|
47
|
+
return this.manifest.uid;
|
|
56
48
|
}
|
|
57
49
|
|
|
58
50
|
get displayName(): string {
|
|
@@ -60,6 +52,7 @@ export class Ctx<T extends ICtxNode = ICtxNode> implements ICtx {
|
|
|
60
52
|
}
|
|
61
53
|
|
|
62
54
|
createComponent(initials: Hash): IComponent {
|
|
55
|
+
// by default, a component is just a plain object.
|
|
63
56
|
return { ...initials };
|
|
64
57
|
}
|
|
65
58
|
|
|
@@ -80,11 +73,7 @@ export class Ctx<T extends ICtxNode = ICtxNode> implements ICtx {
|
|
|
80
73
|
Object.entries(Δ).forEach(([k, v]) => {
|
|
81
74
|
if (v instanceof Promise) {
|
|
82
75
|
this.upAsync(v, k);
|
|
83
|
-
} else if (
|
|
84
|
-
k &&
|
|
85
|
-
typeof v !== "undefined" &&
|
|
86
|
-
!areEqual(v, this.#state[k])
|
|
87
|
-
) {
|
|
76
|
+
} else if (k && typeof v !== "undefined" && !areEqual(v, this.#state[k])) {
|
|
88
77
|
this.#state[k] = v;
|
|
89
78
|
changes.set(k, v);
|
|
90
79
|
}
|
|
@@ -148,14 +137,9 @@ export class Ctx<T extends ICtxNode = ICtxNode> implements ICtx {
|
|
|
148
137
|
fn = () => val;
|
|
149
138
|
} else {
|
|
150
139
|
const gettr = impl[`get${capitalize(pk)}`];
|
|
151
|
-
const fn0 =
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
: () => impl[pk] ?? null;
|
|
155
|
-
|
|
156
|
-
fn = !path.length
|
|
157
|
-
? fn0
|
|
158
|
-
: () => path.reduce((r, p) => r?.[p] ?? null, fn0());
|
|
140
|
+
const fn0 = typeof gettr === "function" ? () => gettr.call(impl) ?? null : () => impl[pk] ?? null;
|
|
141
|
+
|
|
142
|
+
fn = !path.length ? fn0 : () => path.reduce((r, p) => r?.[p] ?? null, fn0());
|
|
159
143
|
}
|
|
160
144
|
}
|
|
161
145
|
|
|
@@ -175,7 +159,7 @@ export class Ctx<T extends ICtxNode = ICtxNode> implements ICtx {
|
|
|
175
159
|
}
|
|
176
160
|
|
|
177
161
|
// add listener to componet state changes
|
|
178
|
-
subscribe(handler: (src:
|
|
162
|
+
subscribe(handler: (src: Entitron) => any): Proc {
|
|
179
163
|
const listeners = this.#listeners ?? (this.#listeners = new Set());
|
|
180
164
|
listeners.add(handler);
|
|
181
165
|
return () => {
|
|
@@ -187,38 +171,46 @@ export class Ctx<T extends ICtxNode = ICtxNode> implements ICtx {
|
|
|
187
171
|
|
|
188
172
|
// emits action event to this component
|
|
189
173
|
emit(key: string, data: Delta): void {
|
|
190
|
-
if (!key || !key.includes(".")) {
|
|
191
|
-
this.up(key ? { [key]: data } : data);
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const [refId, target] = key.split(".");
|
|
196
174
|
try {
|
|
175
|
+
// when key.endsWith("...") it will produce empty target, so don't need to hande it peculiarly
|
|
176
|
+
let [refId, target] = key.split(".");
|
|
177
|
+
if (!target) {
|
|
178
|
+
target = refId;
|
|
179
|
+
refId = 'this'
|
|
180
|
+
}
|
|
181
|
+
|
|
197
182
|
const ref = this.getByRef(refId);
|
|
198
183
|
if (!ref) {
|
|
199
184
|
throw new Error(`No such reference: ${refId}`);
|
|
200
185
|
}
|
|
201
|
-
|
|
186
|
+
|
|
187
|
+
if (!target) { // spread '...'
|
|
202
188
|
ref.up(data);
|
|
203
189
|
return;
|
|
204
190
|
}
|
|
205
191
|
|
|
206
|
-
|
|
192
|
+
if (target[0] === "@") {
|
|
193
|
+
ref.up({ [target.slice(1)]: data });
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
207
197
|
const impl = ref.component;
|
|
208
|
-
const method = impl[
|
|
198
|
+
const method = impl[target] ?? impl[`on${capitalize(target)}`];
|
|
209
199
|
|
|
200
|
+
// deprecated. Use @-prefix instead.
|
|
210
201
|
if (!method) {
|
|
211
|
-
|
|
202
|
+
const propKey = target[0] === "@" ? target.slice(1) : target;
|
|
203
|
+
ref.up({ [propKey]: data });
|
|
212
204
|
return;
|
|
213
205
|
}
|
|
214
206
|
|
|
215
|
-
if (!
|
|
216
|
-
throw new Error(`
|
|
207
|
+
if (!(typeof method === "function")) {
|
|
208
|
+
throw new Error(`Not a method: ${refId}.${target}`);
|
|
217
209
|
}
|
|
218
210
|
|
|
219
211
|
const result = method.call(impl, data, impl, ref);
|
|
220
212
|
|
|
221
|
-
this.log(`-> ${refId}.${
|
|
213
|
+
this.log(`-> ${refId}.${target}`, result, data, impl);
|
|
222
214
|
|
|
223
215
|
ref.up(result);
|
|
224
216
|
} catch (ex) {
|
|
@@ -246,13 +238,13 @@ export class Ctx<T extends ICtxNode = ICtxNode> implements ICtx {
|
|
|
246
238
|
}
|
|
247
239
|
|
|
248
240
|
// map of children contexts
|
|
249
|
-
get children(): Map<Uid,
|
|
241
|
+
get children(): Map<Uid, IEntitron> | undefined {
|
|
250
242
|
return this.$children;
|
|
251
243
|
}
|
|
252
244
|
|
|
253
245
|
// map of chidren of its node
|
|
254
|
-
get
|
|
255
|
-
return this.
|
|
246
|
+
get contentManifests() {
|
|
247
|
+
return this.manifest.getSubNodes(this.platform);
|
|
256
248
|
}
|
|
257
249
|
|
|
258
250
|
// actual scope for re-content
|
|
@@ -270,13 +262,13 @@ export class Ctx<T extends ICtxNode = ICtxNode> implements ICtx {
|
|
|
270
262
|
|
|
271
263
|
this.up(this.component.init?.(this));
|
|
272
264
|
} else {
|
|
273
|
-
void this.up(this.
|
|
265
|
+
void this.up(this.manifest.resolveProps(this), true);
|
|
274
266
|
}
|
|
275
267
|
}
|
|
276
268
|
|
|
277
269
|
// perform children instantiation and updates
|
|
278
270
|
private recontent() {
|
|
279
|
-
const nodes = this.
|
|
271
|
+
const nodes = this.contentManifests;
|
|
280
272
|
|
|
281
273
|
this.platform.redraw(this, this.root);
|
|
282
274
|
|
|
@@ -286,12 +278,10 @@ export class Ctx<T extends ICtxNode = ICtxNode> implements ICtx {
|
|
|
286
278
|
}
|
|
287
279
|
});
|
|
288
280
|
|
|
289
|
-
const children = new Map<Uid,
|
|
281
|
+
const children = new Map<Uid, IEntitron>();
|
|
290
282
|
|
|
291
|
-
nodes?.forEach((node:
|
|
292
|
-
const e =
|
|
293
|
-
this.children?.get(uid) ??
|
|
294
|
-
node.createContext(this.platform, this, this.recontentScope);
|
|
283
|
+
nodes?.forEach((node: IManifestNode, uid: Uid) => {
|
|
284
|
+
const e = this.children?.get(uid) ?? node.createEntitron(this.platform, this, this.recontentScope);
|
|
295
285
|
children.set(e.uid, e);
|
|
296
286
|
});
|
|
297
287
|
|
|
@@ -305,11 +295,11 @@ export class Ctx<T extends ICtxNode = ICtxNode> implements ICtx {
|
|
|
305
295
|
// --- Referencing.
|
|
306
296
|
|
|
307
297
|
get refId() {
|
|
308
|
-
return this.
|
|
298
|
+
return this.manifest.refId;
|
|
309
299
|
}
|
|
310
300
|
|
|
311
301
|
// gets any context instance in current or upper scope by key
|
|
312
|
-
getByRef(refId: string):
|
|
302
|
+
getByRef(refId: string): IEntitron | undefined {
|
|
313
303
|
if (!refId) return undefined;
|
|
314
304
|
|
|
315
305
|
if (refId === "this") {
|
|
@@ -320,41 +310,39 @@ export class Ctx<T extends ICtxNode = ICtxNode> implements ICtx {
|
|
|
320
310
|
return local;
|
|
321
311
|
}
|
|
322
312
|
const ref = this === this.scope ? null : this.scope.getByRef(refId);
|
|
323
|
-
return ref as
|
|
313
|
+
return ref as Entitron;
|
|
324
314
|
}
|
|
325
315
|
|
|
326
|
-
|
|
316
|
+
addReference(refId: string, c: IEntitron) {
|
|
327
317
|
(this.#refs ?? (this.#refs = {}))[refId] = c;
|
|
328
318
|
}
|
|
329
319
|
|
|
330
320
|
// --- Routines -------------------------
|
|
331
321
|
|
|
332
322
|
private initConnectors() {
|
|
333
|
-
if (this.
|
|
334
|
-
for (const [key, pipes] of this.
|
|
323
|
+
if (this.manifest.connectors) {
|
|
324
|
+
for (const [key, pipes] of this.manifest.connectors.entries()) {
|
|
335
325
|
const [srcIds, targetPropName] = key.split("|");
|
|
336
|
-
const [refId,
|
|
326
|
+
const [refId, sourcePropName] = srcIds.split(".");
|
|
337
327
|
|
|
338
|
-
const ref = this.scope.getByRef(refId) as
|
|
328
|
+
const ref = this.scope.getByRef(refId) as Entitron;
|
|
339
329
|
if (!ref) {
|
|
340
330
|
this.logError(`Connect: No such ref: ${refId}`);
|
|
341
331
|
continue;
|
|
342
332
|
}
|
|
343
333
|
|
|
344
|
-
if (!
|
|
334
|
+
if (!sourcePropName) {
|
|
345
335
|
this.logError(`Connect: No prop specified: ${key}`);
|
|
346
336
|
continue;
|
|
347
337
|
}
|
|
348
338
|
|
|
349
|
-
const
|
|
350
|
-
const applicator = targetPropName
|
|
351
|
-
? (rr: any) => ({ [targetPropName]: rr })
|
|
352
|
-
: identity;
|
|
339
|
+
const propName = sourcePropName[0] === '@' ? sourcePropName.slice(1) : sourcePropName;
|
|
340
|
+
const applicator = targetPropName ? (rr: any) => ({ [targetPropName]: rr }) : identity;
|
|
353
341
|
|
|
354
342
|
this.defer(
|
|
355
|
-
ref.subscribe((source:
|
|
343
|
+
ref.subscribe((source: Entitron) => {
|
|
356
344
|
try {
|
|
357
|
-
const r = applicator(pipes(this, source.get(
|
|
345
|
+
const r = applicator(pipes(this, source.get(propName)));
|
|
358
346
|
this.up(r as Delta);
|
|
359
347
|
} catch (ex) {
|
|
360
348
|
source.logError("Notify ", ex);
|
|
@@ -363,10 +351,7 @@ export class Ctx<T extends ICtxNode = ICtxNode> implements ICtx {
|
|
|
363
351
|
);
|
|
364
352
|
|
|
365
353
|
// hot subscription
|
|
366
|
-
Object.assign(
|
|
367
|
-
this.#initialState,
|
|
368
|
-
applicator(pipes(ref, ref.get(propId)))
|
|
369
|
-
);
|
|
354
|
+
Object.assign(this.#initialState, applicator(pipes(ref, ref.get(propName))));
|
|
370
355
|
}
|
|
371
356
|
}
|
|
372
357
|
}
|
|
@@ -405,10 +390,7 @@ export class Ctx<T extends ICtxNode = ICtxNode> implements ICtx {
|
|
|
405
390
|
level: "error",
|
|
406
391
|
source: `${this.displayName}:${this.uid}`,
|
|
407
392
|
error,
|
|
408
|
-
message: `${String(error.message || error)}\n${this.toString().slice(
|
|
409
|
-
0,
|
|
410
|
-
120
|
|
411
|
-
)}`,
|
|
393
|
+
message: `${String(error.message || error)}\n${this.toString().slice(0, 120)}`,
|
|
412
394
|
params: args,
|
|
413
395
|
});
|
|
414
396
|
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { nextId } from "ultimus";
|
|
2
|
+
import { compilePipeExpression } from "../utils/compileExpression";
|
|
3
|
+
import type { Delta, ExpressionText, Hash, Uid } from "ultimus/types";
|
|
4
|
+
import type { IPlatform, IEntitron, IManifestNode } from "arrmatura/types";
|
|
5
|
+
import { resolveExpression } from "./resolveExpression";
|
|
6
|
+
|
|
7
|
+
type PropertyResolver = (c: IEntitron, acc: Delta) => Delta;
|
|
8
|
+
|
|
9
|
+
type PropertyGetter = (c: IEntitron) => unknown;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* ManifestNode is a abstract factory for Entitron.
|
|
13
|
+
*
|
|
14
|
+
* Each specific kind of Entitron has its own descending implementation of ManifestNode.
|
|
15
|
+
*/
|
|
16
|
+
export abstract class ManifestNode implements IManifestNode {
|
|
17
|
+
protected $content?: Map<Uid, IManifestNode> | undefined;
|
|
18
|
+
uid: Uid = nextId("N");
|
|
19
|
+
refId?: string;
|
|
20
|
+
private readonly propertyResolvers: PropertyResolver[] = [];
|
|
21
|
+
private initialState: Hash<(c: IEntitron) => any> = {};
|
|
22
|
+
connectors?: Map<string, (c: IEntitron, x: unknown) => unknown>;
|
|
23
|
+
tag: any;
|
|
24
|
+
|
|
25
|
+
abstract get EntitronConstructor(): new (platform: IPlatform, node: typeof this, parent?: IEntitron, scope?: IEntitron) => IEntitron;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Create a context object.
|
|
29
|
+
*
|
|
30
|
+
* @param {IPlatform} platform - The platform object.
|
|
31
|
+
* @param {IEntitron} parent - The parent context object (optional).
|
|
32
|
+
* @param {IEntitron} scope - The scope context object (optional).
|
|
33
|
+
* @return {IEntitron} The created context object.
|
|
34
|
+
*/
|
|
35
|
+
createEntitron(platform: IPlatform, parent?: IEntitron, scope?: IEntitron): IEntitron {
|
|
36
|
+
return new this.EntitronConstructor(platform, this, parent, scope);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getSubNodes(_platform: IPlatform): Map<Uid, IManifestNode> | undefined {
|
|
40
|
+
return this.$content;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
addPropertyResolver(getter: PropertyGetter, propKey: string) {
|
|
44
|
+
this.propertyResolvers.push((c: IEntitron, delta: Delta) => {
|
|
45
|
+
delta[propKey] = getter(c);
|
|
46
|
+
return delta;
|
|
47
|
+
});
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private addDataPropertyResolver(getter: PropertyGetter, propKey: string) {
|
|
52
|
+
this.propertyResolvers.push((c: IEntitron, delta: Delta) => {
|
|
53
|
+
const val = getter(c);
|
|
54
|
+
delta.data = Object.assign((delta.data as object) || {}, {
|
|
55
|
+
[propKey]: val,
|
|
56
|
+
});
|
|
57
|
+
return delta;
|
|
58
|
+
});
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private addPropertiesResolver(getter: PropertyGetter) {
|
|
63
|
+
this.propertyResolvers.push((c: IEntitron, delta: Delta) => {
|
|
64
|
+
const value = getter(c);
|
|
65
|
+
if (value && typeof value === "object") {
|
|
66
|
+
Object.entries(value).forEach(([key, val]) => {
|
|
67
|
+
delta[key] = val;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return delta;
|
|
71
|
+
});
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
addConnector(expr: ExpressionText, propName: string) {
|
|
76
|
+
const { key, pipec } = compilePipeExpression(expr);
|
|
77
|
+
(this.connectors ?? (this.connectors = new Map())).set(`${key}|${propName}`, pipec);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private addEmitter(expr: ExpressionText, k: string) {
|
|
81
|
+
const { key, pipec } = compilePipeExpression(expr);
|
|
82
|
+
this.initialState[k] = (c) => (data: unknown) => c.scope?.emit(key, pipec(c, data) as Delta);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
resolveInitialProps(c: IEntitron) {
|
|
86
|
+
return this.propertyResolvers.reduce(
|
|
87
|
+
(delta, resolver) => resolver(c, delta),
|
|
88
|
+
Object.entries(this.initialState).reduce((acc, [key, fn]) => {
|
|
89
|
+
acc[key] = fn(c);
|
|
90
|
+
return acc;
|
|
91
|
+
}, {} as Hash)
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
resolveProps(c: IEntitron) {
|
|
96
|
+
return this.propertyResolvers.reduce((delta, resolver) => resolver(c, delta), {});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
compileAttribute(k: string, v: unknown) {
|
|
100
|
+
if (k.startsWith("data-")) {
|
|
101
|
+
this.addDataPropertyResolver(resolveExpression(v), k.slice(5));
|
|
102
|
+
} else if (k === "ref") {
|
|
103
|
+
this.refId = String(v);
|
|
104
|
+
} else if (k === "props" || k.startsWith('{...')) {
|
|
105
|
+
const sv = k.startsWith('{...') ? k.slice(4, -1) : String(v);
|
|
106
|
+
if (sv[0] === "<" && sv[1] === "-") {
|
|
107
|
+
this.addConnector(sv.slice(2), "");
|
|
108
|
+
} else {
|
|
109
|
+
this.addPropertiesResolver(resolveExpression(sv));
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
if (typeof v !== "string") {
|
|
113
|
+
this.initialState[k] = () => v;
|
|
114
|
+
} else {
|
|
115
|
+
if (v[0] === "<" && v[1] === "-") {
|
|
116
|
+
this.addConnector(v.slice(2), k);
|
|
117
|
+
} else if (v[0] === "-" && v[1] === ">") {
|
|
118
|
+
this.addEmitter(v.slice(2), k);
|
|
119
|
+
} else {
|
|
120
|
+
if (!v.includes("{") && v[0] !== "@") {
|
|
121
|
+
this.initialState[k] = () => v;
|
|
122
|
+
} else {
|
|
123
|
+
if (v[0] === "@" && v[1] === "@" && v.match(/^@@[a-z0-9\-_.]+$/i) != null) {
|
|
124
|
+
this.initialState[k] = (c) => c.platform.getResource(v.slice(2));
|
|
125
|
+
} else {
|
|
126
|
+
this.addPropertyResolver(resolveExpression(v), k);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
compileAttributes(attrs?: Hash) {
|
|
135
|
+
if (attrs) {
|
|
136
|
+
Object.entries(attrs).forEach((keyValue) => this.compileAttribute(...keyValue));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createStringInterpolator } from "ultimus/src/parsing/createStringInterpolator";
|
|
2
|
+
import { compileConstant, compileJsExpression, compilePlaceholder } from "../utils/compileExpression";
|
|
3
|
+
|
|
4
|
+
const CACHE = new Map();
|
|
5
|
+
|
|
6
|
+
function compileExpression(v: unknown) {
|
|
7
|
+
if (typeof v !== "string") return () => v;
|
|
8
|
+
|
|
9
|
+
if (v[0] === "@") return compilePlaceholder(v);
|
|
10
|
+
|
|
11
|
+
if (v.startsWith("js:")) return compileJsExpression(v.slice(3));
|
|
12
|
+
|
|
13
|
+
if (v.includes("{")) return createStringInterpolator(v.replace(/\s+/g, " "), compilePlaceholder);
|
|
14
|
+
|
|
15
|
+
return compileConstant(v);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const resolveExpression = (x: unknown) => {
|
|
19
|
+
return CACHE.has(x) ? CACHE.get(x) : CACHE.set(x, compileExpression(x)).get(x);
|
|
20
|
+
}
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { ManifestNode } from "../core/ManifestNode";
|
|
2
|
+
import { Entitron } from "../core/Entitron";
|
|
3
3
|
import type { Hash, XmlNode } from "ultimus/types";
|
|
4
|
-
import type { EContent,
|
|
4
|
+
import type { EContent, IManifestNode, IPlatform } from "arrmatura/types";
|
|
5
5
|
|
|
6
|
-
export class Composite extends
|
|
6
|
+
export class Composite extends Entitron<CCompositeNode> {
|
|
7
7
|
get displayName(): string {
|
|
8
|
-
return `💠${this.
|
|
8
|
+
return `💠${this.manifest.tag}`;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
get recontentScope():
|
|
11
|
+
get recontentScope(): Entitron {
|
|
12
12
|
return this;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
slotContent(slotId?: string): EContent | undefined {
|
|
16
|
-
return this.
|
|
16
|
+
return this.manifest.getSlotContent(this.platform, slotId);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
createComponent(initials: Hash) {
|
|
20
|
-
return this.platform.createComponent(this.
|
|
20
|
+
return this.platform.createComponent(this.manifest.tag, initials, this);
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export class CCompositeNode extends
|
|
24
|
+
export class CCompositeNode extends ManifestNode {
|
|
25
25
|
slotContent: Hash<EContent> | null = null;
|
|
26
26
|
tag: string;
|
|
27
27
|
nodes?: XmlNode["nodes"];
|
|
28
|
-
#content
|
|
28
|
+
#content?: Map<string, IManifestNode>;
|
|
29
29
|
|
|
30
30
|
constructor({ tag, attrs, nodes }: XmlNode) {
|
|
31
31
|
super();
|
|
@@ -43,29 +43,24 @@ export class CCompositeNode extends CtxNode {
|
|
|
43
43
|
gId = e.tag.split(":")[1];
|
|
44
44
|
isContainer = true;
|
|
45
45
|
}
|
|
46
|
-
groups[gId] = (groups[gId] || []).concat(
|
|
47
|
-
isContainer ? e.nodes ?? [] : e
|
|
48
|
-
);
|
|
46
|
+
groups[gId] = (groups[gId] || []).concat(isContainer ? e.nodes ?? [] : e);
|
|
49
47
|
return groups;
|
|
50
48
|
}, {});
|
|
51
49
|
|
|
52
|
-
this.slotContent = Object.entries(slots ?? {}).reduce<Hash<EContent>>(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
},
|
|
57
|
-
{}
|
|
58
|
-
);
|
|
50
|
+
this.slotContent = Object.entries(slots ?? {}).reduce<Hash<EContent>>((groups, [gId, nodes]) => {
|
|
51
|
+
groups[gId] = platform.getCompiledNodes(nodes);
|
|
52
|
+
return groups;
|
|
53
|
+
}, {});
|
|
59
54
|
}
|
|
60
55
|
|
|
61
56
|
return this.slotContent[slotId ?? "default"] ?? undefined;
|
|
62
57
|
}
|
|
63
58
|
|
|
64
|
-
|
|
59
|
+
getSubNodes(platform: IPlatform) {
|
|
65
60
|
return this.#content ?? (this.#content = platform.getCompiledNodes(this.tag));
|
|
66
61
|
}
|
|
67
62
|
|
|
68
|
-
get
|
|
63
|
+
get EntitronConstructor() {
|
|
69
64
|
return Composite;
|
|
70
65
|
}
|
|
71
66
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ManifestNode } from "../core/ManifestNode";
|
|
2
2
|
import { Composite } from "./composition";
|
|
3
|
-
import {
|
|
4
|
-
import type { EContent,
|
|
3
|
+
import { Entitron } from "../core/Entitron";
|
|
4
|
+
import type { EContent, IEntitron, IPlatform } from "arrmatura/types";
|
|
5
5
|
import type { XmlNode } from "ultimus/types";
|
|
6
6
|
import { compilePlaceholder } from "../utils/compileExpression";
|
|
7
7
|
|
|
8
|
-
class Conditional extends
|
|
9
|
-
get
|
|
10
|
-
const branches = this.
|
|
8
|
+
class Conditional extends Entitron<CIfNode> {
|
|
9
|
+
get contentManifests() {
|
|
10
|
+
const branches = this.manifest.getBranches(this.platform);
|
|
11
11
|
return this.component.condition ? branches.then : branches.else;
|
|
12
12
|
}
|
|
13
13
|
|
|
@@ -16,7 +16,7 @@ class Conditional extends Ctx<CIfNode> {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export class CIfNode extends
|
|
19
|
+
export class CIfNode extends ManifestNode {
|
|
20
20
|
#xml: XmlNode;
|
|
21
21
|
#branches?: { then?: EContent; else?: EContent };
|
|
22
22
|
constructor(xml: XmlNode, expr: string) {
|
|
@@ -25,7 +25,7 @@ export class CIfNode extends CtxNode {
|
|
|
25
25
|
this.compileCondition(expr);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
get
|
|
28
|
+
get EntitronConstructor() {
|
|
29
29
|
return Conditional;
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -43,16 +43,14 @@ export class CIfNode extends CtxNode {
|
|
|
43
43
|
const ifElse = nodes.find((e) => e.tag === "Else");
|
|
44
44
|
const ifThen = nodes.find((e) => e.tag === "Then");
|
|
45
45
|
if (ifElse) {
|
|
46
|
-
this.#branches.else = ifElse.nodes
|
|
47
|
-
? platform.getCompiledNodes(ifElse.nodes)
|
|
48
|
-
: undefined;
|
|
46
|
+
this.#branches.else = ifElse.nodes ? platform.getCompiledNodes(ifElse.nodes) : undefined;
|
|
49
47
|
$then = ifThen
|
|
50
48
|
? ifThen.nodes
|
|
51
49
|
: [
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
Object.assign(this.#xml, {
|
|
51
|
+
nodes: nodes.filter((e) => e !== ifElse),
|
|
52
|
+
}),
|
|
53
|
+
];
|
|
56
54
|
} else if (ifThen) {
|
|
57
55
|
$then = ifThen.nodes;
|
|
58
56
|
}
|
|
@@ -68,10 +66,7 @@ export class CIfNode extends CtxNode {
|
|
|
68
66
|
} else if (expr.slice(0, 5) === "slot(") {
|
|
69
67
|
const slotId = expr.slice(5, -1);
|
|
70
68
|
// DO NOT place inside iterations.
|
|
71
|
-
this.addPropertyResolver(
|
|
72
|
-
(c: ICtx) => !!(c.scope as Composite).slotContent?.(slotId)?.size,
|
|
73
|
-
"condition"
|
|
74
|
-
);
|
|
69
|
+
this.addPropertyResolver((c: IEntitron) => !!(c.scope as Composite).slotContent?.(slotId)?.size, "condition");
|
|
75
70
|
} else {
|
|
76
71
|
const resolver = compilePlaceholder(expr);
|
|
77
72
|
this.addPropertyResolver((c) => !!resolver(c), "condition");
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ManifestNode } from "../core/ManifestNode";
|
|
2
|
+
import { Entitron } from "../core/Entitron";
|
|
3
|
+
import type { Hash, XmlNode } from "ultimus/types";
|
|
4
|
+
import { IPlatform } from "arrmatura/types";
|
|
5
|
+
|
|
6
|
+
class NativeElementEntitron extends Entitron<CElementNode> {
|
|
7
|
+
get displayName(): string {
|
|
8
|
+
return this.manifest.tag;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
createComponent(initials: Hash) {
|
|
12
|
+
return this.platform.createComponent({ tag: this.manifest.tag, native: true }, initials, this);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class CElementNode extends ManifestNode {
|
|
17
|
+
#nodes: XmlNode[] | undefined;
|
|
18
|
+
|
|
19
|
+
constructor({ tag, attrs, nodes, text }: XmlNode) {
|
|
20
|
+
super();
|
|
21
|
+
this.tag = tag;
|
|
22
|
+
this.compileAttributes(attrs);
|
|
23
|
+
if (text) {
|
|
24
|
+
this.compileAttribute("#text", text);
|
|
25
|
+
}
|
|
26
|
+
this.#nodes = nodes;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getSubNodes(platform: IPlatform) {
|
|
30
|
+
return this.#nodes ? platform.getCompiledNodes(this.#nodes) : undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get EntitronConstructor() {
|
|
34
|
+
return NativeElementEntitron;
|
|
35
|
+
}
|
|
36
|
+
}
|