@mmapp/react 0.1.0-alpha.18 → 0.1.0-alpha.19
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/dist/actions-MFI2V4DX.mjs +116 -0
- package/dist/atoms/index.d.mts +2 -2
- package/dist/atoms/index.d.ts +2 -2
- package/dist/atoms/index.js +1 -1
- package/dist/atoms/index.mjs +1 -1
- package/dist/builtin-atoms-C-sNyYJl.d.mts +647 -0
- package/dist/builtin-atoms-C-sNyYJl.d.ts +647 -0
- package/dist/builtin-atoms-DCKrjG7i.d.mts +96 -0
- package/dist/builtin-atoms-DCKrjG7i.d.ts +96 -0
- package/dist/builtin-atoms-DRD3EwG6.d.mts +648 -0
- package/dist/builtin-atoms-DRD3EwG6.d.ts +648 -0
- package/dist/builtin-atoms-jt04b7Rw.d.mts +643 -0
- package/dist/builtin-atoms-jt04b7Rw.d.ts +643 -0
- package/dist/chunk-247T4GDJ.mjs +677 -0
- package/dist/chunk-3H6CR7E7.mjs +1924 -0
- package/dist/chunk-3PL6FL6I.mjs +96 -0
- package/dist/chunk-3SJSW3C4.mjs +2039 -0
- package/dist/chunk-5OI2VI57.mjs +1964 -0
- package/dist/chunk-CL6FYZ43.mjs +105 -0
- package/dist/chunk-ENQOCZI5.mjs +1938 -0
- package/dist/chunk-FB3WCZAU.mjs +512 -0
- package/dist/chunk-GLJ7VC7Z.mjs +684 -0
- package/dist/chunk-HHMWR6NA.mjs +504 -0
- package/dist/chunk-HULEMSN2.mjs +120 -0
- package/dist/chunk-J5MW6CRU.mjs +1938 -0
- package/dist/chunk-PNTTKNYU.mjs +677 -0
- package/dist/chunk-TY5OTJP4.mjs +684 -0
- package/dist/chunk-WV7DVCP6.mjs +513 -0
- package/dist/chunk-YFMPTGUF.mjs +677 -0
- package/dist/{chunk-2VJQJM7S.mjs → chunk-ZDWACXZN.mjs} +1 -1
- package/dist/composition-BJ6QQTWT.mjs +12 -0
- package/dist/composition-XBGKKCI7.mjs +57 -0
- package/dist/content-QVPFUG4P.mjs +246 -0
- package/dist/control-flow-CBREHWJW.mjs +35 -0
- package/dist/control-flow-FWBOI6SM.mjs +35 -0
- package/dist/control-flow-ZWUGCDSP.mjs +35 -0
- package/dist/data-WCMIZYKD.mjs +97 -0
- package/dist/grouping-E6F377VZ.mjs +204 -0
- package/dist/grouping-FRPOEXO3.mjs +233 -0
- package/dist/index.d.mts +4 -433
- package/dist/index.d.ts +4 -433
- package/dist/index.js +3648 -581
- package/dist/index.mjs +335 -1040
- package/dist/input-PUOZDNSI.mjs +222 -0
- package/dist/layout-RATDMCLP.mjs +106 -0
- package/dist/navigation-VCT7ZBMA.mjs +15 -0
- package/dist/navigation-WFV7YWOU.mjs +14 -0
- package/dist/player/index.d.mts +37 -11
- package/dist/player/index.d.ts +37 -11
- package/dist/player/index.js +3280 -174
- package/dist/player/index.mjs +55 -5
- package/package.json +4 -4
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
// src/player/expression-engine.ts
|
|
2
|
+
var _wasmModule = null;
|
|
3
|
+
var _wasmAvailable = false;
|
|
4
|
+
function setWasmModule(module) {
|
|
5
|
+
_wasmModule = module;
|
|
6
|
+
_wasmAvailable = module != null;
|
|
7
|
+
}
|
|
8
|
+
var LRUCache = class {
|
|
9
|
+
constructor(maxSize) {
|
|
10
|
+
this.maxSize = maxSize;
|
|
11
|
+
}
|
|
12
|
+
map = /* @__PURE__ */ new Map();
|
|
13
|
+
get(key) {
|
|
14
|
+
const val = this.map.get(key);
|
|
15
|
+
if (val !== void 0) {
|
|
16
|
+
this.map.delete(key);
|
|
17
|
+
this.map.set(key, val);
|
|
18
|
+
}
|
|
19
|
+
return val;
|
|
20
|
+
}
|
|
21
|
+
set(key, value) {
|
|
22
|
+
if (this.map.has(key)) this.map.delete(key);
|
|
23
|
+
this.map.set(key, value);
|
|
24
|
+
if (this.map.size > this.maxSize) {
|
|
25
|
+
const first = this.map.keys().next().value;
|
|
26
|
+
if (first !== void 0) this.map.delete(first);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
has(key) {
|
|
30
|
+
return this.map.has(key);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
var classificationCache = new LRUCache(500);
|
|
34
|
+
function classifyExpression(expr) {
|
|
35
|
+
const cached = classificationCache.get(expr);
|
|
36
|
+
if (cached) return cached;
|
|
37
|
+
const cls = classifyUncached(expr);
|
|
38
|
+
classificationCache.set(expr, cls);
|
|
39
|
+
return cls;
|
|
40
|
+
}
|
|
41
|
+
function classifyUncached(expr) {
|
|
42
|
+
const t = expr.trim();
|
|
43
|
+
if (t === "[Expression]" || t.includes("[Expression]")) return "literal";
|
|
44
|
+
if (t.startsWith("$expr(") && t.endsWith(")")) return "condition";
|
|
45
|
+
if (t.startsWith('"') && t.endsWith('"') || t.startsWith("'") && t.endsWith("'")) return "literal";
|
|
46
|
+
if (/^-?\d+(\.\d+)?$/.test(t)) return "literal";
|
|
47
|
+
if (t === "true" || t === "false" || t === "null" || t === "undefined") return "literal";
|
|
48
|
+
if (t === "[]" || t === "{}") return "literal";
|
|
49
|
+
if (/^\$fn\.\w+\(/.test(t)) return "function";
|
|
50
|
+
if (/^\$action\.\w+\(/.test(t)) return "action";
|
|
51
|
+
if (t.startsWith("$action.") && !t.includes("(")) return "action";
|
|
52
|
+
if (t.startsWith("$") && !/[!=<>&|+\-*/%?:]/.test(t) && !t.includes("(")) return "path";
|
|
53
|
+
if (/^[a-zA-Z_]\w*(\.\w+)+$/.test(t) && !/[!=<>&|+\-*/%?:]/.test(t)) return "path";
|
|
54
|
+
if (/^[a-zA-Z_]\w*$/.test(t)) return "path";
|
|
55
|
+
return "condition";
|
|
56
|
+
}
|
|
57
|
+
function resolvePath(path, scope) {
|
|
58
|
+
const parts = path.split(".");
|
|
59
|
+
let current = scope;
|
|
60
|
+
for (const part of parts) {
|
|
61
|
+
if (current == null) return void 0;
|
|
62
|
+
if (typeof current !== "object") return void 0;
|
|
63
|
+
const asNum = Number(part);
|
|
64
|
+
if (Array.isArray(current) && !isNaN(asNum)) {
|
|
65
|
+
current = current[asNum];
|
|
66
|
+
} else {
|
|
67
|
+
current = current[part];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return current;
|
|
71
|
+
}
|
|
72
|
+
var builtinFunctions = {
|
|
73
|
+
// Array
|
|
74
|
+
filter: (arr, fn) => {
|
|
75
|
+
if (!Array.isArray(arr)) return [];
|
|
76
|
+
if (typeof fn === "function") return arr.filter(fn);
|
|
77
|
+
return arr;
|
|
78
|
+
},
|
|
79
|
+
map: (arr, fn) => {
|
|
80
|
+
if (!Array.isArray(arr)) return [];
|
|
81
|
+
if (typeof fn === "function") return arr.map(fn);
|
|
82
|
+
return arr;
|
|
83
|
+
},
|
|
84
|
+
reduce: (arr, fn, init) => {
|
|
85
|
+
if (!Array.isArray(arr)) return init;
|
|
86
|
+
if (typeof fn === "function") return arr.reduce(fn, init);
|
|
87
|
+
return init;
|
|
88
|
+
},
|
|
89
|
+
find: (arr, fn) => {
|
|
90
|
+
if (!Array.isArray(arr)) return void 0;
|
|
91
|
+
if (typeof fn === "function") return arr.find(fn);
|
|
92
|
+
return void 0;
|
|
93
|
+
},
|
|
94
|
+
includes: (arr, item) => {
|
|
95
|
+
if (Array.isArray(arr)) return arr.includes(item);
|
|
96
|
+
if (typeof arr === "string") return arr.includes(String(item));
|
|
97
|
+
return false;
|
|
98
|
+
},
|
|
99
|
+
indexOf: (arr, item) => {
|
|
100
|
+
if (Array.isArray(arr)) return arr.indexOf(item);
|
|
101
|
+
if (typeof arr === "string") return arr.indexOf(String(item));
|
|
102
|
+
return -1;
|
|
103
|
+
},
|
|
104
|
+
slice: (arr, start, end) => {
|
|
105
|
+
if (Array.isArray(arr) || typeof arr === "string") {
|
|
106
|
+
return arr.slice(Number(start) || 0, end != null ? Number(end) : void 0);
|
|
107
|
+
}
|
|
108
|
+
return arr;
|
|
109
|
+
},
|
|
110
|
+
sort: (arr, fn) => {
|
|
111
|
+
if (!Array.isArray(arr)) return [];
|
|
112
|
+
const copy = [...arr];
|
|
113
|
+
if (typeof fn === "function") return copy.sort(fn);
|
|
114
|
+
return copy.sort();
|
|
115
|
+
},
|
|
116
|
+
reverse: (arr) => {
|
|
117
|
+
if (!Array.isArray(arr)) return [];
|
|
118
|
+
return [...arr].reverse();
|
|
119
|
+
},
|
|
120
|
+
unique: (arr) => {
|
|
121
|
+
if (!Array.isArray(arr)) return [];
|
|
122
|
+
return [...new Set(arr)];
|
|
123
|
+
},
|
|
124
|
+
flat: (arr, depth) => {
|
|
125
|
+
if (!Array.isArray(arr)) return [];
|
|
126
|
+
return arr.flat(Number(depth) || 1);
|
|
127
|
+
},
|
|
128
|
+
concat: (a, b) => {
|
|
129
|
+
if (Array.isArray(a)) return a.concat(Array.isArray(b) ? b : [b]);
|
|
130
|
+
if (typeof a === "string") return a + String(b ?? "");
|
|
131
|
+
return [a, b];
|
|
132
|
+
},
|
|
133
|
+
any: (arr, fn) => {
|
|
134
|
+
if (!Array.isArray(arr)) return false;
|
|
135
|
+
if (typeof fn === "function") return arr.some(fn);
|
|
136
|
+
return arr.length > 0;
|
|
137
|
+
},
|
|
138
|
+
all: (arr, fn) => {
|
|
139
|
+
if (!Array.isArray(arr)) return false;
|
|
140
|
+
if (typeof fn === "function") return arr.every(fn);
|
|
141
|
+
return false;
|
|
142
|
+
},
|
|
143
|
+
group_by: (arr, key) => {
|
|
144
|
+
if (!Array.isArray(arr) || typeof key !== "string") return {};
|
|
145
|
+
const groups = {};
|
|
146
|
+
for (const item of arr) {
|
|
147
|
+
const k = String(item?.[key] ?? "undefined");
|
|
148
|
+
(groups[k] ??= []).push(item);
|
|
149
|
+
}
|
|
150
|
+
return groups;
|
|
151
|
+
},
|
|
152
|
+
count: (arr) => Array.isArray(arr) ? arr.length : 0,
|
|
153
|
+
sum: (arr, key) => {
|
|
154
|
+
if (!Array.isArray(arr)) return 0;
|
|
155
|
+
if (typeof key === "string") {
|
|
156
|
+
return arr.reduce((s, item) => s + (Number(item?.[key]) || 0), 0);
|
|
157
|
+
}
|
|
158
|
+
return arr.reduce((s, v) => s + (Number(v) || 0), 0);
|
|
159
|
+
},
|
|
160
|
+
avg: (arr, key) => {
|
|
161
|
+
if (!Array.isArray(arr) || arr.length === 0) return 0;
|
|
162
|
+
const s = builtinFunctions.sum(arr, key);
|
|
163
|
+
return s / arr.length;
|
|
164
|
+
},
|
|
165
|
+
min: (...args) => {
|
|
166
|
+
if (args.length === 1 && Array.isArray(args[0])) return Math.min(...args[0].map(Number));
|
|
167
|
+
return Math.min(...args.map(Number));
|
|
168
|
+
},
|
|
169
|
+
max: (...args) => {
|
|
170
|
+
if (args.length === 1 && Array.isArray(args[0])) return Math.max(...args[0].map(Number));
|
|
171
|
+
return Math.max(...args.map(Number));
|
|
172
|
+
},
|
|
173
|
+
// String
|
|
174
|
+
len: (v) => {
|
|
175
|
+
if (Array.isArray(v)) return v.length;
|
|
176
|
+
if (typeof v === "string") return v.length;
|
|
177
|
+
return 0;
|
|
178
|
+
},
|
|
179
|
+
upper: (v) => typeof v === "string" ? v.toUpperCase() : String(v ?? "").toUpperCase(),
|
|
180
|
+
lower: (v) => typeof v === "string" ? v.toLowerCase() : String(v ?? "").toLowerCase(),
|
|
181
|
+
trim: (v) => typeof v === "string" ? v.trim() : String(v ?? "").trim(),
|
|
182
|
+
contains: (haystack, needle) => {
|
|
183
|
+
if (typeof haystack === "string") return haystack.includes(String(needle));
|
|
184
|
+
if (Array.isArray(haystack)) return haystack.includes(needle);
|
|
185
|
+
return false;
|
|
186
|
+
},
|
|
187
|
+
starts_with: (v, prefix) => typeof v === "string" ? v.startsWith(String(prefix)) : false,
|
|
188
|
+
ends_with: (v, suffix) => typeof v === "string" ? v.endsWith(String(suffix)) : false,
|
|
189
|
+
replace: (v, search, rep) => typeof v === "string" ? v.replaceAll(String(search), String(rep)) : v,
|
|
190
|
+
split: (v, sep) => typeof v === "string" ? v.split(String(sep)) : [],
|
|
191
|
+
join: (arr, sep) => Array.isArray(arr) ? arr.join(String(sep ?? ",")) : String(arr),
|
|
192
|
+
substr: (v, start, len) => {
|
|
193
|
+
if (typeof v !== "string") return "";
|
|
194
|
+
return v.substring(Number(start) || 0, len != null ? (Number(start) || 0) + Number(len) : void 0);
|
|
195
|
+
},
|
|
196
|
+
format: (template, ...args) => {
|
|
197
|
+
if (typeof template !== "string") return String(template);
|
|
198
|
+
return template.replace(/\{(\d+)\}/g, (_, i) => String(args[Number(i)] ?? ""));
|
|
199
|
+
},
|
|
200
|
+
// Math
|
|
201
|
+
abs: (v) => Math.abs(Number(v) || 0),
|
|
202
|
+
ceil: (v) => Math.ceil(Number(v) || 0),
|
|
203
|
+
floor: (v) => Math.floor(Number(v) || 0),
|
|
204
|
+
round: (v, decimals) => {
|
|
205
|
+
const n = Number(v) || 0;
|
|
206
|
+
const d = Number(decimals) || 0;
|
|
207
|
+
const f = Math.pow(10, d);
|
|
208
|
+
return Math.round(n * f) / f;
|
|
209
|
+
},
|
|
210
|
+
pow: (base, exp) => Math.pow(Number(base) || 0, Number(exp) || 0),
|
|
211
|
+
sqrt: (v) => Math.sqrt(Number(v) || 0),
|
|
212
|
+
random: () => Math.random(),
|
|
213
|
+
clamp: (v, lo, hi) => Math.min(Math.max(Number(v), Number(lo)), Number(hi)),
|
|
214
|
+
// Date
|
|
215
|
+
now: () => (/* @__PURE__ */ new Date()).toISOString(),
|
|
216
|
+
formatDate: (v, fmt) => {
|
|
217
|
+
try {
|
|
218
|
+
const d = new Date(v);
|
|
219
|
+
if (isNaN(d.getTime())) return String(v);
|
|
220
|
+
if (fmt === "relative") {
|
|
221
|
+
const diff = Date.now() - d.getTime();
|
|
222
|
+
if (diff < 6e4) return "just now";
|
|
223
|
+
if (diff < 36e5) return `${Math.floor(diff / 6e4)}m ago`;
|
|
224
|
+
if (diff < 864e5) return `${Math.floor(diff / 36e5)}h ago`;
|
|
225
|
+
return `${Math.floor(diff / 864e5)}d ago`;
|
|
226
|
+
}
|
|
227
|
+
return d.toLocaleDateString();
|
|
228
|
+
} catch {
|
|
229
|
+
return String(v);
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
// Utility
|
|
233
|
+
coalesce: (...args) => args.find((a) => a != null),
|
|
234
|
+
default: (v, fallback) => v ?? fallback,
|
|
235
|
+
json: (v) => JSON.stringify(v),
|
|
236
|
+
keys: (v) => v && typeof v === "object" && !Array.isArray(v) ? Object.keys(v) : [],
|
|
237
|
+
values: (v) => v && typeof v === "object" && !Array.isArray(v) ? Object.values(v) : [],
|
|
238
|
+
entries: (v) => v && typeof v === "object" && !Array.isArray(v) ? Object.entries(v) : [],
|
|
239
|
+
merge: (...args) => Object.assign({}, ...args.filter((a) => a && typeof a === "object")),
|
|
240
|
+
debug: (v, label) => {
|
|
241
|
+
console.log(label ? `[debug:${label}]` : "[debug]", v);
|
|
242
|
+
return v;
|
|
243
|
+
},
|
|
244
|
+
Boolean: (v) => Boolean(v),
|
|
245
|
+
Number: (v) => Number(v),
|
|
246
|
+
String: (v) => String(v ?? "")
|
|
247
|
+
};
|
|
248
|
+
var compiledCache = new LRUCache(2e3);
|
|
249
|
+
var FORBIDDEN_PATTERNS = [
|
|
250
|
+
/\beval\b/,
|
|
251
|
+
/\bFunction\b/,
|
|
252
|
+
/\bfetch\b/,
|
|
253
|
+
/\bdocument\b/,
|
|
254
|
+
/\bwindow\b/,
|
|
255
|
+
/\bprocess\b/,
|
|
256
|
+
/\bglobalThis\b/,
|
|
257
|
+
/\bimport\b/,
|
|
258
|
+
/\brequire\b/,
|
|
259
|
+
/\b__proto__\b/,
|
|
260
|
+
/\bconstructor\b/
|
|
261
|
+
];
|
|
262
|
+
function validateExpression(expr) {
|
|
263
|
+
for (const pat of FORBIDDEN_PATTERNS) {
|
|
264
|
+
if (pat.test(expr)) {
|
|
265
|
+
return { valid: false, error: `Forbidden pattern: ${pat.source}` };
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const depth = (expr.match(/\(/g) || []).length;
|
|
269
|
+
if (depth > 15) return { valid: false, error: "Expression too deeply nested" };
|
|
270
|
+
return { valid: true };
|
|
271
|
+
}
|
|
272
|
+
function compileToFunction(expr, paramNames) {
|
|
273
|
+
const cached = compiledCache.get(expr);
|
|
274
|
+
if (cached) return cached;
|
|
275
|
+
const validation = validateExpression(expr);
|
|
276
|
+
if (!validation.valid) {
|
|
277
|
+
console.warn(`[expression-engine] Unsafe expression blocked: ${validation.error} in "${expr}"`);
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
const fn = new Function(...paramNames, `"use strict"; try { return (${expr}); } catch(e) { return undefined; }`);
|
|
282
|
+
compiledCache.set(expr, fn);
|
|
283
|
+
return fn;
|
|
284
|
+
} catch {
|
|
285
|
+
try {
|
|
286
|
+
const fn = new Function(...paramNames, `"use strict"; try { ${expr} } catch(e) { return undefined; }`);
|
|
287
|
+
compiledCache.set(expr, fn);
|
|
288
|
+
return fn;
|
|
289
|
+
} catch {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function evaluateExpression(expr, context) {
|
|
295
|
+
const trimmed = expr.trim();
|
|
296
|
+
if (!trimmed) return void 0;
|
|
297
|
+
const cls = classifyExpression(trimmed);
|
|
298
|
+
if (cls === "literal") return parseLiteral(trimmed);
|
|
299
|
+
if (cls === "path") return resolveScopePath(trimmed, context);
|
|
300
|
+
let evalExpr = trimmed;
|
|
301
|
+
if (evalExpr.startsWith("$expr(") && evalExpr.endsWith(")")) {
|
|
302
|
+
evalExpr = evalExpr.slice(6, -1);
|
|
303
|
+
}
|
|
304
|
+
if (_wasmAvailable && _wasmModule) {
|
|
305
|
+
try {
|
|
306
|
+
const translated = translateForWasm(evalExpr);
|
|
307
|
+
const contextJson = JSON.stringify(buildWasmContext(context));
|
|
308
|
+
const resultJson = _wasmModule.evaluateExpressionSync(translated, contextJson);
|
|
309
|
+
return JSON.parse(resultJson);
|
|
310
|
+
} catch {
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return evaluateWithJS(evalExpr, context);
|
|
314
|
+
}
|
|
315
|
+
function parseLiteral(expr) {
|
|
316
|
+
if (expr.startsWith('"') && expr.endsWith('"') || expr.startsWith("'") && expr.endsWith("'")) {
|
|
317
|
+
return expr.slice(1, -1);
|
|
318
|
+
}
|
|
319
|
+
if (/^-?\d+(\.\d+)?$/.test(expr)) return Number(expr);
|
|
320
|
+
if (expr === "true") return true;
|
|
321
|
+
if (expr === "false") return false;
|
|
322
|
+
if (expr === "null") return null;
|
|
323
|
+
if (expr === "undefined") return void 0;
|
|
324
|
+
if (expr === "[]") return [];
|
|
325
|
+
if (expr === "{}") return {};
|
|
326
|
+
if (expr === "[Expression]") return void 0;
|
|
327
|
+
return void 0;
|
|
328
|
+
}
|
|
329
|
+
function resolveScopePath(expr, context) {
|
|
330
|
+
const trimmed = expr.trim();
|
|
331
|
+
if (trimmed.startsWith("$")) {
|
|
332
|
+
const dotIdx = trimmed.indexOf(".");
|
|
333
|
+
if (dotIdx === -1) {
|
|
334
|
+
return context[trimmed];
|
|
335
|
+
}
|
|
336
|
+
const root = trimmed.slice(0, dotIdx);
|
|
337
|
+
const rest = trimmed.slice(dotIdx + 1);
|
|
338
|
+
const rootVal = context[root];
|
|
339
|
+
if (rootVal == null) return void 0;
|
|
340
|
+
return resolvePath(rest, rootVal);
|
|
341
|
+
}
|
|
342
|
+
return resolvePath(trimmed, context);
|
|
343
|
+
}
|
|
344
|
+
function translateForWasm(expr) {
|
|
345
|
+
return expr.replace(/\$fn\./g, "").replace(/\$instance\./g, "state_data.").replace(/\$data\./g, "state_data.").replace(/\$row\./g, "state_data.");
|
|
346
|
+
}
|
|
347
|
+
function buildWasmContext(context) {
|
|
348
|
+
const wasmCtx = {};
|
|
349
|
+
if (context.$instance && typeof context.$instance === "object") {
|
|
350
|
+
const inst = context.$instance;
|
|
351
|
+
wasmCtx.state_data = inst.fields ?? inst;
|
|
352
|
+
}
|
|
353
|
+
if (context.$local) wasmCtx.$local = context.$local;
|
|
354
|
+
if (context.$user) wasmCtx.$user = context.$user;
|
|
355
|
+
if (context.$entity) wasmCtx.$entity = context.$entity;
|
|
356
|
+
if (context.state_data) wasmCtx.state_data = context.state_data;
|
|
357
|
+
if (context.memory) wasmCtx.memory = context.memory;
|
|
358
|
+
if (context.context) wasmCtx.context = context.context;
|
|
359
|
+
return wasmCtx;
|
|
360
|
+
}
|
|
361
|
+
function evaluateWithJS(expr, context) {
|
|
362
|
+
const paramNames = [];
|
|
363
|
+
const paramValues = [];
|
|
364
|
+
for (const [key, value] of Object.entries(context)) {
|
|
365
|
+
if (key.startsWith("$")) {
|
|
366
|
+
paramNames.push(key);
|
|
367
|
+
paramValues.push(value);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (context.$fn && typeof context.$fn === "object") {
|
|
371
|
+
for (const [name, fn2] of Object.entries(context.$fn)) {
|
|
372
|
+
if (!paramNames.includes(name)) {
|
|
373
|
+
paramNames.push(name);
|
|
374
|
+
paramValues.push(fn2);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if ("item" in context) {
|
|
379
|
+
paramNames.push("item");
|
|
380
|
+
paramValues.push(context.item);
|
|
381
|
+
}
|
|
382
|
+
if ("index" in context) {
|
|
383
|
+
paramNames.push("index");
|
|
384
|
+
paramValues.push(context.index);
|
|
385
|
+
}
|
|
386
|
+
if (context.$instances) {
|
|
387
|
+
paramNames.push("items");
|
|
388
|
+
paramValues.push(context.$instances);
|
|
389
|
+
}
|
|
390
|
+
if (context.$instance) {
|
|
391
|
+
paramNames.push("instance");
|
|
392
|
+
paramValues.push(context.$instance);
|
|
393
|
+
}
|
|
394
|
+
if ("loading" in context) {
|
|
395
|
+
paramNames.push("loading");
|
|
396
|
+
paramValues.push(context.loading);
|
|
397
|
+
}
|
|
398
|
+
const fn = compileToFunction(expr, paramNames);
|
|
399
|
+
if (!fn) return void 0;
|
|
400
|
+
try {
|
|
401
|
+
return fn(...paramValues);
|
|
402
|
+
} catch {
|
|
403
|
+
return void 0;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// src/player/scope-builder.ts
|
|
408
|
+
import { createContext, useContext } from "react";
|
|
409
|
+
var emptyScopeData = {
|
|
410
|
+
$local: {},
|
|
411
|
+
$user: { roles: [] },
|
|
412
|
+
$fn: builtinFunctions,
|
|
413
|
+
$action: {}
|
|
414
|
+
};
|
|
415
|
+
var ScopeContext = createContext(emptyScopeData);
|
|
416
|
+
function useScope() {
|
|
417
|
+
return useContext(ScopeContext);
|
|
418
|
+
}
|
|
419
|
+
function buildScope(opts) {
|
|
420
|
+
const {
|
|
421
|
+
dataSources = {},
|
|
422
|
+
localState = {},
|
|
423
|
+
auth,
|
|
424
|
+
entity,
|
|
425
|
+
parentScope,
|
|
426
|
+
actionScope,
|
|
427
|
+
functions = {}
|
|
428
|
+
} = opts;
|
|
429
|
+
const scope = {
|
|
430
|
+
...parentScope ?? emptyScopeData,
|
|
431
|
+
$local: { ...parentScope?.$local ?? {}, ...localState },
|
|
432
|
+
$fn: { ...builtinFunctions, ...parentScope?.$fn ?? {}, ...functions }
|
|
433
|
+
};
|
|
434
|
+
if (auth) {
|
|
435
|
+
scope.$user = { id: auth.userId, roles: auth.roles ?? [] };
|
|
436
|
+
}
|
|
437
|
+
if (entity) {
|
|
438
|
+
scope.$entity = entity;
|
|
439
|
+
}
|
|
440
|
+
if (actionScope) {
|
|
441
|
+
scope.$action = actionScope;
|
|
442
|
+
}
|
|
443
|
+
if (Object.keys(dataSources).length > 0) {
|
|
444
|
+
const allInstances = [];
|
|
445
|
+
let primaryInstance;
|
|
446
|
+
let primaryDefinition;
|
|
447
|
+
let primaryPagination;
|
|
448
|
+
for (const [name, result] of Object.entries(dataSources)) {
|
|
449
|
+
if (result.instance) {
|
|
450
|
+
primaryInstance = result.instance;
|
|
451
|
+
scope[name] = result.instance;
|
|
452
|
+
}
|
|
453
|
+
if (result.instances) {
|
|
454
|
+
allInstances.push(...result.instances);
|
|
455
|
+
scope[name] = result.instances;
|
|
456
|
+
}
|
|
457
|
+
if (result.definition) {
|
|
458
|
+
primaryDefinition = result.definition;
|
|
459
|
+
}
|
|
460
|
+
if (result.pagination) {
|
|
461
|
+
primaryPagination = result.pagination;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
if (primaryInstance) scope.$instance = primaryInstance;
|
|
465
|
+
if (allInstances.length > 0) scope.$instances = allInstances;
|
|
466
|
+
if (primaryDefinition) scope.$definition = primaryDefinition;
|
|
467
|
+
if (primaryPagination) scope.$pagination = primaryPagination;
|
|
468
|
+
const anyLoading = Object.values(dataSources).some((r) => r.loading);
|
|
469
|
+
scope.loading = anyLoading;
|
|
470
|
+
}
|
|
471
|
+
return scope;
|
|
472
|
+
}
|
|
473
|
+
function mergeScope(parent, child) {
|
|
474
|
+
return {
|
|
475
|
+
...parent,
|
|
476
|
+
...child,
|
|
477
|
+
$local: { ...parent.$local, ...child.$local ?? {} },
|
|
478
|
+
$fn: { ...parent.$fn, ...child.$fn ?? {} },
|
|
479
|
+
$action: child.$action ?? parent.$action
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
function buildLoopScope(parent, item, index, as) {
|
|
483
|
+
return {
|
|
484
|
+
...parent,
|
|
485
|
+
$item: item,
|
|
486
|
+
$index: index,
|
|
487
|
+
[as]: item,
|
|
488
|
+
// Convenience: flatten item fields for direct access
|
|
489
|
+
item,
|
|
490
|
+
index
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export {
|
|
495
|
+
setWasmModule,
|
|
496
|
+
resolvePath,
|
|
497
|
+
builtinFunctions,
|
|
498
|
+
evaluateExpression,
|
|
499
|
+
ScopeContext,
|
|
500
|
+
useScope,
|
|
501
|
+
buildScope,
|
|
502
|
+
mergeScope,
|
|
503
|
+
buildLoopScope
|
|
504
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useScope
|
|
3
|
+
} from "./chunk-WV7DVCP6.mjs";
|
|
4
|
+
|
|
5
|
+
// src/player/atoms/navigation.tsx
|
|
6
|
+
import { useState, useCallback, createContext, useContext, useMemo } from "react";
|
|
7
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
var RouterCtx = createContext({
|
|
9
|
+
path: "/",
|
|
10
|
+
navigate: () => {
|
|
11
|
+
},
|
|
12
|
+
params: {}
|
|
13
|
+
});
|
|
14
|
+
function usePlayerRouter() {
|
|
15
|
+
return useContext(RouterCtx);
|
|
16
|
+
}
|
|
17
|
+
var Router = ({ children, basePath, className, style }) => {
|
|
18
|
+
const [path, setPath] = useState(basePath ?? "/");
|
|
19
|
+
const navigate = useCallback((to) => {
|
|
20
|
+
setPath(to);
|
|
21
|
+
}, []);
|
|
22
|
+
return /* @__PURE__ */ jsx(RouterCtx.Provider, { value: { path, navigate, params: {} }, children: /* @__PURE__ */ jsx("div", { className, style, children }) });
|
|
23
|
+
};
|
|
24
|
+
function matchPath(pattern, currentPath, exact) {
|
|
25
|
+
const params = {};
|
|
26
|
+
const patternParts = pattern.split("/").filter(Boolean);
|
|
27
|
+
const pathParts = currentPath.split("/").filter(Boolean);
|
|
28
|
+
if (exact && patternParts.length !== pathParts.length) {
|
|
29
|
+
return { match: false, params };
|
|
30
|
+
}
|
|
31
|
+
if (patternParts.length > pathParts.length) {
|
|
32
|
+
return { match: false, params };
|
|
33
|
+
}
|
|
34
|
+
for (let i = 0; i < patternParts.length; i++) {
|
|
35
|
+
const pp = patternParts[i];
|
|
36
|
+
if (pp.startsWith(":")) {
|
|
37
|
+
params[pp.slice(1)] = pathParts[i];
|
|
38
|
+
} else if (pp === "*") {
|
|
39
|
+
return { match: true, params };
|
|
40
|
+
} else if (pp !== pathParts[i]) {
|
|
41
|
+
return { match: false, params };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return { match: true, params };
|
|
45
|
+
}
|
|
46
|
+
var Route = ({ children, path: routePath, exact, className, style }) => {
|
|
47
|
+
const { path } = useContext(RouterCtx);
|
|
48
|
+
const pattern = routePath ?? "/";
|
|
49
|
+
const { match, params } = matchPath(pattern, path, exact !== false);
|
|
50
|
+
if (!match) return null;
|
|
51
|
+
return /* @__PURE__ */ jsx(RouterCtx.Provider, { value: { path, navigate: useContext(RouterCtx).navigate, params }, children: /* @__PURE__ */ jsx("div", { className, style, children }) });
|
|
52
|
+
};
|
|
53
|
+
var NavLink = ({
|
|
54
|
+
children,
|
|
55
|
+
to,
|
|
56
|
+
label,
|
|
57
|
+
icon,
|
|
58
|
+
activeClassName,
|
|
59
|
+
className,
|
|
60
|
+
style,
|
|
61
|
+
onClick
|
|
62
|
+
}) => {
|
|
63
|
+
const { path, navigate } = useContext(RouterCtx);
|
|
64
|
+
const target = to ?? "/";
|
|
65
|
+
const isActive = path === target || path.startsWith(target + "/");
|
|
66
|
+
const text = children ?? label;
|
|
67
|
+
const handleClick = useCallback((e) => {
|
|
68
|
+
e.preventDefault();
|
|
69
|
+
navigate(target);
|
|
70
|
+
if (typeof onClick === "function") onClick(e);
|
|
71
|
+
}, [navigate, target, onClick]);
|
|
72
|
+
return /* @__PURE__ */ jsxs(
|
|
73
|
+
"button",
|
|
74
|
+
{
|
|
75
|
+
className: `${className ?? ""} ${isActive && activeClassName ? activeClassName : ""}`.trim() || void 0,
|
|
76
|
+
onClick: handleClick,
|
|
77
|
+
style: {
|
|
78
|
+
background: isActive ? "#ebf8ff" : "transparent",
|
|
79
|
+
color: isActive ? "#2b6cb0" : "#4a5568",
|
|
80
|
+
border: "none",
|
|
81
|
+
borderRadius: 6,
|
|
82
|
+
padding: "6px 12px",
|
|
83
|
+
fontSize: 14,
|
|
84
|
+
fontWeight: isActive ? 600 : 400,
|
|
85
|
+
cursor: "pointer",
|
|
86
|
+
display: "inline-flex",
|
|
87
|
+
alignItems: "center",
|
|
88
|
+
gap: 6,
|
|
89
|
+
transition: "background 0.15s, color 0.15s",
|
|
90
|
+
...style
|
|
91
|
+
},
|
|
92
|
+
children: [
|
|
93
|
+
icon ? /* @__PURE__ */ jsx("span", { children: String(icon) }) : null,
|
|
94
|
+
text != null ? String(text) : null
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
var RoleGuard = ({ children, role, roles, fallback }) => {
|
|
100
|
+
const scope = useScope();
|
|
101
|
+
const allowed = useMemo(() => {
|
|
102
|
+
const required = Array.isArray(roles) ? roles : role != null ? [String(role)] : [];
|
|
103
|
+
if (required.length === 0) return true;
|
|
104
|
+
const userRoles = scope.$user?.roles ?? [];
|
|
105
|
+
if (userRoles.length === 0) return false;
|
|
106
|
+
return required.some((r) => userRoles.includes(r));
|
|
107
|
+
}, [role, roles, scope.$user]);
|
|
108
|
+
if (!allowed) {
|
|
109
|
+
return fallback ? /* @__PURE__ */ jsx(Fragment, { children: fallback }) : null;
|
|
110
|
+
}
|
|
111
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export {
|
|
115
|
+
usePlayerRouter,
|
|
116
|
+
Router,
|
|
117
|
+
Route,
|
|
118
|
+
NavLink,
|
|
119
|
+
RoleGuard
|
|
120
|
+
};
|