desktopr 1.3.1 → 2.0.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/dist-sdk/_companion_context.js +10 -5
- package/dist-sdk/_helpers.js +1 -1
- package/dist-sdk/desktopr/_helpers.js +1 -1
- package/dist-sdk/desktopr/_types.d.ts +2 -2
- package/dist-sdk/modules/rs/app/_types.d.ts +1 -1
- package/dist-sdk/modules/rs/badge/_types.d.ts +1 -1
- package/dist-sdk/modules/rs/contextMenu/_helpers.js +1 -1
- package/dist-sdk/modules/rs/diagnostics/_helpers.js +1 -1
- package/dist-sdk/modules/rs/diagnostics/_types.d.ts +1 -1
- package/dist-sdk/modules/rs/files/_types.d.ts +1 -1
- package/dist-sdk/modules/rs/fs/_main.js +44 -1
- package/dist-sdk/modules/rs/fs/_types.d.ts +7 -1
- package/dist-sdk/modules/rs/menu/_helpers.js +1 -1
- package/dist-sdk/modules/rs/network/_types.d.ts +1 -1
- package/dist-sdk/modules/rs/plugins/_helpers.d.ts +1 -0
- package/dist-sdk/modules/rs/plugins/_helpers.js +9 -0
- package/dist-sdk/modules/rs/plugins/_main.d.ts +5 -0
- package/dist-sdk/modules/rs/plugins/_main.js +15 -0
- package/dist-sdk/modules/rs/plugins/_types.d.ts +38 -0
- package/dist-sdk/modules/rs/plugins/_types.js +2 -0
- package/dist-sdk/modules/rs/window/_helpers.js +1 -1
- package/dist-sdk/modules/rs/window/_main.js +0 -5
- package/dist-sdk/modules/rs/window/_types.d.ts +0 -5
- package/dist-sdk/modules/rs/worker/_main.d.ts +1 -1
- package/dist-sdk/modules/rs/worker/_types.d.ts +1 -1
- package/dist-sdk/utils/crypto/_crypto.d.ts +31 -0
- package/dist-sdk/utils/crypto/_crypto.js +116 -0
- package/dist-sdk/utils/crypto/index.d.ts +1 -0
- package/dist-sdk/utils/crypto/index.js +17 -0
- package/dist-sdk/utils/index.d.ts +2 -0
- package/dist-sdk/utils/index.js +18 -0
- package/dist-sdk/utils/shared/_types.d.ts +64 -0
- package/dist-sdk/utils/shared/_types.js +2 -0
- package/dist-sdk/utils/utils/_browserStorage.d.ts +22 -0
- package/dist-sdk/utils/utils/_browserStorage.js +149 -0
- package/dist-sdk/utils/utils/_integerUtils.d.ts +50 -0
- package/dist-sdk/utils/utils/_integerUtils.js +74 -0
- package/dist-sdk/utils/utils/_jitter.d.ts +4 -0
- package/dist-sdk/utils/utils/_jitter.js +25 -0
- package/dist-sdk/utils/utils/_json.d.ts +63 -0
- package/dist-sdk/utils/utils/_json.js +510 -0
- package/dist-sdk/utils/utils/_logger.d.ts +16 -0
- package/dist-sdk/utils/utils/_logger.js +43 -0
- package/dist-sdk/utils/utils/_pageStore.d.ts +37 -0
- package/dist-sdk/utils/utils/_pageStore.js +61 -0
- package/dist-sdk/utils/utils/_regexPatterns.d.ts +113 -0
- package/dist-sdk/utils/utils/_regexPatterns.js +150 -0
- package/dist-sdk/utils/utils/_typesValidation.d.ts +50 -0
- package/dist-sdk/utils/utils/_typesValidation.js +125 -0
- package/dist-sdk/utils/utils/_utils.d.ts +201 -0
- package/dist-sdk/utils/utils/_utils.js +1200 -0
- package/dist-sdk/utils/utils/index.d.ts +9 -0
- package/dist-sdk/utils/utils/index.js +25 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.dropdownOptionsFromStrings = exports.wait = exports.componentCallbackDispatcher = exports.toggleHiddenStatus = exports.setHiddenStatus = exports.removeWWW = exports.redirectOrReload = exports.getSubdomain = exports.isDev = exports.isWindowAvailable = exports.isBrowser = void 0;
|
|
4
|
+
exports.stringStartsWith = stringStartsWith;
|
|
5
|
+
exports.validateFile = validateFile;
|
|
6
|
+
exports.imageExistsAtURL = imageExistsAtURL;
|
|
7
|
+
exports.clearUrl = clearUrl;
|
|
8
|
+
exports.checkPasswordStrength = checkPasswordStrength;
|
|
9
|
+
exports.capitalize = capitalize;
|
|
10
|
+
exports.formSubmit = formSubmit;
|
|
11
|
+
exports.capitalizeEachWord = capitalizeEachWord;
|
|
12
|
+
exports.parseCookie = parseCookie;
|
|
13
|
+
exports.isLocalhost = isLocalhost;
|
|
14
|
+
exports.getUrlParam = getUrlParam;
|
|
15
|
+
exports.sleep = sleep;
|
|
16
|
+
exports.mapToObject = mapToObject;
|
|
17
|
+
exports.getRandomNumber = getRandomNumber;
|
|
18
|
+
exports.getRandomString = getRandomString;
|
|
19
|
+
exports.arrrayGetLast = arrrayGetLast;
|
|
20
|
+
exports.getPathList = getPathList;
|
|
21
|
+
exports.getCurrentPath = getCurrentPath;
|
|
22
|
+
exports.buildPath = buildPath;
|
|
23
|
+
exports.detectAnalysisFileType = detectAnalysisFileType;
|
|
24
|
+
exports.scrollToElement = scrollToElement;
|
|
25
|
+
exports.clickOutside = clickOutside;
|
|
26
|
+
exports.toHtmlId = toHtmlId;
|
|
27
|
+
exports.toggleArrayItem = toggleArrayItem;
|
|
28
|
+
exports.updateUniqueArray = updateUniqueArray;
|
|
29
|
+
exports.updateArrayByKey = updateArrayByKey;
|
|
30
|
+
exports.toCamelCase = toCamelCase;
|
|
31
|
+
exports.toSnakeCase = toSnakeCase;
|
|
32
|
+
exports.portal = portal;
|
|
33
|
+
exports.isValidTimeStr = isValidTimeStr;
|
|
34
|
+
exports.isValidDate = isValidDate;
|
|
35
|
+
exports.formatDateForInput = formatDateForInput;
|
|
36
|
+
exports.addMinutesToTime = addMinutesToTime;
|
|
37
|
+
exports.setTimeForDate = setTimeForDate;
|
|
38
|
+
exports.addMinutesToDate = addMinutesToDate;
|
|
39
|
+
exports.parseDate = parseDate;
|
|
40
|
+
exports.dateToTime24h = dateToTime24h;
|
|
41
|
+
exports.dateToTime12h = dateToTime12h;
|
|
42
|
+
exports.URLGetParam = URLGetParam;
|
|
43
|
+
exports.isSameMonth = isSameMonth;
|
|
44
|
+
exports.isSameYear = isSameYear;
|
|
45
|
+
exports.getMidpointDate = getMidpointDate;
|
|
46
|
+
exports.getYearBounds = getYearBounds;
|
|
47
|
+
exports.callerName = callerName;
|
|
48
|
+
exports.arrayGetByKey = arrayGetByKey;
|
|
49
|
+
exports.checkFileSize = checkFileSize;
|
|
50
|
+
exports.getMatchScore = getMatchScore;
|
|
51
|
+
exports.objectsDiffer = objectsDiffer;
|
|
52
|
+
exports.removeNullish = removeNullish;
|
|
53
|
+
exports.flattenObject = flattenObject;
|
|
54
|
+
exports.getTimeBounds = getTimeBounds;
|
|
55
|
+
exports.listMonthsInRange = listMonthsInRange;
|
|
56
|
+
exports.getMonthBoundsByYearMonthString = getMonthBoundsByYearMonthString;
|
|
57
|
+
exports.getYearMonthStringFromDate = getYearMonthStringFromDate;
|
|
58
|
+
exports.getMonthBounds = getMonthBounds;
|
|
59
|
+
exports.mergeByKey = mergeByKey;
|
|
60
|
+
exports.removeFromArrayByKey = removeFromArrayByKey;
|
|
61
|
+
exports.hexToRgb = hexToRgb;
|
|
62
|
+
exports.copyToClipboard = copyToClipboard;
|
|
63
|
+
exports.URLReload = URLReload;
|
|
64
|
+
exports.flagEmojiToCountryCode = flagEmojiToCountryCode;
|
|
65
|
+
exports.sanitizeMessageSensitiveData = sanitizeMessageSensitiveData;
|
|
66
|
+
exports.getErrorInfo = getErrorInfo;
|
|
67
|
+
exports.serializeToString = serializeToString;
|
|
68
|
+
exports.createHashInput = createHashInput;
|
|
69
|
+
exports.arrayIncludesString = arrayIncludesString;
|
|
70
|
+
const _logger_1 = require("./_logger");
|
|
71
|
+
const _regexPatterns_1 = require("./_regexPatterns");
|
|
72
|
+
const _typesValidation_1 = require("./_typesValidation");
|
|
73
|
+
const isBrowser = () => {
|
|
74
|
+
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
75
|
+
};
|
|
76
|
+
exports.isBrowser = isBrowser;
|
|
77
|
+
const isWindowAvailable = () => {
|
|
78
|
+
return typeof window !== "undefined";
|
|
79
|
+
};
|
|
80
|
+
exports.isWindowAvailable = isWindowAvailable;
|
|
81
|
+
const isDev = () => {
|
|
82
|
+
const isLocalhost = typeof window !== "undefined" &&
|
|
83
|
+
["localhost", "127.0.0.1"].includes(window.location.hostname);
|
|
84
|
+
const nodeEnv = process?.env?.NODE_ENV;
|
|
85
|
+
return nodeEnv !== "production" || isLocalhost;
|
|
86
|
+
};
|
|
87
|
+
exports.isDev = isDev;
|
|
88
|
+
function stringStartsWith(input, prefix, caseSensitive = false) {
|
|
89
|
+
if (!caseSensitive) {
|
|
90
|
+
return input.toLowerCase().startsWith(prefix.toLowerCase());
|
|
91
|
+
}
|
|
92
|
+
return input.startsWith(prefix);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Validates a file by checking its MIME type and maximum size.
|
|
96
|
+
* @param file - The file to validate (can be null or undefined).
|
|
97
|
+
* @param maxSizeMB - Maximum allowed size in MB (default: 2).
|
|
98
|
+
* @param allowedTypes - Allowed MIME types (default: empty = all types allowed).
|
|
99
|
+
* @returns Validation result object.
|
|
100
|
+
*/
|
|
101
|
+
function validateFile(file, maxSizeMB = 2, allowedTypes = []) {
|
|
102
|
+
if (!file) {
|
|
103
|
+
return { valid: false, error: "No file provided." };
|
|
104
|
+
}
|
|
105
|
+
const maxSizeBytes = maxSizeMB * 1024 * 1024;
|
|
106
|
+
// MIME type check (only if allowedTypes is not empty)
|
|
107
|
+
if (allowedTypes.length > 0 && !allowedTypes.includes(file.type)) {
|
|
108
|
+
return {
|
|
109
|
+
valid: false,
|
|
110
|
+
error: `Invalid file type. Allowed types: ${allowedTypes.join(", ")}.`
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
// File size check
|
|
114
|
+
if (file.size > maxSizeBytes) {
|
|
115
|
+
return { valid: false, error: `File is too large. Max ${maxSizeMB} MB allowed.` };
|
|
116
|
+
}
|
|
117
|
+
return { valid: true };
|
|
118
|
+
}
|
|
119
|
+
function imageExistsAtURL(url, bustCache = true) {
|
|
120
|
+
return new Promise((resolve) => {
|
|
121
|
+
const img = new Image();
|
|
122
|
+
img.onload = () => resolve(true);
|
|
123
|
+
img.onerror = () => resolve(false);
|
|
124
|
+
img.referrerPolicy = "no-referrer";
|
|
125
|
+
img.src = bustCache
|
|
126
|
+
? `${url}${url.includes("?") ? "&" : "?"}_=${Date.now()}`
|
|
127
|
+
: url;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function clearUrl(input, domainOnly = false) {
|
|
131
|
+
if (!_typesValidation_1.validate.nonEmptyString(input)) {
|
|
132
|
+
console.error(`[invalid url string] ${input}`);
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
// Rimuove spazi iniziali/finali
|
|
136
|
+
let url = input.trim();
|
|
137
|
+
if (stringStartsWith(url, "http://"))
|
|
138
|
+
url = url?.replace("http://", "https://");
|
|
139
|
+
// Se manca lo schema, aggiunge "https://"
|
|
140
|
+
if (!stringStartsWith(url, "https://"))
|
|
141
|
+
url = "https://" + url;
|
|
142
|
+
try {
|
|
143
|
+
_logger_1.logger.log(`[url to clear] ${url}`);
|
|
144
|
+
const parsed = new URL(url);
|
|
145
|
+
// Facoltativo: normalizza schema e host in lowercase
|
|
146
|
+
parsed.protocol = parsed.protocol.toLowerCase();
|
|
147
|
+
parsed.hostname = parsed.hostname.toLowerCase();
|
|
148
|
+
// Rimuove slash finale superfluo
|
|
149
|
+
if (parsed.pathname.endsWith("/") && parsed.pathname !== "/") {
|
|
150
|
+
parsed.pathname = parsed.pathname.slice(0, -1);
|
|
151
|
+
}
|
|
152
|
+
const urlParsed = parsed.toString();
|
|
153
|
+
if (!domainOnly)
|
|
154
|
+
return urlParsed;
|
|
155
|
+
else
|
|
156
|
+
return urlParsed?.replace("https://", "");
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
console.error(`[invalid url] ${input}`);
|
|
160
|
+
return null; // URL non valido
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Estrae il sottodominio da un URL o dal dominio corrente (es. builder.bubbledesk.app → "builder").
|
|
165
|
+
*
|
|
166
|
+
* ⚠️ Restituisce `null` se non è presente alcun sottodominio (es. bubbledesk.app o localhost).
|
|
167
|
+
*
|
|
168
|
+
* @param url - (opzionale) Una stringa URL da cui estrarre il sottodominio. Se non fornita, usa `window.location.hostname`.
|
|
169
|
+
* @returns Il sottodominio come stringa, oppure `null` se non rilevabile.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* getSubdomain("https://auth.bubbledesk.app"); // "auth"
|
|
173
|
+
* getSubdomain(); // se eseguito su builder.bubbledesk.app → "builder"
|
|
174
|
+
* getSubdomain("https://bubbledesk.app"); // null
|
|
175
|
+
* getSubdomain("http://localhost:5173"); // null
|
|
176
|
+
*/
|
|
177
|
+
const getSubdomain = (url) => {
|
|
178
|
+
const hostname = url ? new URL(url).hostname : window.location.hostname;
|
|
179
|
+
const parts = hostname.split(".");
|
|
180
|
+
// Gestisce casi tipo: "builder.bubbledesk.app"
|
|
181
|
+
// Evita problemi su "localhost" o "bubbledesk.app"
|
|
182
|
+
if (parts.length >= 3)
|
|
183
|
+
return parts[0];
|
|
184
|
+
return null;
|
|
185
|
+
};
|
|
186
|
+
exports.getSubdomain = getSubdomain;
|
|
187
|
+
/**
|
|
188
|
+
*
|
|
189
|
+
* @param options.reload If true, redirect url and related logic will be ignored
|
|
190
|
+
*/
|
|
191
|
+
const redirectOrReload = (options) => {
|
|
192
|
+
if (options?.reload)
|
|
193
|
+
window.location.reload();
|
|
194
|
+
if (!options?.reload && _typesValidation_1.validate.nonEmptyString(options?.redirectUrl)) {
|
|
195
|
+
const redirectUrl = new URL(options?.redirectUrl);
|
|
196
|
+
if (redirectUrl?.href) {
|
|
197
|
+
switch (options?.redirectReplace) {
|
|
198
|
+
case true:
|
|
199
|
+
window.location.replace(redirectUrl?.href);
|
|
200
|
+
break;
|
|
201
|
+
default:
|
|
202
|
+
window.location.href = redirectUrl?.href;
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
exports.redirectOrReload = redirectOrReload;
|
|
209
|
+
function checkPasswordStrength(password) {
|
|
210
|
+
if (password.length < 8)
|
|
211
|
+
return { text: "very weak", score: 0 };
|
|
212
|
+
let score = 0;
|
|
213
|
+
if (/[a-z]/.test(password))
|
|
214
|
+
score++; // lettere minuscole
|
|
215
|
+
if (/[A-Z]/.test(password))
|
|
216
|
+
score++; // lettere maiuscole
|
|
217
|
+
if (/\d/.test(password))
|
|
218
|
+
score++; // numeri
|
|
219
|
+
if (/[^A-Za-z0-9]/.test(password))
|
|
220
|
+
score++; // simboli
|
|
221
|
+
if (password.length >= 12)
|
|
222
|
+
score++; // lunghezza
|
|
223
|
+
switch (score) {
|
|
224
|
+
case 0:
|
|
225
|
+
case 1:
|
|
226
|
+
return { text: "weak", score: 1 };
|
|
227
|
+
case 2:
|
|
228
|
+
return { text: "medium", score: 2 };
|
|
229
|
+
case 3:
|
|
230
|
+
return { text: "strong", score: 3 };
|
|
231
|
+
case 4:
|
|
232
|
+
case 5:
|
|
233
|
+
return { text: "very strong", score: 4 };
|
|
234
|
+
default:
|
|
235
|
+
return { text: "very weak", score: 0 };
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function capitalize(str) {
|
|
239
|
+
if (!str)
|
|
240
|
+
return "";
|
|
241
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
242
|
+
}
|
|
243
|
+
function formSubmit(callback) {
|
|
244
|
+
return async (event) => {
|
|
245
|
+
event.preventDefault();
|
|
246
|
+
await callback();
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function capitalizeEachWord(input) {
|
|
250
|
+
return input
|
|
251
|
+
.toLowerCase()
|
|
252
|
+
.split(" ")
|
|
253
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
254
|
+
.join(" ");
|
|
255
|
+
}
|
|
256
|
+
function parseCookie(header) {
|
|
257
|
+
return Object.fromEntries(header.split("; ").map((c) => c.split("=")));
|
|
258
|
+
}
|
|
259
|
+
function isLocalhost() {
|
|
260
|
+
if (typeof window === "undefined")
|
|
261
|
+
return false;
|
|
262
|
+
return window.location.hostname.startsWith("localhost");
|
|
263
|
+
}
|
|
264
|
+
function getUrlParam(param) {
|
|
265
|
+
if (typeof window === "undefined")
|
|
266
|
+
return null;
|
|
267
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
268
|
+
return urlParams.get(param);
|
|
269
|
+
}
|
|
270
|
+
function sleep(seconds) {
|
|
271
|
+
return new Promise((resolve) => setTimeout(resolve, seconds * 1000));
|
|
272
|
+
}
|
|
273
|
+
const removeWWW = (host) => {
|
|
274
|
+
if (host && typeof host == "string")
|
|
275
|
+
return host.replace(/^www\./, "");
|
|
276
|
+
else
|
|
277
|
+
return null;
|
|
278
|
+
};
|
|
279
|
+
exports.removeWWW = removeWWW;
|
|
280
|
+
function mapToObject(value) {
|
|
281
|
+
try {
|
|
282
|
+
if (value instanceof Map) {
|
|
283
|
+
const obj = {};
|
|
284
|
+
for (const [key, val] of value.entries()) {
|
|
285
|
+
obj[key] = mapToObject(val);
|
|
286
|
+
}
|
|
287
|
+
return obj;
|
|
288
|
+
}
|
|
289
|
+
else if (Array.isArray(value)) {
|
|
290
|
+
return value.map(mapToObject);
|
|
291
|
+
}
|
|
292
|
+
else if (typeof value === "object" && value !== null) {
|
|
293
|
+
const obj = {};
|
|
294
|
+
for (const key in value) {
|
|
295
|
+
obj[key] = mapToObject(value[key]);
|
|
296
|
+
}
|
|
297
|
+
return obj;
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
return value;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
_logger_1.logger.error(error);
|
|
305
|
+
return value;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function getRandomNumber(min = 0, max = 10000, integer = true) {
|
|
309
|
+
const rand = Math.random() * (max - min) + min;
|
|
310
|
+
return integer ? Math.floor(rand) : rand;
|
|
311
|
+
}
|
|
312
|
+
function getRandomString(options) {
|
|
313
|
+
const length = options?.length || 6;
|
|
314
|
+
const includeUppercase = options?.includeUppercase || true;
|
|
315
|
+
const lower = "abcdefghijklmnopqrstuvwxyz";
|
|
316
|
+
const digits = "0123456789";
|
|
317
|
+
const upper = includeUppercase ? lower.toUpperCase() : "";
|
|
318
|
+
const chars = lower + digits + upper;
|
|
319
|
+
let result = "";
|
|
320
|
+
for (let i = 0; i < length; i++) {
|
|
321
|
+
const randIndex = Math.floor(Math.random() * chars.length);
|
|
322
|
+
result += chars[randIndex];
|
|
323
|
+
}
|
|
324
|
+
if (options?.prefix)
|
|
325
|
+
return `${options?.prefix}${result}`;
|
|
326
|
+
else
|
|
327
|
+
return result;
|
|
328
|
+
}
|
|
329
|
+
function arrrayGetLast(arr) {
|
|
330
|
+
return arr.length > 0 ? arr[arr.length - 1] : undefined;
|
|
331
|
+
}
|
|
332
|
+
function getPathList(path) {
|
|
333
|
+
try {
|
|
334
|
+
if (path)
|
|
335
|
+
return path.split("/");
|
|
336
|
+
return window.location.pathname.split("/");
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
_logger_1.logger.error(error);
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function getCurrentPath(path) {
|
|
344
|
+
try {
|
|
345
|
+
let pathList = getPathList(path);
|
|
346
|
+
if (pathList)
|
|
347
|
+
return arrrayGetLast(pathList);
|
|
348
|
+
else
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
catch (error) {
|
|
352
|
+
_logger_1.logger.error(error);
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function buildPath(parts, endIndex) {
|
|
357
|
+
const sliced = endIndex !== undefined ? parts.slice(0, endIndex + 1) : parts;
|
|
358
|
+
return "/" + sliced.filter(Boolean).join("/");
|
|
359
|
+
}
|
|
360
|
+
function detectAnalysisFileType(filename) {
|
|
361
|
+
const ext = filename.split(".").pop()?.toLowerCase();
|
|
362
|
+
switch (ext) {
|
|
363
|
+
case "vcf":
|
|
364
|
+
case "fasta":
|
|
365
|
+
case "fa":
|
|
366
|
+
case "fastq":
|
|
367
|
+
case "fq":
|
|
368
|
+
case "gtf":
|
|
369
|
+
case "gff":
|
|
370
|
+
case "bed":
|
|
371
|
+
case "csv":
|
|
372
|
+
case "tsv":
|
|
373
|
+
return ext;
|
|
374
|
+
default:
|
|
375
|
+
return "unknown";
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
function scrollToElement(target, offset = 0, scrollBehavior = "auto") {
|
|
379
|
+
let element = null;
|
|
380
|
+
if (typeof target == "string") {
|
|
381
|
+
const normalizedId = target.startsWith("#") ? target.slice(1) : target;
|
|
382
|
+
element = document.getElementById(normalizedId);
|
|
383
|
+
}
|
|
384
|
+
else if (target instanceof HTMLElement) {
|
|
385
|
+
element = target;
|
|
386
|
+
}
|
|
387
|
+
if (!element)
|
|
388
|
+
return;
|
|
389
|
+
const y = element.getBoundingClientRect().top + window.pageYOffset + offset;
|
|
390
|
+
window.scrollTo({
|
|
391
|
+
top: y,
|
|
392
|
+
behavior: scrollBehavior,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
function clickOutside(node, callback) {
|
|
396
|
+
const handleClick = (event) => {
|
|
397
|
+
if (!node.contains(event.target)) {
|
|
398
|
+
callback();
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
document.addEventListener("click", handleClick, true);
|
|
402
|
+
return {
|
|
403
|
+
destroy() {
|
|
404
|
+
document.removeEventListener("click", handleClick, true);
|
|
405
|
+
},
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
const setHiddenStatus = (element, hidden) => {
|
|
409
|
+
let el;
|
|
410
|
+
if (typeof element === "string") {
|
|
411
|
+
el = document.getElementById(element);
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
el = element;
|
|
415
|
+
}
|
|
416
|
+
if (!el) {
|
|
417
|
+
_logger_1.logger.error("Element is null running setHiddenStatus()");
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
if (hidden) {
|
|
421
|
+
el?.classList.add("hidden");
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
el?.classList.remove("hidden");
|
|
425
|
+
setTimeout(() => {
|
|
426
|
+
if (!el)
|
|
427
|
+
return;
|
|
428
|
+
el.focus();
|
|
429
|
+
}, 100);
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
exports.setHiddenStatus = setHiddenStatus;
|
|
433
|
+
const toggleHiddenStatus = (element) => {
|
|
434
|
+
let el;
|
|
435
|
+
if (typeof element === "string") {
|
|
436
|
+
el = document.getElementById(element);
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
el = element;
|
|
440
|
+
}
|
|
441
|
+
if (!el) {
|
|
442
|
+
_logger_1.logger.error("Element is null running toggleHiddenStatus()");
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
const hasHidden = el?.classList.contains("hidden");
|
|
446
|
+
if (!hasHidden) {
|
|
447
|
+
el?.classList.add("hidden");
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
el?.classList.remove("hidden");
|
|
451
|
+
setTimeout(() => {
|
|
452
|
+
if (!el)
|
|
453
|
+
return;
|
|
454
|
+
el.focus();
|
|
455
|
+
}, 100);
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
exports.toggleHiddenStatus = toggleHiddenStatus;
|
|
459
|
+
function toHtmlId(str) {
|
|
460
|
+
return str
|
|
461
|
+
.toLowerCase()
|
|
462
|
+
.trim()
|
|
463
|
+
.replace(/\s+/g, "-") // replace spaces with hyphens
|
|
464
|
+
.replace(/[^a-z0-9\-_]/g, "") // remove invalid characters
|
|
465
|
+
.replace(/^-+|-+$/g, ""); // remove leading/trailing hyphens
|
|
466
|
+
}
|
|
467
|
+
function toggleArrayItem(array, item) {
|
|
468
|
+
return array.includes(item)
|
|
469
|
+
? array.filter((i) => i !== item) // remove if exists
|
|
470
|
+
: [...array, item]; // add if not present
|
|
471
|
+
}
|
|
472
|
+
function updateUniqueArray(array, item, action) {
|
|
473
|
+
if (action === "add") {
|
|
474
|
+
return array.includes(item) ? array : [...array, item];
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
return array.filter((i) => i !== item);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
function updateArrayByKey(array, item, action, key) {
|
|
481
|
+
const referenceKey = (key || "id");
|
|
482
|
+
const index = array.findIndex((el) => el[referenceKey] === item[referenceKey]);
|
|
483
|
+
if (action === "add") {
|
|
484
|
+
if (index === -1) {
|
|
485
|
+
return [...array, item];
|
|
486
|
+
}
|
|
487
|
+
return array;
|
|
488
|
+
}
|
|
489
|
+
if (action === "remove") {
|
|
490
|
+
if (index > -1) {
|
|
491
|
+
return [...array.slice(0, index), ...array.slice(index + 1)];
|
|
492
|
+
}
|
|
493
|
+
return array;
|
|
494
|
+
}
|
|
495
|
+
throw new Error(`Unknown action: ${action}`);
|
|
496
|
+
}
|
|
497
|
+
function toCamelCase(input) {
|
|
498
|
+
// Normalize separators to space
|
|
499
|
+
const normalized = input.replace(/[_\-\s]+/g, " ").trim();
|
|
500
|
+
// Split by space first
|
|
501
|
+
const roughWords = normalized.split(" ");
|
|
502
|
+
const words = [];
|
|
503
|
+
for (const segment of roughWords) {
|
|
504
|
+
// Split segment by camel case and acronym boundaries
|
|
505
|
+
const parts = segment.match(/([A-Z]+(?=[A-Z][a-z]))|([A-Z]?[a-z]+)|([A-Z]+)|(\d+)/g);
|
|
506
|
+
if (parts)
|
|
507
|
+
words.push(...parts);
|
|
508
|
+
}
|
|
509
|
+
// Rebuild into camelCase
|
|
510
|
+
return words
|
|
511
|
+
.map((word, i) => i === 0
|
|
512
|
+
? word.toLowerCase()
|
|
513
|
+
: word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
514
|
+
.join("");
|
|
515
|
+
}
|
|
516
|
+
function toSnakeCase(input) {
|
|
517
|
+
// Normalize separators to space
|
|
518
|
+
const normalized = input.replace(/[_\-\s]+/g, " ").trim();
|
|
519
|
+
// Split by space
|
|
520
|
+
const roughWords = normalized.split(" ");
|
|
521
|
+
const words = [];
|
|
522
|
+
for (const segment of roughWords) {
|
|
523
|
+
// Split segment by camelCase / PascalCase / acronym transitions
|
|
524
|
+
const parts = segment.match(/([A-Z]+(?=[A-Z][a-z]))|([A-Z]?[a-z]+)|([A-Z]+)|(\d+)/g);
|
|
525
|
+
if (parts)
|
|
526
|
+
words.push(...parts);
|
|
527
|
+
}
|
|
528
|
+
return words.map((w) => w.toLowerCase()).join("_");
|
|
529
|
+
}
|
|
530
|
+
function portal(node, target = document.body) {
|
|
531
|
+
target.appendChild(node);
|
|
532
|
+
return {
|
|
533
|
+
destroy() {
|
|
534
|
+
if (node.parentNode) {
|
|
535
|
+
node.parentNode.removeChild(node);
|
|
536
|
+
}
|
|
537
|
+
},
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
function isValidTimeStr(timeStr) {
|
|
541
|
+
if (timeStr.trim() == "")
|
|
542
|
+
return false;
|
|
543
|
+
if (typeof timeStr !== "string")
|
|
544
|
+
return false;
|
|
545
|
+
const parts = timeStr.trim().split(":");
|
|
546
|
+
if (parts.length !== 2)
|
|
547
|
+
return false;
|
|
548
|
+
const [hStr, mStr] = parts;
|
|
549
|
+
if (!/^\d+$/.test(hStr) || !/^\d+$/.test(mStr))
|
|
550
|
+
return false;
|
|
551
|
+
const hours = Number(hStr);
|
|
552
|
+
const minutes = Number(mStr);
|
|
553
|
+
return hours >= 0 && hours <= 23 && minutes >= 0 && minutes <= 59;
|
|
554
|
+
}
|
|
555
|
+
function isValidDate(date) {
|
|
556
|
+
return date && date instanceof Date && !isNaN(date.getTime());
|
|
557
|
+
}
|
|
558
|
+
function formatDateForInput(date) {
|
|
559
|
+
if (!isValidDate(date)) {
|
|
560
|
+
_logger_1.logger.warn("Invalid 'date' in function 'formatDateForInput(date: Date)'");
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
const year = date.getFullYear();
|
|
564
|
+
const month = String(date.getMonth() + 1).padStart(2, "0"); // mesi 0-indexed
|
|
565
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
566
|
+
return `${year}-${month}-${day}`;
|
|
567
|
+
}
|
|
568
|
+
function addMinutesToTime(timeStr, minutesToAdd) {
|
|
569
|
+
if (!isValidTimeStr(timeStr)) {
|
|
570
|
+
_logger_1.logger.error("Invalid 'timeStr' at ddMinutesToTime(timeStr: string, minutesToAdd: number).");
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
const [hours, minutes] = timeStr.split(":").map(Number);
|
|
574
|
+
const date = new Date();
|
|
575
|
+
date.setHours(hours, minutes, 0, 0); // set to today, with given time
|
|
576
|
+
date.setMinutes(date.getMinutes() + minutesToAdd); // add minutes
|
|
577
|
+
const newHours = String(date.getHours()).padStart(2, "0");
|
|
578
|
+
const newMinutes = String(date.getMinutes()).padStart(2, "0");
|
|
579
|
+
return `${newHours}:${newMinutes}`;
|
|
580
|
+
}
|
|
581
|
+
function setTimeForDate(date, timeStr) {
|
|
582
|
+
/*if(!isValidDate(date)) {
|
|
583
|
+
logger.error("Invalid 'date' at setTimeForDate(date: Date, timeStr: string).")
|
|
584
|
+
return;
|
|
585
|
+
};*/
|
|
586
|
+
if (!isValidTimeStr(timeStr)) {
|
|
587
|
+
_logger_1.logger.error("Invalid 'timeStr' at setTimeForDate(date: Date, timeStr: string).");
|
|
588
|
+
return date;
|
|
589
|
+
}
|
|
590
|
+
const [hours, minutes] = timeStr.split(":").map(Number);
|
|
591
|
+
const newDate = new Date(date); // clone to avoid mutating original
|
|
592
|
+
newDate.setHours(hours, minutes, 0, 0);
|
|
593
|
+
newDate.setMinutes(newDate.getMinutes());
|
|
594
|
+
return newDate;
|
|
595
|
+
}
|
|
596
|
+
function addMinutesToDate(date, minutesToAdd, dateTimeStr) {
|
|
597
|
+
if (!isValidDate(date)) {
|
|
598
|
+
_logger_1.logger.error("Invalid 'date' at addMinutesToDate(date: Date, minutesToAdd: number, dateTimeStr?: string).");
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
let newDate = new Date(date); // clone to avoid mutating original
|
|
602
|
+
if (dateTimeStr && isValidTimeStr(dateTimeStr))
|
|
603
|
+
newDate = setTimeForDate(newDate, dateTimeStr);
|
|
604
|
+
newDate.setMinutes(newDate.getMinutes() + minutesToAdd);
|
|
605
|
+
return newDate;
|
|
606
|
+
}
|
|
607
|
+
function parseDate(dateStr, fallbackToToday) {
|
|
608
|
+
try {
|
|
609
|
+
if (typeof dateStr !== "string")
|
|
610
|
+
return null;
|
|
611
|
+
// ISO 8601 (safe and recommended)
|
|
612
|
+
if (/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2})?)?/.test(dateStr)) {
|
|
613
|
+
const isoDate = new Date(dateStr);
|
|
614
|
+
return isNaN(isoDate.getTime()) ? null : isoDate;
|
|
615
|
+
}
|
|
616
|
+
// US format MM/DD/YYYY
|
|
617
|
+
if (/^\d{1,2}\/\d{1,2}\/\d{4}$/.test(dateStr)) {
|
|
618
|
+
const [month, day, year] = dateStr.split("/").map(Number);
|
|
619
|
+
const date = new Date(year, month - 1, day);
|
|
620
|
+
return isNaN(date.getTime()) ? null : date;
|
|
621
|
+
}
|
|
622
|
+
// Optional: support DD/MM/YYYY format
|
|
623
|
+
if (/^\d{1,2}-\d{1,2}-\d{4}$/.test(dateStr)) {
|
|
624
|
+
const [day, month, year] = dateStr.split("-").map(Number);
|
|
625
|
+
const date = new Date(year, month - 1, day);
|
|
626
|
+
return isNaN(date.getTime()) ? null : date;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
catch (error) {
|
|
630
|
+
_logger_1.logger.devError(error);
|
|
631
|
+
}
|
|
632
|
+
finally {
|
|
633
|
+
if (fallbackToToday) {
|
|
634
|
+
return new Date();
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
// Fallback attempt (not recommended in general)
|
|
638
|
+
return new Date(dateStr);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
function dateToTime24h(date) {
|
|
643
|
+
if (!_typesValidation_1.validate.date(date))
|
|
644
|
+
return null;
|
|
645
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
646
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
647
|
+
return `${hours}:${minutes}`;
|
|
648
|
+
}
|
|
649
|
+
function dateToTime12h(date) {
|
|
650
|
+
if (!(date instanceof Date) || isNaN(date.getTime()))
|
|
651
|
+
return null;
|
|
652
|
+
let hours = date.getHours();
|
|
653
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
654
|
+
const ampm = hours >= 12 ? "PM" : "AM";
|
|
655
|
+
hours = hours % 12;
|
|
656
|
+
hours = hours === 0 ? 12 : hours; // 0 => 12
|
|
657
|
+
return `${String(hours).padStart(2, "0")}:${minutes} ${ampm}`;
|
|
658
|
+
}
|
|
659
|
+
function URLGetParam(paramName, url = window.location.href) {
|
|
660
|
+
try {
|
|
661
|
+
const parsedUrl = new URL(url);
|
|
662
|
+
return parsedUrl.searchParams.get(paramName);
|
|
663
|
+
}
|
|
664
|
+
catch (e) {
|
|
665
|
+
return null; // URL malformato
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
function isSameMonth(date1, date2) {
|
|
669
|
+
if (!_typesValidation_1.validate.date(date1) || !_typesValidation_1.validate.date(date2)) {
|
|
670
|
+
_logger_1.logger.devError("One or more dates are invalid at isSameMonth(date1: Date, date2: Date)");
|
|
671
|
+
return false;
|
|
672
|
+
}
|
|
673
|
+
return (date1.getFullYear() === date2.getFullYear() &&
|
|
674
|
+
date1.getMonth() === date2.getMonth());
|
|
675
|
+
}
|
|
676
|
+
function isSameYear(date1, date2) {
|
|
677
|
+
if (!(date1 instanceof Date) || isNaN(date1.getTime()))
|
|
678
|
+
return false;
|
|
679
|
+
if (!(date2 instanceof Date) || isNaN(date2.getTime()))
|
|
680
|
+
return false;
|
|
681
|
+
return date1.getFullYear() === date2.getFullYear();
|
|
682
|
+
}
|
|
683
|
+
function getMidpointDate(date1, date2) {
|
|
684
|
+
if (!(date1 instanceof Date) ||
|
|
685
|
+
isNaN(date1.getTime()) ||
|
|
686
|
+
!(date2 instanceof Date) ||
|
|
687
|
+
isNaN(date2.getTime())) {
|
|
688
|
+
throw new Error("Invalid date");
|
|
689
|
+
}
|
|
690
|
+
const time1 = date1.getTime();
|
|
691
|
+
const time2 = date2.getTime();
|
|
692
|
+
const midpoint = (time1 + time2) / 2;
|
|
693
|
+
return new Date(midpoint);
|
|
694
|
+
}
|
|
695
|
+
function getYearBounds(date) {
|
|
696
|
+
const d = date instanceof Date && !isNaN(date.getTime()) ? date : new Date();
|
|
697
|
+
const year = d.getFullYear();
|
|
698
|
+
const start = new Date(year, 0, 1, 0, 0, 0, 0); // 1 gennaio alle 00:00
|
|
699
|
+
const end = new Date(year, 11, 31, 23, 59, 59, 999); // 31 dicembre alle 23:59:59.999
|
|
700
|
+
return { start, end };
|
|
701
|
+
}
|
|
702
|
+
function callerName(level = 2) {
|
|
703
|
+
const err = new Error();
|
|
704
|
+
const stack = err.stack?.split("\n");
|
|
705
|
+
if (stack && stack.length > level) {
|
|
706
|
+
const match = stack[level].trim().match(/^at\s+([^\s(]+)/);
|
|
707
|
+
return match?.[1] || "<anonymous>";
|
|
708
|
+
}
|
|
709
|
+
return "<unknown>";
|
|
710
|
+
}
|
|
711
|
+
function arrayGetByKey(array, value, key = "id") {
|
|
712
|
+
if (!value)
|
|
713
|
+
return [];
|
|
714
|
+
if (!key)
|
|
715
|
+
return [];
|
|
716
|
+
if (!array)
|
|
717
|
+
return [];
|
|
718
|
+
return array.filter((item) => item[key] === value);
|
|
719
|
+
}
|
|
720
|
+
function checkFileSize(files, maxSizeMB) {
|
|
721
|
+
const maxSizeBytes = maxSizeMB * 1024 * 1024;
|
|
722
|
+
let iterable = Array.from(files);
|
|
723
|
+
for (const file of iterable) {
|
|
724
|
+
if (file.size > maxSizeBytes) {
|
|
725
|
+
return {
|
|
726
|
+
valid: false,
|
|
727
|
+
message: `Il file "${file.name}" supera la dimensione massima di ${maxSizeMB} MB.`,
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
return { valid: true };
|
|
732
|
+
}
|
|
733
|
+
function getMatchScore(text, term) {
|
|
734
|
+
if (text === term)
|
|
735
|
+
return 100;
|
|
736
|
+
if (text.startsWith(term))
|
|
737
|
+
return 80;
|
|
738
|
+
if (text.includes(` ${term}`))
|
|
739
|
+
return 60;
|
|
740
|
+
if (text.includes(term))
|
|
741
|
+
return 40;
|
|
742
|
+
return 0;
|
|
743
|
+
}
|
|
744
|
+
const componentCallbackDispatcher = (callback, data) => {
|
|
745
|
+
if (callback)
|
|
746
|
+
callback(data);
|
|
747
|
+
};
|
|
748
|
+
exports.componentCallbackDispatcher = componentCallbackDispatcher;
|
|
749
|
+
function objectsDiffer(a, b, strict = false) {
|
|
750
|
+
const clean = (obj) => {
|
|
751
|
+
// Rimuove Proxy, getter, reactive wrapper ecc.
|
|
752
|
+
try {
|
|
753
|
+
return structuredClone(obj);
|
|
754
|
+
}
|
|
755
|
+
catch {
|
|
756
|
+
return JSON.parse(JSON.stringify(obj));
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
const cleanA = clean(a);
|
|
760
|
+
const cleanB = clean(b);
|
|
761
|
+
return deepCompare(cleanA, cleanB, strict);
|
|
762
|
+
}
|
|
763
|
+
function deepCompare(a, b, strict) {
|
|
764
|
+
if (a === b)
|
|
765
|
+
return false;
|
|
766
|
+
if (typeof a !== "object" ||
|
|
767
|
+
a === null ||
|
|
768
|
+
typeof b !== "object" ||
|
|
769
|
+
b === null) {
|
|
770
|
+
return a !== b;
|
|
771
|
+
}
|
|
772
|
+
const keys = strict
|
|
773
|
+
? new Set([...Object.keys(a), ...Object.keys(b)])
|
|
774
|
+
: new Set([...Object.keys(a)].filter((key) => key in b));
|
|
775
|
+
for (const key of keys) {
|
|
776
|
+
const valA = a[key];
|
|
777
|
+
const valB = b[key];
|
|
778
|
+
const bothObjects = typeof valA === "object" &&
|
|
779
|
+
valA !== null &&
|
|
780
|
+
typeof valB === "object" &&
|
|
781
|
+
valB !== null;
|
|
782
|
+
if (bothObjects) {
|
|
783
|
+
if (deepCompare(valA, valB, strict))
|
|
784
|
+
return true;
|
|
785
|
+
}
|
|
786
|
+
else if (valA !== valB) {
|
|
787
|
+
return true;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
return false;
|
|
791
|
+
}
|
|
792
|
+
function removeNullish(obj) {
|
|
793
|
+
return Object.fromEntries(Object.entries(obj).filter(([_, value]) => value != null));
|
|
794
|
+
}
|
|
795
|
+
function flattenObject(obj, options = {}) {
|
|
796
|
+
return _flattenObject(obj, options, "", {}, new Set());
|
|
797
|
+
}
|
|
798
|
+
function _flattenObject(obj, options, _parentKey, _result, _seen) {
|
|
799
|
+
const { useDotNotation = false } = options;
|
|
800
|
+
if (_seen.has(obj)) {
|
|
801
|
+
_result[_parentKey || "[Circular]"] = "[Circular]";
|
|
802
|
+
return _result;
|
|
803
|
+
}
|
|
804
|
+
_seen.add(obj);
|
|
805
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
806
|
+
const isPlainObject = value !== null &&
|
|
807
|
+
typeof value === "object" &&
|
|
808
|
+
!Array.isArray(value) &&
|
|
809
|
+
!(value instanceof Date);
|
|
810
|
+
const fullKey = useDotNotation && _parentKey ? `${_parentKey}.${key}` : key;
|
|
811
|
+
if (isPlainObject) {
|
|
812
|
+
_flattenObject(value, options, fullKey, _result, _seen);
|
|
813
|
+
}
|
|
814
|
+
else {
|
|
815
|
+
if (useDotNotation) {
|
|
816
|
+
_result[fullKey] = value;
|
|
817
|
+
}
|
|
818
|
+
else {
|
|
819
|
+
_result[key] = value;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
_seen.delete(obj);
|
|
824
|
+
return _result;
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* @param normalizeMonthly If true and units is months or years, it will normalize the start and end date to the first and last day of the first and last month.
|
|
828
|
+
*/
|
|
829
|
+
function getTimeBounds(midpoint, before, after, unit, normalizeMonthly) {
|
|
830
|
+
const center = new Date(midpoint);
|
|
831
|
+
const start = new Date(center);
|
|
832
|
+
const end = new Date(center);
|
|
833
|
+
switch (unit) {
|
|
834
|
+
case "minutes":
|
|
835
|
+
start.setMinutes(start.getMinutes() - before);
|
|
836
|
+
end.setMinutes(end.getMinutes() + after);
|
|
837
|
+
break;
|
|
838
|
+
case "days":
|
|
839
|
+
start.setDate(start.getDate() - before);
|
|
840
|
+
end.setDate(end.getDate() + after);
|
|
841
|
+
start.setHours(0, 0, 0, 0);
|
|
842
|
+
end.setHours(23, 59, 59, 999);
|
|
843
|
+
break;
|
|
844
|
+
case "months":
|
|
845
|
+
if (normalizeMonthly)
|
|
846
|
+
start.setDate(1); // sicurezza (primo del mese)
|
|
847
|
+
start.setMonth(start.getMonth() - before);
|
|
848
|
+
end.setMonth(end.getMonth() + after);
|
|
849
|
+
if (normalizeMonthly) {
|
|
850
|
+
start.setDate(1);
|
|
851
|
+
end.setMonth(end.getMonth() + 1, 0); // ultimo giorno del mese
|
|
852
|
+
}
|
|
853
|
+
start.setHours(0, 0, 0, 0);
|
|
854
|
+
end.setHours(23, 59, 59, 999);
|
|
855
|
+
break;
|
|
856
|
+
case "years":
|
|
857
|
+
if (normalizeMonthly)
|
|
858
|
+
start.setMonth(0, 1); // sicurezza (1 gennaio)
|
|
859
|
+
start.setFullYear(start.getFullYear() - before);
|
|
860
|
+
end.setFullYear(end.getFullYear() + after);
|
|
861
|
+
if (normalizeMonthly) {
|
|
862
|
+
start.setMonth(0, 1); // 1 gennaio
|
|
863
|
+
end.setMonth(12, 0); // 31 dicembre
|
|
864
|
+
}
|
|
865
|
+
start.setHours(0, 0, 0, 0);
|
|
866
|
+
end.setHours(23, 59, 59, 999);
|
|
867
|
+
break;
|
|
868
|
+
default:
|
|
869
|
+
throw new Error(`Unsupported unit: ${unit}`);
|
|
870
|
+
}
|
|
871
|
+
return { start, end };
|
|
872
|
+
}
|
|
873
|
+
function listMonthsInRange(start, end, format = "YYYY-MM", options) {
|
|
874
|
+
const { locale = "en-US", excludeStart = false, excludeEnd = false, descending = false, } = options || {};
|
|
875
|
+
let startDate = new Date(start);
|
|
876
|
+
let endDate = new Date(end);
|
|
877
|
+
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
|
|
878
|
+
throw new Error("Invalid start or end date");
|
|
879
|
+
}
|
|
880
|
+
// Normalizza inizio e fine al primo giorno del mese
|
|
881
|
+
startDate = new Date(startDate.getFullYear(), startDate.getMonth(), 1);
|
|
882
|
+
endDate = new Date(endDate.getFullYear(), endDate.getMonth(), 1);
|
|
883
|
+
const months = [];
|
|
884
|
+
let current = new Date(startDate);
|
|
885
|
+
while (current <= endDate) {
|
|
886
|
+
const isStart = current.getTime() === startDate.getTime();
|
|
887
|
+
const isEnd = current.getTime() === endDate.getTime();
|
|
888
|
+
if ((excludeStart && isStart) || (excludeEnd && isEnd)) {
|
|
889
|
+
// salta questo mese
|
|
890
|
+
}
|
|
891
|
+
else {
|
|
892
|
+
const year = current.getFullYear();
|
|
893
|
+
const month = current.getMonth();
|
|
894
|
+
let formatted;
|
|
895
|
+
switch (format) {
|
|
896
|
+
case "YYYY-MM":
|
|
897
|
+
formatted = `${year}-${String(month + 1).padStart(2, "0")}`;
|
|
898
|
+
break;
|
|
899
|
+
case "YYYY-MMM":
|
|
900
|
+
formatted = `${year}-${current.toLocaleString(locale, {
|
|
901
|
+
month: "short",
|
|
902
|
+
})}`;
|
|
903
|
+
break;
|
|
904
|
+
case "YYYY-MMMM":
|
|
905
|
+
formatted = `${year}-${current.toLocaleString(locale, {
|
|
906
|
+
month: "long",
|
|
907
|
+
})}`;
|
|
908
|
+
break;
|
|
909
|
+
default:
|
|
910
|
+
throw new Error(`Unsupported format: ${format}`);
|
|
911
|
+
}
|
|
912
|
+
months.push(formatted);
|
|
913
|
+
}
|
|
914
|
+
current.setMonth(current.getMonth() + 1);
|
|
915
|
+
}
|
|
916
|
+
return descending ? months.reverse() : months;
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* @param monthKey (deve essere in formato YYYY-MM)
|
|
920
|
+
* @returns Restituisce un oggetto contente start e end
|
|
921
|
+
*/
|
|
922
|
+
function getMonthBoundsByYearMonthString(monthKey) {
|
|
923
|
+
const [year, month] = monthKey.split("-").map(Number); // es. 2025, 6
|
|
924
|
+
const start = new Date(Date.UTC(year, month, 1, 0, 0, 0, 0)); // primo giorno del mese
|
|
925
|
+
const end = new Date(Date.UTC(year, month + 1, 0, 23, 59, 59, 999)); // ultimo giorno del mese
|
|
926
|
+
return { start, end };
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* @returns The month of the passed date as string in the format YYYY-MM (eg. 2025-05), in UTC
|
|
930
|
+
*/
|
|
931
|
+
function getYearMonthStringFromDate(date) {
|
|
932
|
+
const year = date.getUTCFullYear();
|
|
933
|
+
const month = (date.getUTCMonth() + 1).toString().padStart(2, "0");
|
|
934
|
+
return `${year}-${month}`;
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* @returns Restituisce un oggetto contente start e end
|
|
938
|
+
*/
|
|
939
|
+
function getMonthBounds(date) {
|
|
940
|
+
const year = date.getUTCFullYear();
|
|
941
|
+
const month = date.getUTCMonth(); // 0-based
|
|
942
|
+
const start = new Date(Date.UTC(year, month, 1, 0, 0, 0, 0)); // primo giorno del mese
|
|
943
|
+
const end = new Date(Date.UTC(year, month + 1, 0, 23, 59, 59, 999)); // ultimo giorno del mese
|
|
944
|
+
return { start, end };
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Unisce l'array originale con nuovi oggetti, sovrascrivendo quelli con la stessa chiave.
|
|
948
|
+
* @param original Array originale
|
|
949
|
+
* @param updates Nuovi oggetti da aggiungere o aggiornare
|
|
950
|
+
* @param key Chiave identificativa (default: "id")
|
|
951
|
+
* @returns Nuovo array aggiornato
|
|
952
|
+
*/
|
|
953
|
+
function mergeByKey(original, updates, key = "id") {
|
|
954
|
+
const map = new Map();
|
|
955
|
+
for (const item of original) {
|
|
956
|
+
map.set(item[key], item);
|
|
957
|
+
}
|
|
958
|
+
for (const item of updates) {
|
|
959
|
+
map.set(item[key], item);
|
|
960
|
+
}
|
|
961
|
+
return Array.from(map.values());
|
|
962
|
+
}
|
|
963
|
+
/**
|
|
964
|
+
* Rimuove un elemento da un array di oggetti confrontando un campo chiave.
|
|
965
|
+
*
|
|
966
|
+
* @param array - L'array di oggetti da cui rimuovere l'elemento
|
|
967
|
+
* @param value - Il valore da confrontare per la rimozione
|
|
968
|
+
* @param key - Il campo su cui fare il confronto (default: "id")
|
|
969
|
+
* @returns Un nuovo array senza l'elemento corrispondente
|
|
970
|
+
*/
|
|
971
|
+
function removeFromArrayByKey(array, value, key = "id") {
|
|
972
|
+
if (!_typesValidation_1.validate.array(array))
|
|
973
|
+
throw new Error("Invalid array");
|
|
974
|
+
return array.filter((item) => item[key] !== value);
|
|
975
|
+
}
|
|
976
|
+
function hexToRgb(hexString) {
|
|
977
|
+
const hex = hexString || "#000000";
|
|
978
|
+
const cleaned = hex.replace(/^#/, "");
|
|
979
|
+
if (![3, 6].includes(cleaned.length))
|
|
980
|
+
return "(0,0,0)";
|
|
981
|
+
const fullHex = cleaned.length === 3
|
|
982
|
+
? cleaned
|
|
983
|
+
.split("")
|
|
984
|
+
.map((c) => c + c)
|
|
985
|
+
.join("")
|
|
986
|
+
: cleaned;
|
|
987
|
+
const num = parseInt(fullHex, 16);
|
|
988
|
+
return `(${(num >> 16) & 255 || 0},${(num >> 8) & 255 || 0},${num & 255 || 0})`;
|
|
989
|
+
}
|
|
990
|
+
async function copyToClipboard(text, callback) {
|
|
991
|
+
try {
|
|
992
|
+
await navigator.clipboard.writeText(text);
|
|
993
|
+
_logger_1.logger.log("Text copied to clipboard");
|
|
994
|
+
if (callback)
|
|
995
|
+
callback();
|
|
996
|
+
}
|
|
997
|
+
catch (err) {
|
|
998
|
+
_logger_1.logger.error("Failed to copy text: ", err);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Reloads the page with the given query parameters if they are not already present or different.
|
|
1003
|
+
* Prevents infinite loops by comparing current parameters with target ones.
|
|
1004
|
+
* Optionally appends a hash anchor (e.g., #section) for native browser scrolling.
|
|
1005
|
+
*
|
|
1006
|
+
* @param newParams An object containing key-value pairs to be added to the URL.
|
|
1007
|
+
* @param anchor Optional ID of the element to scroll to after reload (e.g. 'comments')
|
|
1008
|
+
*/
|
|
1009
|
+
function URLReload(newParams, anchor) {
|
|
1010
|
+
const current = new URLSearchParams(window.location.search);
|
|
1011
|
+
let changed = false;
|
|
1012
|
+
if (!_typesValidation_1.validate.object(newParams)) {
|
|
1013
|
+
const hash = anchor ? `#${anchor}` : "";
|
|
1014
|
+
location.replace(`${window.location.pathname}${window.location.search}${hash}`);
|
|
1015
|
+
return location.reload();
|
|
1016
|
+
}
|
|
1017
|
+
for (const [key, value] of Object.entries(newParams)) {
|
|
1018
|
+
if (current.get(key) !== value) {
|
|
1019
|
+
current.set(key, value);
|
|
1020
|
+
changed = true;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
const hash = anchor ? `#${anchor}` : "";
|
|
1024
|
+
const query = current.toString();
|
|
1025
|
+
const newUrl = `${window.location.pathname}?${query}${hash}`;
|
|
1026
|
+
window.location.replace(newUrl);
|
|
1027
|
+
return location.reload();
|
|
1028
|
+
}
|
|
1029
|
+
function flagEmojiToCountryCode(flag) {
|
|
1030
|
+
const countryCode = [...flag]
|
|
1031
|
+
.map((char) => String.fromCharCode(char.codePointAt(0) - 0x1f1e6 + 0x61))
|
|
1032
|
+
.join("");
|
|
1033
|
+
return countryCode.trim().toLowerCase();
|
|
1034
|
+
}
|
|
1035
|
+
const wait = (timeout = 100, callback) => {
|
|
1036
|
+
return new Promise((resolve) => {
|
|
1037
|
+
setTimeout(() => {
|
|
1038
|
+
callback?.();
|
|
1039
|
+
resolve();
|
|
1040
|
+
}, timeout);
|
|
1041
|
+
});
|
|
1042
|
+
};
|
|
1043
|
+
exports.wait = wait;
|
|
1044
|
+
const dropdownOptionsFromStrings = (strings) => {
|
|
1045
|
+
const options = [];
|
|
1046
|
+
const optionsIds = [];
|
|
1047
|
+
for (const str of strings) {
|
|
1048
|
+
if (!str.trim() || !_typesValidation_1.validate.nonEmptyString(str))
|
|
1049
|
+
continue;
|
|
1050
|
+
const sluggifiedStr = str
|
|
1051
|
+
.trim()
|
|
1052
|
+
?.replaceAll(" ", "_")
|
|
1053
|
+
.toLowerCase();
|
|
1054
|
+
if (optionsIds?.includes(sluggifiedStr))
|
|
1055
|
+
continue;
|
|
1056
|
+
optionsIds.push(sluggifiedStr);
|
|
1057
|
+
const option = {
|
|
1058
|
+
label: str,
|
|
1059
|
+
value: sluggifiedStr,
|
|
1060
|
+
id: sluggifiedStr,
|
|
1061
|
+
};
|
|
1062
|
+
options.push(option);
|
|
1063
|
+
}
|
|
1064
|
+
return options || [];
|
|
1065
|
+
};
|
|
1066
|
+
exports.dropdownOptionsFromStrings = dropdownOptionsFromStrings;
|
|
1067
|
+
/** Redact likely-sensitive data and normalize message */
|
|
1068
|
+
function sanitizeMessageSensitiveData(msg, maxLen = 300) {
|
|
1069
|
+
let s = typeof msg === "string" ? msg : String(msg);
|
|
1070
|
+
// Normalize whitespace (flattens multi-line content)
|
|
1071
|
+
s = s.replace(_regexPatterns_1.RE_WS, " ").trim();
|
|
1072
|
+
// Redactions
|
|
1073
|
+
s = s.replace(_regexPatterns_1.RE_EMAIL, "[EMAIL]");
|
|
1074
|
+
s = s.replace(_regexPatterns_1.RE_QUERY_SENSITIVE, (_m, sep, k) => `${sep}${k}=[REDACTED]`);
|
|
1075
|
+
s = s.replace(_regexPatterns_1.RE_AUTH, (_m, k) => `${k} [REDACTED]`);
|
|
1076
|
+
s = s.replace(_regexPatterns_1.RE_AWS_ACCESS_KEY, "[AWS_ACCESS_KEY]");
|
|
1077
|
+
s = s.replace(_regexPatterns_1.RE_SECRET40, "[SECRET_40]");
|
|
1078
|
+
s = s.replace(_regexPatterns_1.RE_JWT, "[JWT]");
|
|
1079
|
+
s = s.replace(_regexPatterns_1.RE_LONG_DIGITS, "[NUMBER]");
|
|
1080
|
+
s = s.replace(_regexPatterns_1.RE_PEM_BLOCK, "[PEM]");
|
|
1081
|
+
// IPv4 without lookbehind: keep the prefix char (group 1) intact
|
|
1082
|
+
s = s.replace(_regexPatterns_1.RE_IPV4_NOLB, (_m, pre) => `${pre}[IP]`);
|
|
1083
|
+
// Secrets in URL path segments
|
|
1084
|
+
s = s.replace(_regexPatterns_1.RE_PATH_SECRET, (m) => {
|
|
1085
|
+
const i = m.lastIndexOf("/");
|
|
1086
|
+
return m.slice(0, i + 1) + "[REDACTED]";
|
|
1087
|
+
});
|
|
1088
|
+
// Truncate
|
|
1089
|
+
if (s.length > maxLen)
|
|
1090
|
+
s = s.slice(0, maxLen) + "… (truncated)";
|
|
1091
|
+
return s;
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
*
|
|
1095
|
+
* @param err The error to extract message and code from
|
|
1096
|
+
* @param sanitize Wether to sanitize the error message, removing potential sensitive data
|
|
1097
|
+
* @returns Error info in the format of { message, code}
|
|
1098
|
+
*/
|
|
1099
|
+
function getErrorInfo(err, sanitize = true) {
|
|
1100
|
+
let message;
|
|
1101
|
+
let code;
|
|
1102
|
+
const pull = (e, depth = 0) => {
|
|
1103
|
+
if (!e || depth > 3)
|
|
1104
|
+
return;
|
|
1105
|
+
// Message from common spots (never use String(e) here)
|
|
1106
|
+
if (!message && typeof e.message === "string")
|
|
1107
|
+
message = e.message;
|
|
1108
|
+
// Common code fields across runtimes/libs
|
|
1109
|
+
if (code == null) {
|
|
1110
|
+
code =
|
|
1111
|
+
e.code ??
|
|
1112
|
+
e.status ??
|
|
1113
|
+
e.statusCode ??
|
|
1114
|
+
e.errorCode ??
|
|
1115
|
+
e.response?.status ??
|
|
1116
|
+
e.body?.status;
|
|
1117
|
+
}
|
|
1118
|
+
// HTTP/API payload shapes
|
|
1119
|
+
if (!message) {
|
|
1120
|
+
const m = e.response?.data?.message ??
|
|
1121
|
+
e.response?.data?.error?.message ??
|
|
1122
|
+
e.data?.message ??
|
|
1123
|
+
e.error?.message ??
|
|
1124
|
+
e.body?.message;
|
|
1125
|
+
if (typeof m === "string")
|
|
1126
|
+
message = m;
|
|
1127
|
+
}
|
|
1128
|
+
// Aggregate/cause chains
|
|
1129
|
+
if (!message && Array.isArray(e.errors) && e.errors.length)
|
|
1130
|
+
pull(e.errors[0], depth + 1);
|
|
1131
|
+
if (!message && e.cause)
|
|
1132
|
+
pull(e.cause, depth + 1);
|
|
1133
|
+
};
|
|
1134
|
+
if (err instanceof Error || (typeof err === "object" && err !== null)) {
|
|
1135
|
+
pull(err);
|
|
1136
|
+
}
|
|
1137
|
+
else if (typeof err === "string") {
|
|
1138
|
+
// Only case where we accept String(err): it's already a string.
|
|
1139
|
+
message = err;
|
|
1140
|
+
}
|
|
1141
|
+
// Final fallback: never fabricate by stringifying (could include stack).
|
|
1142
|
+
if (!message)
|
|
1143
|
+
message = "Unknown error";
|
|
1144
|
+
let finalMessage = "";
|
|
1145
|
+
if (sanitize)
|
|
1146
|
+
finalMessage = sanitizeMessageSensitiveData(message);
|
|
1147
|
+
else
|
|
1148
|
+
finalMessage = message;
|
|
1149
|
+
return code != null ? { message: finalMessage, code } : { message: finalMessage };
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Serializes an arbitrary values into a deterministic, hash-friendly string.
|
|
1153
|
+
*
|
|
1154
|
+
* - Objects are serialized with sorted keys to ensure stable ordering.
|
|
1155
|
+
* - Arrays are serialized by preserving element order.
|
|
1156
|
+
* - Dates are converted to ISO strings.
|
|
1157
|
+
* - Files are represented by stable metadata (name, size, lastModified).
|
|
1158
|
+
*
|
|
1159
|
+
* @param value - The value or values to serialize.
|
|
1160
|
+
* @returns A deterministic string representation.
|
|
1161
|
+
*/
|
|
1162
|
+
function serializeToString(value) {
|
|
1163
|
+
if (value === null || value === undefined)
|
|
1164
|
+
return '';
|
|
1165
|
+
if (typeof value === 'string')
|
|
1166
|
+
return value;
|
|
1167
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
1168
|
+
return String(value);
|
|
1169
|
+
if (value instanceof Date)
|
|
1170
|
+
return value.toISOString();
|
|
1171
|
+
if (value instanceof File) {
|
|
1172
|
+
// Represent File by stable metadata only, not by its binary content
|
|
1173
|
+
return `file:${value.name}:${value.size}:${value.lastModified}`;
|
|
1174
|
+
}
|
|
1175
|
+
if (Array.isArray(value)) {
|
|
1176
|
+
// Serialize each element and preserve order
|
|
1177
|
+
return `[${value.map(serializeToString).join(',')}]`;
|
|
1178
|
+
}
|
|
1179
|
+
if (typeof value === 'object') {
|
|
1180
|
+
// Ensure deterministic key order for object properties
|
|
1181
|
+
const entries = Object.entries(value).sort(([a], [b]) => a.localeCompare(b));
|
|
1182
|
+
return `{${entries
|
|
1183
|
+
.map(([k, v]) => `${k}:${serializeToString(v)}`)
|
|
1184
|
+
.join(',')}}`;
|
|
1185
|
+
}
|
|
1186
|
+
// Fallback for other types
|
|
1187
|
+
return String(value);
|
|
1188
|
+
}
|
|
1189
|
+
// Build a deterministic string from an array of values
|
|
1190
|
+
function createHashInput(values, separator = '|') {
|
|
1191
|
+
return values.map(serializeToString).join(separator);
|
|
1192
|
+
}
|
|
1193
|
+
function arrayIncludesString(arr, needle, caseSensitive = false) {
|
|
1194
|
+
if (!Array.isArray(arr) || typeof needle !== "string")
|
|
1195
|
+
return false;
|
|
1196
|
+
if (caseSensitive)
|
|
1197
|
+
return arr.includes(needle);
|
|
1198
|
+
const n = needle.toLowerCase();
|
|
1199
|
+
return arr.some((s) => typeof s === "string" && s.toLowerCase() === n);
|
|
1200
|
+
}
|