data-primals-engine 1.7.0 → 1.7.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/client/package-lock.json +702 -143
- package/client/package.json +6 -3
- package/package.json +19 -13
- package/src/core.js +487 -477
- package/src/email.js +0 -2
- package/src/filter.js +348 -343
- package/src/modules/data/data.js +311 -302
- package/src/modules/workflow.js +1828 -1815
- package/src/packs.js +14 -10
package/src/core.js
CHANGED
|
@@ -1,478 +1,488 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import crypto from "node:crypto";
|
|
4
|
-
|
|
5
|
-
export const sleep = (ms = 1000) =>
|
|
6
|
-
new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
|
-
|
|
8
|
-
export function escapeRegex(string) {
|
|
9
|
-
return string.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export const sequential = async (tasks) => {
|
|
13
|
-
const res = [];
|
|
14
|
-
for (const task of tasks) {
|
|
15
|
-
const r = await task();
|
|
16
|
-
res.push(r);
|
|
17
|
-
}
|
|
18
|
-
return res;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export function isValidRegex(s) {
|
|
22
|
-
try {
|
|
23
|
-
const m = s.match(/^([/~@;%#'])(.*?)\1([gimsuy]*)$/);
|
|
24
|
-
return m ? !!new RegExp(m[2],m[3])
|
|
25
|
-
: false;
|
|
26
|
-
} catch (e) {
|
|
27
|
-
return false
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
export function escapeHtml(string){
|
|
31
|
-
return string.replace(/(javascript|data|vbscript):/gi, '').replace(/[^\w-_. ]/gi, function (c) {
|
|
32
|
-
return `&#${c.charCodeAt(0)};`;
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export const isUnsecureKey = (key) => {
|
|
37
|
-
return ["__proto__", "constructor", "prototype"].includes(key);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export const parseSafeJSON = (json) => JSON.parse(json, (key, value) => isUnsecureKey(key) ? undefined : value);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
export const isDate = dt => {
|
|
44
|
-
if (dt === null || typeof dt === 'undefined') return false;
|
|
45
|
-
return String(new Date(dt)) !== 'Invalid Date';
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
export const safeAssignObject = (obj, key, value) => {
|
|
49
|
-
if( !isUnsecureKey(key)){
|
|
50
|
-
obj[key] = value;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
export function debounce(callback, delay=300){
|
|
54
|
-
var timer;
|
|
55
|
-
return function(){
|
|
56
|
-
var args = arguments;
|
|
57
|
-
var context = this;
|
|
58
|
-
clearTimeout(timer);
|
|
59
|
-
timer = setTimeout(function(){
|
|
60
|
-
callback.apply(context, args);
|
|
61
|
-
}, delay)
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function uuidv4() {
|
|
66
|
-
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
|
|
67
|
-
(+c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16)
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function cssProps(cssString) {
|
|
72
|
-
if (!cssString) return {};
|
|
73
|
-
|
|
74
|
-
const style = {};
|
|
75
|
-
const declarations = cssString.split(';');
|
|
76
|
-
|
|
77
|
-
declarations.forEach(declaration => {
|
|
78
|
-
const trimmed = declaration.trim();
|
|
79
|
-
if (!trimmed) return;
|
|
80
|
-
|
|
81
|
-
const colonIndex = trimmed.indexOf(':');
|
|
82
|
-
if (colonIndex === -1) return;
|
|
83
|
-
|
|
84
|
-
let property = trimmed.slice(0, colonIndex).trim();
|
|
85
|
-
const value = trimmed.slice(colonIndex + 1).trim();
|
|
86
|
-
|
|
87
|
-
// Convert kebab-case to camelCase
|
|
88
|
-
if (property.includes('-')) {
|
|
89
|
-
property = property.replace(/-([a-z])/g, (match, letter) => letter.toUpperCase());
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
style[property] = value;
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
return style;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
export function removeDir(dirPath) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
return
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
return
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
ret =
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
if (
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
if
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
export const
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
|
|
5
|
+
export const sleep = (ms = 1000) =>
|
|
6
|
+
new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
|
+
|
|
8
|
+
export function escapeRegex(string) {
|
|
9
|
+
return string.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const sequential = async (tasks) => {
|
|
13
|
+
const res = [];
|
|
14
|
+
for (const task of tasks) {
|
|
15
|
+
const r = await task();
|
|
16
|
+
res.push(r);
|
|
17
|
+
}
|
|
18
|
+
return res;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function isValidRegex(s) {
|
|
22
|
+
try {
|
|
23
|
+
const m = s.match(/^([/~@;%#'])(.*?)\1([gimsuy]*)$/);
|
|
24
|
+
return m ? !!new RegExp(m[2],m[3])
|
|
25
|
+
: false;
|
|
26
|
+
} catch (e) {
|
|
27
|
+
return false
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function escapeHtml(string){
|
|
31
|
+
return string.replace(/(javascript|data|vbscript):/gi, '').replace(/[^\w-_. ]/gi, function (c) {
|
|
32
|
+
return `&#${c.charCodeAt(0)};`;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const isUnsecureKey = (key) => {
|
|
37
|
+
return ["__proto__", "constructor", "prototype"].includes(key);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const parseSafeJSON = (json) => JSON.parse(json, (key, value) => isUnsecureKey(key) ? undefined : value);
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
export const isDate = dt => {
|
|
44
|
+
if (dt === null || typeof dt === 'undefined') return false;
|
|
45
|
+
return String(new Date(dt)) !== 'Invalid Date';
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const safeAssignObject = (obj, key, value) => {
|
|
49
|
+
if( !isUnsecureKey(key)){
|
|
50
|
+
obj[key] = value;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export function debounce(callback, delay=300){
|
|
54
|
+
var timer;
|
|
55
|
+
return function(){
|
|
56
|
+
var args = arguments;
|
|
57
|
+
var context = this;
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
timer = setTimeout(function(){
|
|
60
|
+
callback.apply(context, args);
|
|
61
|
+
}, delay)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function uuidv4() {
|
|
66
|
+
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
|
|
67
|
+
(+c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16)
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function cssProps(cssString) {
|
|
72
|
+
if (!cssString) return {};
|
|
73
|
+
|
|
74
|
+
const style = {};
|
|
75
|
+
const declarations = cssString.split(';');
|
|
76
|
+
|
|
77
|
+
declarations.forEach(declaration => {
|
|
78
|
+
const trimmed = declaration.trim();
|
|
79
|
+
if (!trimmed) return;
|
|
80
|
+
|
|
81
|
+
const colonIndex = trimmed.indexOf(':');
|
|
82
|
+
if (colonIndex === -1) return;
|
|
83
|
+
|
|
84
|
+
let property = trimmed.slice(0, colonIndex).trim();
|
|
85
|
+
const value = trimmed.slice(colonIndex + 1).trim();
|
|
86
|
+
|
|
87
|
+
// Convert kebab-case to camelCase
|
|
88
|
+
if (property.includes('-')) {
|
|
89
|
+
property = property.replace(/-([a-z])/g, (match, letter) => letter.toUpperCase());
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
style[property] = value;
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
return style;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
export function removeDir(dirPath) {
|
|
100
|
+
// --- SÉCURITÉ : Ajout de la validation du chemin ---
|
|
101
|
+
// Définir le répertoire de base autorisé pour les suppressions.
|
|
102
|
+
const allowedBaseDir = path.resolve(process.cwd()); // Exemple, à adapter
|
|
103
|
+
const resolvedDirPath = path.resolve(dirPath);
|
|
104
|
+
|
|
105
|
+
// S'assurer que le répertoire à supprimer est bien un sous-répertoire de la base autorisée.
|
|
106
|
+
if (!resolvedDirPath.startsWith(allowedBaseDir)) {
|
|
107
|
+
throw new Error(`Suppression non autorisée en dehors du répertoire de base : ${dirPath}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const dirContents = fs.readdirSync(dirPath); // List dir content
|
|
111
|
+
|
|
112
|
+
for (const fileOrDirPath of dirContents) {
|
|
113
|
+
try {
|
|
114
|
+
// Get Full path
|
|
115
|
+
const fullPath = path.join(dirPath, fileOrDirPath);
|
|
116
|
+
const stat = fs.statSync(fullPath);
|
|
117
|
+
if (stat.isDirectory()) {
|
|
118
|
+
// It's a sub directory
|
|
119
|
+
if (fs.readdirSync(fullPath).length) removeDir(fullPath);
|
|
120
|
+
// If the dir is not empty then remove it's contents too(recursively)
|
|
121
|
+
fs.rmdirSync(fullPath);
|
|
122
|
+
} else fs.unlinkSync(fullPath); // It's a file
|
|
123
|
+
} catch (ex) {
|
|
124
|
+
console.error(ex.message);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export const wordWrap = (str, max, br = '\n') => str.replace(
|
|
131
|
+
new RegExp(`(?![^\\n]{1,${max}}$)([^\\n]{1,${max}})\\s`, 'g'), `$1${br}`
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
export const getObjectHash = (obj, uniqueFields = null, key = "") => {
|
|
135
|
+
let str = "";
|
|
136
|
+
const keysToProcess = Object.keys(obj).sort(); // Trier les clés pour la cohérence
|
|
137
|
+
|
|
138
|
+
keysToProcess.forEach(k1 => {
|
|
139
|
+
const v = obj[k1];
|
|
140
|
+
if (v !== undefined) { // Ignorer les clés avec des valeurs undefined
|
|
141
|
+
// Simplification de la logique: on inclut soit les champs uniques, soit tous les champs.
|
|
142
|
+
// La sérialisation est la même dans les deux cas pour un champ donné.
|
|
143
|
+
if (uniqueFields === null || uniqueFields.length === 0 || uniqueFields.includes(k1)) {
|
|
144
|
+
try {
|
|
145
|
+
// Utiliser JSON.stringify avec un replacer pour gérer plus de types si nécessaire,
|
|
146
|
+
// ou simplement stringify directement. Attention aux types non sérialisables.
|
|
147
|
+
str += k1 + ':' + JSON.stringify(v) + ';'; // Inclure clé + valeur sérialisée
|
|
148
|
+
} catch (e) {
|
|
149
|
+
// Gérer les erreurs de sérialisation (ex: objets circulaires)
|
|
150
|
+
console.warn(`Could not stringify value for key ${k1} in getObjectHash`, e);
|
|
151
|
+
str += k1 + ':[unserializable];';
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const buffer = str + key; // Ajouter la clé optionnelle à la fin
|
|
158
|
+
|
|
159
|
+
// Utiliser cyrb53 pour le hachage final
|
|
160
|
+
return cyrb53(buffer);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export function isPathRelativeTo(dir, parent) {
|
|
164
|
+
const relative = path.relative(parent, dir);
|
|
165
|
+
return relative && !relative.startsWith('..') && !path.isAbsolute(relative);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Ensures the value is a valid GUID
|
|
170
|
+
* @param value string value
|
|
171
|
+
*/
|
|
172
|
+
export function isGUID(value) {
|
|
173
|
+
return !!(typeof(value) === 'string' && value.match(/^[a-zA-Z0-9]{8}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{12}$/));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function isPlainObject(obj) {
|
|
177
|
+
return typeof obj === 'object' && obj !== null && !Array.isArray(obj) && !(obj instanceof Date);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function escapeRegExp(string) {
|
|
181
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function shuffle(array) {
|
|
185
|
+
let currentIndex = array.length;
|
|
186
|
+
|
|
187
|
+
// While there remain elements to shuffle...
|
|
188
|
+
while (currentIndex !== 0) {
|
|
189
|
+
|
|
190
|
+
// Pick a remaining element...
|
|
191
|
+
let randomIndex = Math.floor(Math.random() * currentIndex);
|
|
192
|
+
currentIndex--;
|
|
193
|
+
|
|
194
|
+
// And swap it with the current element.
|
|
195
|
+
[array[currentIndex], array[randomIndex]] = [
|
|
196
|
+
array[randomIndex], array[currentIndex]];
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function sfc32(a, b, c, d) {
|
|
201
|
+
a |= 0; b |= 0; c |= 0; d |= 0;
|
|
202
|
+
let t = (a + b | 0) + d | 0;
|
|
203
|
+
d = d + 1 | 0;
|
|
204
|
+
a = b ^ b >>> 9;
|
|
205
|
+
b = c + (c << 3) | 0;
|
|
206
|
+
c = (c << 21 | c >>> 11);
|
|
207
|
+
c = c + t | 0;
|
|
208
|
+
return (t >>> 0) / 4294967296;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
let seed = 0;
|
|
212
|
+
export const setSeed = (s)=>{
|
|
213
|
+
seed = s;
|
|
214
|
+
}
|
|
215
|
+
function splitmix32(a) {
|
|
216
|
+
a |= 0;
|
|
217
|
+
a = a + 0x9e3779b9 | 0;
|
|
218
|
+
let t = a ^ a >>> 16;
|
|
219
|
+
t = Math.imul(t, 0x21f0aaad);
|
|
220
|
+
t = t ^ t >>> 15;
|
|
221
|
+
t = Math.imul(t, 0x735a2d97);
|
|
222
|
+
return ((t = t ^ t >>> 15) >>> 0) / 4294967296;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
export const getRand = () => {
|
|
227
|
+
const result = splitmix32(seed);
|
|
228
|
+
seed++; // Simple increment to change the state for the next call
|
|
229
|
+
return result;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const MAX_RANGE_SIZE = 2n ** 64n
|
|
233
|
+
const buffer = new BigUint64Array(512)
|
|
234
|
+
let offset = buffer.length
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Returns a cryptographically secure random integer between min and max, inclusive.
|
|
238
|
+
*
|
|
239
|
+
* @param {number} min - the lowest integer in the desired range (inclusive)
|
|
240
|
+
* @param {number} max - the highest integer in the desired range (inclusive)
|
|
241
|
+
* @returns {number} Random number
|
|
242
|
+
*/
|
|
243
|
+
|
|
244
|
+
export function getRandom(min, max) {
|
|
245
|
+
if (!(Number.isSafeInteger(min) && Number.isSafeInteger(max))) {
|
|
246
|
+
throw Error("min and max must be safe integers")
|
|
247
|
+
}
|
|
248
|
+
if (min > max) {
|
|
249
|
+
throw Error("min must be less than or equal to max")
|
|
250
|
+
}
|
|
251
|
+
const bmin = BigInt(min)
|
|
252
|
+
const rangeSize = BigInt(max) - bmin + 1n
|
|
253
|
+
const rejectionThreshold = MAX_RANGE_SIZE - (MAX_RANGE_SIZE % rangeSize)
|
|
254
|
+
let result;
|
|
255
|
+
do {
|
|
256
|
+
if (offset >= buffer.length) {
|
|
257
|
+
crypto.getRandomValues(buffer)
|
|
258
|
+
offset = 0
|
|
259
|
+
}
|
|
260
|
+
result = buffer[offset++]
|
|
261
|
+
} while (result >= rejectionThreshold)
|
|
262
|
+
return Number(bmin + result % rangeSize)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Returns a cryptographically secure random integer between min and max, inclusive.
|
|
267
|
+
*
|
|
268
|
+
* @param {number} minInclusive - the lowest integer in the desired range (inclusive)
|
|
269
|
+
* @param {number} maxInclusive - the highest integer in the desired range (inclusive)
|
|
270
|
+
* @returns {number} Random number
|
|
271
|
+
*/
|
|
272
|
+
|
|
273
|
+
export function getBrowserRandom(minInclusive, maxInclusive) {
|
|
274
|
+
const randomBuffer = new Uint32Array(1);
|
|
275
|
+
const cr = (window.crypto || window.msCrypto);
|
|
276
|
+
cr?.getRandomValues(randomBuffer);
|
|
277
|
+
const r = cr ? ( randomBuffer[0] / (0xffffffff + 1) ) : getRand();
|
|
278
|
+
return Math.floor(r * (maxInclusive - minInclusive + 1) + minInclusive);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function randomDate(start, end) {
|
|
282
|
+
return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime()));
|
|
283
|
+
}
|
|
284
|
+
export function isLightColor(color) {
|
|
285
|
+
if( !color )
|
|
286
|
+
return true;
|
|
287
|
+
const hex = color.replace('#', '');
|
|
288
|
+
const c_r = parseInt(hex.substr(0, 2), 16);
|
|
289
|
+
const c_g = parseInt(hex.substr(2, 2), 16);
|
|
290
|
+
const c_b = parseInt(hex.substr(4, 2), 16);
|
|
291
|
+
const brightness = ((c_r * 299) + (c_g * 587) + (c_b * 114)) / 1000;
|
|
292
|
+
return brightness > 155;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Mettez cette fonction au début du fichier DataEditor.jsx ou dans un fichier utilitaire importé
|
|
296
|
+
export const tryParseJson = (jsonString) => {
|
|
297
|
+
if (!jsonString || typeof jsonString !== 'string') {
|
|
298
|
+
return null; // Retourne null si la chaîne est vide, null, ou pas une chaîne
|
|
299
|
+
}
|
|
300
|
+
try {
|
|
301
|
+
// Tenter de parser la chaîne JSON
|
|
302
|
+
const parsed = JSON.parse(jsonString);
|
|
303
|
+
// S'assurer que c'est un objet ou null (pas un simple nombre, string, etc.)
|
|
304
|
+
return (typeof parsed === 'object' || parsed === null) ? parsed : null;
|
|
305
|
+
} catch (e) {
|
|
306
|
+
console.error("Failed to parse condition JSON:", e, jsonString);
|
|
307
|
+
return null; // Retourne null en cas d'erreur de parsing
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
export function isIsoDate(str) {
|
|
312
|
+
if (!/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/.test(str)) return false;
|
|
313
|
+
const d = new Date(str);
|
|
314
|
+
return !isNaN(d.getTime()) && d.toISOString()===str; // valid date
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
Math.seed = function(s) {
|
|
319
|
+
return function() {
|
|
320
|
+
s = Math.sin(s) * 10000; return s - Math.floor(s);
|
|
321
|
+
};
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const cyrb53 = (str, seed = 0) => {
|
|
325
|
+
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
|
|
326
|
+
for(let i = 0, ch; i < str.length; i++) {
|
|
327
|
+
ch = str.charCodeAt(i);
|
|
328
|
+
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
329
|
+
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
330
|
+
}
|
|
331
|
+
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
|
|
332
|
+
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
|
333
|
+
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
|
|
334
|
+
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
|
335
|
+
|
|
336
|
+
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
String.prototype.hashCode = function() {
|
|
340
|
+
return cyrb53(this);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export function random(min, max) {
|
|
344
|
+
min = Math.ceil(min);
|
|
345
|
+
max = Math.floor(max);
|
|
346
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
let triggers = {};
|
|
351
|
+
export const event_trigger = (name, ...params) => {
|
|
352
|
+
//console.log('Triggering raw event "' + name + '"');
|
|
353
|
+
let ret = false;
|
|
354
|
+
if (Array.isArray(triggers[name])) {
|
|
355
|
+
triggers[name].forEach((t) => {
|
|
356
|
+
const res = t.callback(...params);
|
|
357
|
+
if (Array.isArray(res)) {
|
|
358
|
+
if (!Array.isArray(ret)) ret = [];
|
|
359
|
+
ret = ret.concat(res);
|
|
360
|
+
} else if (typeof res === "string") {
|
|
361
|
+
if (typeof ret !== "string") ret = "";
|
|
362
|
+
ret += res;
|
|
363
|
+
} else if (typeof res === "number") {
|
|
364
|
+
if (typeof ret !== "number") ret = 0;
|
|
365
|
+
ret += res;
|
|
366
|
+
} else if (typeof res === "boolean") {
|
|
367
|
+
if (typeof ret !== "boolean") ret = true;
|
|
368
|
+
ret = res && ret;
|
|
369
|
+
} else {
|
|
370
|
+
ret = res || ret;
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
return ret;
|
|
375
|
+
};
|
|
376
|
+
export const event_on = (name, callback) => {
|
|
377
|
+
if (!Array.isArray(triggers[name])) {
|
|
378
|
+
safeAssignObject(triggers, name, []);
|
|
379
|
+
}
|
|
380
|
+
triggers[name].push({ callback });
|
|
381
|
+
};
|
|
382
|
+
export const event_off = (name, callback) => {
|
|
383
|
+
if (callback && triggers[name]) {
|
|
384
|
+
safeAssignObject(triggers, name, triggers[name].filter((f) => f.callback !== callback));
|
|
385
|
+
} else {
|
|
386
|
+
triggers[name] = undefined;
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
export function slugify(str,replacer='-', removeNonAscii=false) {
|
|
392
|
+
|
|
393
|
+
if (!str) return '';
|
|
394
|
+
|
|
395
|
+
// 1. Convert to string, lowercase, and trim whitespace.
|
|
396
|
+
str = str.toString().toLowerCase().trim();
|
|
397
|
+
|
|
398
|
+
// 2. Normalize Unicode characters (e.g., è -> e). This is now always done.
|
|
399
|
+
str = str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
|
400
|
+
|
|
401
|
+
// 3. Remove any remaining non-ASCII characters if the flag is set.
|
|
402
|
+
if (removeNonAscii) {
|
|
403
|
+
str = str.replace(/[^a-z0-9\s-]/g, '');
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// 4. Replace spaces with the replacer and clean up consecutive/trailing replacers.
|
|
407
|
+
return str
|
|
408
|
+
.replace(/\s+/g, replacer) // Replace spaces with the replacer.
|
|
409
|
+
.replace(new RegExp(`[^a-z0-9${replacer}]`, 'g'), '') // Remove any character that is not a letter, a number, or the replacer itself.
|
|
410
|
+
.replace(new RegExp(`${replacer}+`, 'g'), replacer); // Replace multiple replacers with a single one.
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// resource : https://stackoverflow.com/questions/190852/how-can-i-get-file-extensions-with-javascript
|
|
414
|
+
export function getFileExtension(fname) {
|
|
415
|
+
return fname.slice((fname.lastIndexOf(".") - 1 >>> 0) + 2);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export function object_equals( x, y ) {
|
|
419
|
+
if ( x === y ) return true;
|
|
420
|
+
// if both x and y are null or undefined and exactly the same
|
|
421
|
+
|
|
422
|
+
if ( ! ( x instanceof Object ) || ! ( y instanceof Object ) ) return false;
|
|
423
|
+
// if they are not strictly equal, they both need to be Objects
|
|
424
|
+
|
|
425
|
+
if ( x.constructor !== y.constructor ) return false;
|
|
426
|
+
// they must have the exact same prototype chain, the closest we can do is
|
|
427
|
+
// test there constructor.
|
|
428
|
+
|
|
429
|
+
for ( var p in x ) {
|
|
430
|
+
if ( ! x.hasOwnProperty( p ) ) continue;
|
|
431
|
+
// other properties were tested using x.constructor === y.constructor
|
|
432
|
+
|
|
433
|
+
if ( ! y.hasOwnProperty( p ) ) return false;
|
|
434
|
+
// allows to compare x[ p ] and y[ p ] when set to undefined
|
|
435
|
+
|
|
436
|
+
if ( x[ p ] === y[ p ] ) continue;
|
|
437
|
+
// if they have the same strict value or identity then they are equal
|
|
438
|
+
|
|
439
|
+
if ( typeof( x[ p ] ) !== "object" ) return false;
|
|
440
|
+
// Numbers, Strings, Functions, Booleans must be strictly equal
|
|
441
|
+
|
|
442
|
+
if ( ! object_equals( x[ p ], y[ p ] ) ) return false;
|
|
443
|
+
// Objects and Arrays must be tested recursively
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
for ( p in y )
|
|
447
|
+
if ( y.hasOwnProperty( p ) && ! x.hasOwnProperty( p ) )
|
|
448
|
+
return false;
|
|
449
|
+
// allows x[ p ] to be set to undefined
|
|
450
|
+
|
|
451
|
+
return true;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export const isValidPath = (path) =>{
|
|
455
|
+
return /^(?:[a-z]:)?[\/\\]{0,2}(?:[.\/\\ ](?![.\/\\\n])|[^<>:"|?*.\/\\ \n])+$/gmi.test(path);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Génère une couleur de base cohérente à partir d'une chaîne de caractères.
|
|
461
|
+
* @param {string} str La chaîne à hasher (par exemple, le libellé d'une catégorie).
|
|
462
|
+
* @returns {string} Une couleur au format 'hsl(h, s, l)'.
|
|
463
|
+
*/
|
|
464
|
+
export const stringToHslColor = (str) => {
|
|
465
|
+
let hash = 0;
|
|
466
|
+
for (let i = 0; i < str.length; i++) {
|
|
467
|
+
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
|
468
|
+
}
|
|
469
|
+
// Utiliser HSL pour un meilleur contrôle sur la saturation et la luminosité
|
|
470
|
+
const h = hash % 360; // Teinte (0-359)
|
|
471
|
+
const s = 70; // Saturation (fixe pour des couleurs vives mais pas criardes)
|
|
472
|
+
const l = 55; // Luminosité (fixe pour une bonne lisibilité)
|
|
473
|
+
return `hsl(${h}, ${s}%, ${l}%)`;
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
export function countKeys(t) {
|
|
477
|
+
switch (t?.constructor) {
|
|
478
|
+
case Object: // 1
|
|
479
|
+
return Object
|
|
480
|
+
.values(t)
|
|
481
|
+
.reduce((r, v) => r + 1 + countKeys(v), 0)
|
|
482
|
+
case Array: // 2
|
|
483
|
+
return t
|
|
484
|
+
.reduce((r, v) => r + countKeys(v), 0)
|
|
485
|
+
default: // 3
|
|
486
|
+
return 0
|
|
487
|
+
}
|
|
478
488
|
}
|