uni-manager 0.0.79 → 0.1.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/fesm2022/uni-manager-error.mjs +69 -0
- package/fesm2022/uni-manager-error.mjs.map +1 -0
- package/fesm2022/uni-manager-file.mjs +82 -0
- package/fesm2022/uni-manager-file.mjs.map +1 -0
- package/fesm2022/uni-manager-http.mjs +677 -0
- package/fesm2022/uni-manager-http.mjs.map +1 -0
- package/fesm2022/uni-manager-locale.mjs +168 -0
- package/fesm2022/uni-manager-locale.mjs.map +1 -0
- package/fesm2022/uni-manager-toast.mjs +59 -0
- package/fesm2022/uni-manager-toast.mjs.map +1 -0
- package/fesm2022/uni-manager-type.mjs +149 -0
- package/fesm2022/uni-manager-type.mjs.map +1 -0
- package/fesm2022/uni-manager.mjs +655 -644
- package/fesm2022/uni-manager.mjs.map +1 -1
- package/package.json +26 -2
- package/types/uni-manager-error.d.ts +27 -0
- package/types/uni-manager-file.d.ts +25 -0
- package/types/uni-manager-http.d.ts +126 -0
- package/types/uni-manager-locale.d.ts +63 -0
- package/types/uni-manager-toast.d.ts +39 -0
- package/types/uni-manager-type.d.ts +40 -0
- package/types/uni-manager.d.ts +31 -39
package/fesm2022/uni-manager.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
|
|
2
2
|
import { map } from 'rxjs/internal/operators/map';
|
|
3
3
|
import { tap } from 'rxjs/internal/operators/tap';
|
|
4
|
-
import {
|
|
4
|
+
import { UniToastManager as UniToastManager$1 } from 'uni-manager/toast';
|
|
5
5
|
import isEqual from 'lodash-es/isEqual';
|
|
6
6
|
import { defer } from 'rxjs/internal/observable/defer';
|
|
7
7
|
import { from } from 'rxjs/internal/observable/from';
|
|
@@ -9,6 +9,7 @@ import { timer } from 'rxjs/internal/observable/timer';
|
|
|
9
9
|
import { catchError } from 'rxjs/internal/operators/catchError';
|
|
10
10
|
import { distinctUntilChanged } from 'rxjs/internal/operators/distinctUntilChanged';
|
|
11
11
|
import { finalize } from 'rxjs/internal/operators/finalize';
|
|
12
|
+
import { UniErrorManager as UniErrorManager$1 } from 'uni-manager/error';
|
|
12
13
|
import { EMPTY } from 'rxjs/internal/observable/empty';
|
|
13
14
|
import { of } from 'rxjs/internal/observable/of';
|
|
14
15
|
import { throwError } from 'rxjs/internal/observable/throwError';
|
|
@@ -16,6 +17,9 @@ import { concatMap } from 'rxjs/internal/operators/concatMap';
|
|
|
16
17
|
import { exhaustMap } from 'rxjs/internal/operators/exhaustMap';
|
|
17
18
|
import { mergeMap } from 'rxjs/internal/operators/mergeMap';
|
|
18
19
|
import { switchMap } from 'rxjs/internal/operators/switchMap';
|
|
20
|
+
import { UniHttpError, isHttpErrorBody } from 'uni-error/http';
|
|
21
|
+
import { UniTypeDateManager as UniTypeDateManager$1 } from 'uni-manager/type';
|
|
22
|
+
import { UniLocaleManager as UniLocaleManager$1 } from 'uni-manager/locale';
|
|
19
23
|
|
|
20
24
|
class UniErrorManager {
|
|
21
25
|
/* ------------------------------------------------------------------------------- */
|
|
@@ -184,401 +188,6 @@ var EmitValueMode;
|
|
|
184
188
|
EmitValueMode[EmitValueMode["EVERY_TIME"] = 1] = "EVERY_TIME";
|
|
185
189
|
})(EmitValueMode || (EmitValueMode = {}));
|
|
186
190
|
|
|
187
|
-
class UniLocaleManager {
|
|
188
|
-
/* ------------------------------------------------------------------------------- */
|
|
189
|
-
/* ----------------------------------- Config ------------------------------------ */
|
|
190
|
-
/* ------------------------------------------------------------------------------- */
|
|
191
|
-
/** Codice lingua corrente (es. 'it-IT', 'en-US') usato per formattare date e numeri */
|
|
192
|
-
static { this._locale = navigator.language ?? 'en-US'; }
|
|
193
|
-
/** Elenco dei codici lingua supportati dall'applicazione (es. ['it-IT', 'en-US']) */
|
|
194
|
-
static { this._localesSupported = []; }
|
|
195
|
-
/** Prefisso globale applicato a tutte le chiavi di traduzione (es. 'APP_') */
|
|
196
|
-
static { this._prefix = undefined; }
|
|
197
|
-
/* ------------------------------------------------------------------------------- */
|
|
198
|
-
/* --------------------------------- Metodi: get --------------------------------- */
|
|
199
|
-
/* ------------------------------------------------------------------------------- */
|
|
200
|
-
/** Restituisce il codice lingua corrente */
|
|
201
|
-
static get locale() {
|
|
202
|
-
return this._locale;
|
|
203
|
-
}
|
|
204
|
-
/** Restituisce l'array contenente tutti i codici locale supportati.*/
|
|
205
|
-
static get localesSupported() {
|
|
206
|
-
return this._localesSupported;
|
|
207
|
-
}
|
|
208
|
-
/** Restituisce solo la lingua (es. 'it') */
|
|
209
|
-
static get language() {
|
|
210
|
-
return this._locale.split('-')[0];
|
|
211
|
-
}
|
|
212
|
-
/** Restituisce solo il paese (es. 'IT') */
|
|
213
|
-
static get region() {
|
|
214
|
-
const parts = this._locale.split('-');
|
|
215
|
-
return parts.length > 1 ? parts[1] : '';
|
|
216
|
-
}
|
|
217
|
-
/* ------------------------------------------------------------------------------- */
|
|
218
|
-
/* ------------------------------------ Store ------------------------------------ */
|
|
219
|
-
/* ------------------------------------------------------------------------------- */
|
|
220
|
-
/** Store privato (Subject) */
|
|
221
|
-
static { this.store = new BehaviorSubject({}); }
|
|
222
|
-
/** Store pubblico (Observable) */
|
|
223
|
-
static { this.store$ = this.store.asObservable(); }
|
|
224
|
-
/** Ottiene il dizionario attuale senza sottoscrizione */
|
|
225
|
-
static get currentValue() {
|
|
226
|
-
return this.store.getValue();
|
|
227
|
-
}
|
|
228
|
-
/* ------------------------------------------------------------------------------- */
|
|
229
|
-
/* -------------------------------- Metodi: setup -------------------------------- */
|
|
230
|
-
/* ------------------------------------------------------------------------------- */
|
|
231
|
-
/**
|
|
232
|
-
* Inizializza la configurazione di rete del manager.
|
|
233
|
-
* Deve essere chiamato prima di effettuare qualsiasi richiesta HTTP.
|
|
234
|
-
*/
|
|
235
|
-
static setup(locale, prefix) {
|
|
236
|
-
this._locale = locale ?? 'en-US';
|
|
237
|
-
this._prefix = prefix;
|
|
238
|
-
}
|
|
239
|
-
/** Imposta l'elenco dei codici lingua supportati dall'applicazione */
|
|
240
|
-
static setLocalesSupported(locales) {
|
|
241
|
-
this._localesSupported = locales ?? [];
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* Aggiorna lo store locale con il dizionario delle traduzioni fornito.
|
|
245
|
-
* Se viene passato undefined, lo store viene inizializzato come oggetto vuoto.
|
|
246
|
-
*/
|
|
247
|
-
static setTranslations(translations) {
|
|
248
|
-
this.store.next(translations ?? {});
|
|
249
|
-
}
|
|
250
|
-
/* ------------------------------------------------------------------------------- */
|
|
251
|
-
/* ----------------------------- Metodi: traduzioni ------------------------------ */
|
|
252
|
-
/* ------------------------------------------------------------------------------- */
|
|
253
|
-
/**
|
|
254
|
-
* Traduce una label in base al dizionario caricato.
|
|
255
|
-
* Gestisce la composizione della chiave, i parametri dinamici (interpolazione) e il fallback.
|
|
256
|
-
*/
|
|
257
|
-
static translate(key, prefix = 'lbl', interpolationParams) {
|
|
258
|
-
if (!key)
|
|
259
|
-
return '-';
|
|
260
|
-
// Costruzione chiave: prefissoGlobale + prefissoLocale + LabelConInizialeMaiuscola
|
|
261
|
-
const keyParts = key.trim().split(/(?=[A-Z])/);
|
|
262
|
-
const rootKeyParts = keyParts.filter((p) => p.toLowerCase() !== prefix.toLowerCase());
|
|
263
|
-
const cleanKey = rootKeyParts.length > 0 ? rootKeyParts.join('') : undefined;
|
|
264
|
-
const capitalizedKey = cleanKey ? cleanKey.charAt(0).toUpperCase() + cleanKey.slice(1) : '';
|
|
265
|
-
const finalKey = `${this._prefix ?? ''}${prefix}${capitalizedKey}`;
|
|
266
|
-
// Cerca la chiave in minuscolo (standardizzazione)
|
|
267
|
-
let translation = this.currentValue?.[finalKey.toLowerCase()];
|
|
268
|
-
// Log e fallback se la traduzione manca
|
|
269
|
-
if (!translation) {
|
|
270
|
-
console.warn(`Translation missing for key: ${finalKey}`);
|
|
271
|
-
return `🔑 ${finalKey}`;
|
|
272
|
-
}
|
|
273
|
-
// Interpolazione variabili
|
|
274
|
-
if (interpolationParams) {
|
|
275
|
-
for (const [key, value] of Object.entries(interpolationParams)) {
|
|
276
|
-
const displayValue = value instanceof Date ? value.toLocaleDateString(this._locale) : String(value);
|
|
277
|
-
translation = translation.replaceAll(`{{${key}}}`, displayValue);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
return translation;
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* Traduce una stringa che contiene parametri separati da un carattere specifico.
|
|
284
|
-
* Supporta ora un numero arbitrario di parametri in formato "label/param1/param2"
|
|
285
|
-
* o semplicemente "label".
|
|
286
|
-
*/
|
|
287
|
-
static translateInlineParams(keyWithParams, splitChar) {
|
|
288
|
-
if (!keyWithParams)
|
|
289
|
-
return '-';
|
|
290
|
-
const [label, ...params] = keyWithParams.split(splitChar);
|
|
291
|
-
// Se non ci sono parametri, esegui una traduzione semplice
|
|
292
|
-
if (params.length === 0) {
|
|
293
|
-
return this.translate(label);
|
|
294
|
-
}
|
|
295
|
-
// Mappa i parametri in un oggetto di interpolazione: { inlineParam0: val, inlineParam1: val, ... }
|
|
296
|
-
const interpolationParameters = {};
|
|
297
|
-
for (const [index, val] of params.entries()) {
|
|
298
|
-
interpolationParameters[`param${index}`] = val;
|
|
299
|
-
}
|
|
300
|
-
return this.translate(label, 'lbl', interpolationParameters);
|
|
301
|
-
}
|
|
302
|
-
/* ------------------------------------------------------------------------------- */
|
|
303
|
-
/* ------------------------------- Metodi: numeri -------------------------------- */
|
|
304
|
-
/* ------------------------------------------------------------------------------- */
|
|
305
|
-
/**
|
|
306
|
-
* Converte un valore numerico in una stringa formattata secondo il locale impostato.
|
|
307
|
-
* Disabilita i separatori delle migliaia e forza un numero fisso di decimali.
|
|
308
|
-
*/
|
|
309
|
-
static toStringNumber(value, decimal) {
|
|
310
|
-
return new Intl.NumberFormat(this._locale, {
|
|
311
|
-
useGrouping: true,
|
|
312
|
-
minimumFractionDigits: decimal,
|
|
313
|
-
maximumFractionDigits: decimal,
|
|
314
|
-
numberingSystem: 'latn',
|
|
315
|
-
}).format(value);
|
|
316
|
-
}
|
|
317
|
-
/* ------------------------------------------------------------------------------- */
|
|
318
|
-
/* -------------------------------- Metodi: date --------------------------------- */
|
|
319
|
-
/* ------------------------------------------------------------------------------- */
|
|
320
|
-
/**
|
|
321
|
-
* Formatta una data o una stringa in base al locale corrente.
|
|
322
|
-
* Gestisce tre modalità predefinite (date, time, full) e accetta opzioni personalizzate Intl.
|
|
323
|
-
*/
|
|
324
|
-
static toDate(date, mode = 'full', force) {
|
|
325
|
-
const dt = new Date(date);
|
|
326
|
-
// Controllo: validità data
|
|
327
|
-
if (Number.isNaN(dt.getTime())) {
|
|
328
|
-
console.error(`[UniLocaleManager] Data non valida fornita a formatDateTime:`, date);
|
|
329
|
-
return '';
|
|
330
|
-
}
|
|
331
|
-
// Controllo: locale da forzare
|
|
332
|
-
const locale = force && this._locale.startsWith(force.oldLang) ? force.newLocale : this._locale;
|
|
333
|
-
switch (mode) {
|
|
334
|
-
case 'date': {
|
|
335
|
-
return dt.toLocaleDateString(locale);
|
|
336
|
-
}
|
|
337
|
-
case 'time': {
|
|
338
|
-
return dt.toLocaleTimeString(locale);
|
|
339
|
-
}
|
|
340
|
-
case 'full': {
|
|
341
|
-
return dt.toLocaleString(locale);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
348
|
-
class UniToastManager {
|
|
349
|
-
/* ------------------------------------------------------------------------------- */
|
|
350
|
-
/* -------------------------------- Metodi: setup -------------------------------- */
|
|
351
|
-
/* ------------------------------------------------------------------------------- */
|
|
352
|
-
/**
|
|
353
|
-
* Inizializza il manager con una serie di operazioni
|
|
354
|
-
*/
|
|
355
|
-
static setup(operations) {
|
|
356
|
-
this.manager = operations;
|
|
357
|
-
}
|
|
358
|
-
/* ------------------------------------------------------------------------------- */
|
|
359
|
-
/* -------------------------------- Metodi: show --------------------------------- */
|
|
360
|
-
/* ------------------------------------------------------------------------------- */
|
|
361
|
-
/**
|
|
362
|
-
* Mostra un toast di successo basato su una risposta HTTP e una label di traduzione.
|
|
363
|
-
*/
|
|
364
|
-
static show(config) {
|
|
365
|
-
/* Messaggio tradotto */
|
|
366
|
-
const msg = UniLocaleManager.translate(config.label, 'toast', config.params);
|
|
367
|
-
const msgFixed = `${config.prefix ?? ''}${msg}${config.suffix ?? ''}`;
|
|
368
|
-
this.manager[config.type ?? 'info'](msgFixed, config);
|
|
369
|
-
}
|
|
370
|
-
/**
|
|
371
|
-
* Mostra un toast di successo basato su una risposta HTTP e una label di traduzione.
|
|
372
|
-
*/
|
|
373
|
-
static showHttp(config, res) {
|
|
374
|
-
const { interpolationParams, resParams, resLengthParam, formatters } = config;
|
|
375
|
-
/* Parametri statici di base */
|
|
376
|
-
const allParams = { ...interpolationParams };
|
|
377
|
-
/* Config parametri risposta: estrazione parametri dalla risposta (se esiste ed è un oggetto) */
|
|
378
|
-
if (resParams?.length && res && typeof res === 'object') {
|
|
379
|
-
for (const param of resParams ?? []) {
|
|
380
|
-
if (param in res)
|
|
381
|
-
continue;
|
|
382
|
-
// Se esiste un formatter per questa chiave lo si usa, altrimenti valore grezzo
|
|
383
|
-
const rawValue = res[param];
|
|
384
|
-
allParams[param] = formatters?.[param] ? formatters[param](rawValue) : rawValue;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
/* Config parametro lunghezza risposta: conteggio array */
|
|
388
|
-
if (resLengthParam && Array.isArray(res)) {
|
|
389
|
-
allParams[resLengthParam] = res.length;
|
|
390
|
-
}
|
|
391
|
-
/* Messaggio tradotto */
|
|
392
|
-
this.show({ ...config, params: allParams });
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
async function execute(url, init) {
|
|
397
|
-
try {
|
|
398
|
-
/* Esegue chiamata http */
|
|
399
|
-
const res = await fetch(url, init);
|
|
400
|
-
/* Nessun contenuto cons status 204 */
|
|
401
|
-
if (res.status === 204) {
|
|
402
|
-
return undefined;
|
|
403
|
-
}
|
|
404
|
-
/* Recupero se è un json */
|
|
405
|
-
const contentType = res.headers.get('content-type') ?? '';
|
|
406
|
-
const isJson = contentType.startsWith('application/json');
|
|
407
|
-
/* Errore HTTP (quindi risposta ricevuta ma non ok) */
|
|
408
|
-
if (!res.ok) {
|
|
409
|
-
let errorBody;
|
|
410
|
-
try {
|
|
411
|
-
/* Clona risposta solo se c'è errore, per leggerla senza consumare la originale */
|
|
412
|
-
const resClone = res.clone();
|
|
413
|
-
/* Aggiorna body con struttura errore */
|
|
414
|
-
errorBody = isJson ? await resClone.json() : await resClone.text();
|
|
415
|
-
}
|
|
416
|
-
catch {
|
|
417
|
-
errorBody = await res.text();
|
|
418
|
-
}
|
|
419
|
-
if (isHttpErrorBody(errorBody)) {
|
|
420
|
-
const type = errorBody.exceptionType.includes('HttpRequestException') ? 'be' : 'ice';
|
|
421
|
-
throw new UniHttpError(type, res.status, url, errorBody);
|
|
422
|
-
}
|
|
423
|
-
else {
|
|
424
|
-
const error = new Error(`HTTP ${res.statusText}`);
|
|
425
|
-
throw new UniHttpError('base', res.status, url, {
|
|
426
|
-
exceptionMessage: error.message,
|
|
427
|
-
exceptionType: 'ErrorBase',
|
|
428
|
-
message: error.message,
|
|
429
|
-
stackTrace: error.stack ?? '',
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
/* Risposta ok */
|
|
434
|
-
return res;
|
|
435
|
-
}
|
|
436
|
-
catch (error) {
|
|
437
|
-
if (error instanceof TypeError) {
|
|
438
|
-
throw new UniHttpError('network', -1, url, {
|
|
439
|
-
exceptionMessage: `${error.name}: ${error.message}\n${error.stack}`,
|
|
440
|
-
exceptionType: error.name,
|
|
441
|
-
message: error.message,
|
|
442
|
-
stackTrace: error.stack ?? '',
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
// Fallback
|
|
446
|
-
throw error;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
async function executeHttp(url, init) {
|
|
450
|
-
/* Esegue la chiamata HTTP e restituisce il corpo decodificato come JSON */
|
|
451
|
-
const res = await execute(url, init);
|
|
452
|
-
if (!res)
|
|
453
|
-
return undefined;
|
|
454
|
-
/* Estrae il contenuto come testo */
|
|
455
|
-
const text = await res.text();
|
|
456
|
-
/* Controllo: se il testo è vuoto (''), ritorna undefined */
|
|
457
|
-
if (!text || text.trim().length === 0) {
|
|
458
|
-
return undefined;
|
|
459
|
-
}
|
|
460
|
-
/* Parsa manualmente il testo, dato che lo stream è stato già letto */
|
|
461
|
-
return JSON.parse(text);
|
|
462
|
-
}
|
|
463
|
-
async function executeBlob(url, init) {
|
|
464
|
-
/* Esegue la chiamata HTTP */
|
|
465
|
-
const res = await execute(url, init);
|
|
466
|
-
if (!res)
|
|
467
|
-
return undefined;
|
|
468
|
-
/* Estrae il contenuto come Blob direttamente */
|
|
469
|
-
const blob = await res.blob();
|
|
470
|
-
/* Controllo: se il blob è vuoto (0 byte), ritorna undefined */
|
|
471
|
-
if (blob.size === 0) {
|
|
472
|
-
return undefined;
|
|
473
|
-
}
|
|
474
|
-
/* Estrae il nome del file dall'header */
|
|
475
|
-
const disposition = res.headers.get('Content-Disposition');
|
|
476
|
-
let fileName = 'download.pdf'; // Fallback di default
|
|
477
|
-
if (disposition) {
|
|
478
|
-
const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(disposition);
|
|
479
|
-
if (!!matches && matches[1]) {
|
|
480
|
-
fileName = matches[1].replaceAll(/['"]/g, '');
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
/* Genera l'URL temporaneo dal blob */
|
|
484
|
-
const fileUrl = URL.createObjectURL(blob);
|
|
485
|
-
return { url: fileUrl, name: fileName };
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
/**
|
|
489
|
-
* Aggiunge una nuova ref nello store solo se non è già presente.
|
|
490
|
-
* Se l'ID esiste già, l'operazione viene interrotta per preservare il dato originale.
|
|
491
|
-
*/
|
|
492
|
-
function add(id, url, type) {
|
|
493
|
-
// Recupera l'ultimo stato (Map)
|
|
494
|
-
const oldMap = UniHttpManager.currentValue;
|
|
495
|
-
// Controllo: se è presente l'item allora termina
|
|
496
|
-
if (oldMap.get(id))
|
|
497
|
-
return;
|
|
498
|
-
// Crea il nuovo oggetto
|
|
499
|
-
const newItemMap = {
|
|
500
|
-
type,
|
|
501
|
-
lineId: undefined,
|
|
502
|
-
url,
|
|
503
|
-
hasError: false,
|
|
504
|
-
pendingCount: 0,
|
|
505
|
-
};
|
|
506
|
-
// Crea una nuova istanza della Map
|
|
507
|
-
const newMap = new Map(oldMap);
|
|
508
|
-
newMap.set(id, newItemMap);
|
|
509
|
-
// Aggiorna il nuovo stato notificando l'observer
|
|
510
|
-
UniHttpManager.store.next(newMap);
|
|
511
|
-
}
|
|
512
|
-
/**
|
|
513
|
-
* Rimuove un http ref tramite ID
|
|
514
|
-
*/
|
|
515
|
-
function remove(id) {
|
|
516
|
-
// Recupera l'ultimo stato (Map)
|
|
517
|
-
const oldMap = UniHttpManager.currentValue;
|
|
518
|
-
// Controllo: se non è presente l'item allora termina
|
|
519
|
-
if (!oldMap.has(id))
|
|
520
|
-
return;
|
|
521
|
-
// Aggiorna store
|
|
522
|
-
const newMap = new Map(oldMap);
|
|
523
|
-
newMap.delete(id);
|
|
524
|
-
UniHttpManager.store.next(newMap);
|
|
525
|
-
}
|
|
526
|
-
/**
|
|
527
|
-
* Aggiorna il contatore delle chiamate pendenti per una specifica ref.
|
|
528
|
-
* Incrementa o decrementa 'pendingCount' garantendo che non scenda mai sotto lo zero.
|
|
529
|
-
* Se la ref non esiste, l'operazione viene ignorata.
|
|
530
|
-
*/
|
|
531
|
-
function updateIsLoading(id, delta) {
|
|
532
|
-
// Recupera l'ultimo stato (Map)
|
|
533
|
-
const oldMap = UniHttpManager.currentValue;
|
|
534
|
-
// Controllo: se non è presente l'item allora termina
|
|
535
|
-
const oldItem = oldMap.get(id);
|
|
536
|
-
if (!oldItem)
|
|
537
|
-
return;
|
|
538
|
-
// Aggiorna l'oggetto
|
|
539
|
-
const newItemMap = {
|
|
540
|
-
...oldItem,
|
|
541
|
-
pendingCount: Math.max(0, oldItem.pendingCount + delta),
|
|
542
|
-
};
|
|
543
|
-
// Crea una nuova istanza della Map
|
|
544
|
-
const newMap = new Map(oldMap);
|
|
545
|
-
newMap.set(id, newItemMap);
|
|
546
|
-
// Aggiorna il nuovo stato notificando l'observer
|
|
547
|
-
UniHttpManager.store.next(newMap);
|
|
548
|
-
}
|
|
549
|
-
/**
|
|
550
|
-
* Aggiorna lo stato di errore per una specifica ref.
|
|
551
|
-
* Se il valore di 'hasError' è identico a quello attuale o se la ref non esiste,
|
|
552
|
-
* l'operazione viene interrotta per evitare aggiornamenti inutili.
|
|
553
|
-
*/
|
|
554
|
-
function updateHasError(id, hasError) {
|
|
555
|
-
// Recupera l'ultimo stato (Map)
|
|
556
|
-
const oldMap = UniHttpManager.currentValue;
|
|
557
|
-
// Controllo: se non è presente l'item allora termina
|
|
558
|
-
const oldItem = oldMap.get(id);
|
|
559
|
-
if (!oldItem)
|
|
560
|
-
return;
|
|
561
|
-
// Controllo: se con lo stesso valore, salta
|
|
562
|
-
if (oldItem.hasError === hasError)
|
|
563
|
-
return;
|
|
564
|
-
// Aggiorna l'oggetto
|
|
565
|
-
const newItemMap = { ...oldItem, hasError };
|
|
566
|
-
// Crea una nuova istanza della Map
|
|
567
|
-
const newMap = new Map(oldMap);
|
|
568
|
-
newMap.set(id, newItemMap);
|
|
569
|
-
// Aggiorna il nuovo stato notificando l'observer
|
|
570
|
-
UniHttpManager.store.next(newMap);
|
|
571
|
-
}
|
|
572
|
-
/**
|
|
573
|
-
* Svuota completamente la lista delle refs
|
|
574
|
-
*/
|
|
575
|
-
function removeAll() {
|
|
576
|
-
// Crea una nuova istanza della Map
|
|
577
|
-
const newMap = new Map();
|
|
578
|
-
// Aggiorna il nuovo stato notificando l'observer
|
|
579
|
-
UniHttpManager.store.next(newMap);
|
|
580
|
-
}
|
|
581
|
-
|
|
582
191
|
function operatorHandler(operator) {
|
|
583
192
|
// Gestione della concorrenza in base al parametro
|
|
584
193
|
switch (operator) {
|
|
@@ -598,32 +207,36 @@ function operatorHandler(operator) {
|
|
|
598
207
|
}
|
|
599
208
|
function errorHandler(err, errorMode, ref) {
|
|
600
209
|
// Controllo: sia effettivamente un errore di tipo 'UniHttpError'
|
|
601
|
-
if (
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
// Aggiorna la ref nella map
|
|
605
|
-
updateHasError(ref, true);
|
|
606
|
-
// Gestione dell'errore in base al parametro
|
|
607
|
-
switch (errorMode) {
|
|
608
|
-
case PollingErrorMode.IGNORE: {
|
|
609
|
-
return of();
|
|
610
|
-
}
|
|
611
|
-
case PollingErrorMode.SKIP: {
|
|
612
|
-
return EMPTY;
|
|
613
|
-
}
|
|
614
|
-
case PollingErrorMode.IGNORE_WITH_ERROR: {
|
|
615
|
-
{
|
|
616
|
-
UniErrorManager.add(ref, err);
|
|
210
|
+
if (err instanceof UniHttpError) {
|
|
211
|
+
switch (errorMode) {
|
|
212
|
+
case PollingErrorMode.IGNORE: {
|
|
617
213
|
return of();
|
|
618
214
|
}
|
|
215
|
+
case PollingErrorMode.SKIP: {
|
|
216
|
+
return EMPTY;
|
|
217
|
+
}
|
|
218
|
+
case PollingErrorMode.IGNORE_WITH_ERROR: {
|
|
219
|
+
UniErrorManager$1.add(ref, err);
|
|
220
|
+
return of();
|
|
221
|
+
}
|
|
222
|
+
case PollingErrorMode.STOP: {
|
|
223
|
+
UniErrorManager$1.add(ref, err);
|
|
224
|
+
return throwError(() => err);
|
|
225
|
+
}
|
|
619
226
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
}
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
return throwError(() => err);
|
|
624
230
|
}
|
|
625
231
|
}
|
|
626
232
|
|
|
233
|
+
/* ------------------------------------------------------------------------------- */
|
|
234
|
+
/* -------------------------------- Funzioni Core RxJS -------------------------- */
|
|
235
|
+
/* ------------------------------------------------------------------------------- */
|
|
236
|
+
/**
|
|
237
|
+
* Gestisce l'esecuzione e il ciclo di vita di una singola richiesta HTTP.
|
|
238
|
+
* Si occupa della registrazione della reference, della gestione dei loader, dei toast di successo e dell'intercettazione degli errori.
|
|
239
|
+
*/
|
|
627
240
|
function http$(url, refType, config, promiseFactory) {
|
|
628
241
|
/* Recupero configurazione */
|
|
629
242
|
const { ref, toast, hasLoader } = config;
|
|
@@ -645,10 +258,12 @@ function http$(url, refType, config, promiseFactory) {
|
|
|
645
258
|
next: (res) => {
|
|
646
259
|
/* Mostra toast (se presente) */
|
|
647
260
|
if (toast) {
|
|
648
|
-
UniToastManager.showHttp(toast, res);
|
|
261
|
+
UniToastManager$1.showHttp(toast, res);
|
|
649
262
|
}
|
|
650
263
|
},
|
|
651
264
|
}), catchError((err) => {
|
|
265
|
+
// Aggiorna la ref nella map
|
|
266
|
+
updateHasError(ref, true);
|
|
652
267
|
/* Gestione errore */
|
|
653
268
|
return errorHandler(err, PollingErrorMode.STOP, ref);
|
|
654
269
|
}), finalize(() => {
|
|
@@ -659,6 +274,10 @@ function http$(url, refType, config, promiseFactory) {
|
|
|
659
274
|
}));
|
|
660
275
|
});
|
|
661
276
|
}
|
|
277
|
+
/**
|
|
278
|
+
* Avvia e coordina un ciclo di polling a intervalli regolari.
|
|
279
|
+
* Gestisce la concorrenza tramite operatori RxJS configurabili, la rimozione dei popup, di errore nelle iterazioni successive e i comportamenti custom al primo avvio.
|
|
280
|
+
*/
|
|
662
281
|
function httpPolling$(url, config, promiseFactory) {
|
|
663
282
|
/* Recupero configurazione */
|
|
664
283
|
const { interval, ref, operator = MapOperator.SWITCH_MAP, errorMode = PollingErrorMode.STOP, emitValueMode = EmitValueMode.ON_NEW_DATA, firstIteration, } = config;
|
|
@@ -680,16 +299,18 @@ function httpPolling$(url, config, promiseFactory) {
|
|
|
680
299
|
return defer(() => {
|
|
681
300
|
const http$ = from(promiseFactory());
|
|
682
301
|
return http$.pipe(tap((res) => {
|
|
683
|
-
/*
|
|
302
|
+
/* Meccanismo di ripristino automatico: se il polling torna in salute, cancella l'errore dallo store e nasconde i relativi messaggi a schermo */
|
|
684
303
|
if (errorMode === PollingErrorMode.IGNORE_WITH_ERROR) {
|
|
685
304
|
updateHasError(ref, false);
|
|
686
|
-
UniErrorManager.remove(ref);
|
|
305
|
+
UniErrorManager$1.remove(ref);
|
|
687
306
|
}
|
|
688
307
|
/* Prima risposta */
|
|
689
308
|
if (index === 0 && firstIteration?.toast) {
|
|
690
|
-
UniToastManager.showHttp(firstIteration.toast, res);
|
|
309
|
+
UniToastManager$1.showHttp(firstIteration.toast, res);
|
|
691
310
|
}
|
|
692
311
|
}), catchError((err) => {
|
|
312
|
+
// Aggiorna la ref nella map
|
|
313
|
+
updateHasError(ref, true);
|
|
693
314
|
/* Gestione errore */
|
|
694
315
|
return errorHandler(err, errorMode, ref);
|
|
695
316
|
}), finalize(() => {
|
|
@@ -704,164 +325,205 @@ function httpPolling$(url, config, promiseFactory) {
|
|
|
704
325
|
? source$.pipe(distinctUntilChanged((prev, cur) => isEqual(prev, cur)))
|
|
705
326
|
: source$;
|
|
706
327
|
}
|
|
707
|
-
|
|
328
|
+
/* ------------------------------------------------------------------------------- */
|
|
329
|
+
/* ------------------------------------ Utils ------------------------------------ */
|
|
330
|
+
/* ------------------------------------------------------------------------------- */
|
|
708
331
|
/**
|
|
709
|
-
*
|
|
710
|
-
*
|
|
332
|
+
* Aggiunge una nuova ref nello store solo se non è già presente.
|
|
333
|
+
* Se l'ID esiste già, l'operazione viene interrotta per preservare il dato originale.
|
|
711
334
|
*/
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
335
|
+
function add(id, url, type) {
|
|
336
|
+
// Recupera l'ultimo stato (Map)
|
|
337
|
+
const oldMap = UniHttpManager.currentValue;
|
|
338
|
+
// Controllo: se è presente l'item allora termina
|
|
339
|
+
if (oldMap.get(id))
|
|
340
|
+
return;
|
|
341
|
+
// Crea il nuovo oggetto
|
|
342
|
+
const newItemMap = {
|
|
343
|
+
type,
|
|
344
|
+
lineId: undefined,
|
|
345
|
+
url,
|
|
346
|
+
hasError: false,
|
|
347
|
+
pendingCount: 0,
|
|
348
|
+
};
|
|
349
|
+
// Crea una nuova istanza della Map
|
|
350
|
+
const newMap = new Map(oldMap);
|
|
351
|
+
newMap.set(id, newItemMap);
|
|
352
|
+
// Aggiorna il nuovo stato notificando l'observer
|
|
353
|
+
UniHttpManager.store.next(newMap);
|
|
724
354
|
}
|
|
725
|
-
|
|
726
355
|
/**
|
|
727
|
-
*
|
|
356
|
+
* Rimuove un http ref tramite ID
|
|
728
357
|
*/
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
358
|
+
function remove(id) {
|
|
359
|
+
// Recupera l'ultimo stato (Map)
|
|
360
|
+
const oldMap = UniHttpManager.currentValue;
|
|
361
|
+
// Controllo: se non è presente l'item allora termina
|
|
362
|
+
if (!oldMap.has(id))
|
|
363
|
+
return;
|
|
364
|
+
// Aggiorna store
|
|
365
|
+
const newMap = new Map(oldMap);
|
|
366
|
+
newMap.delete(id);
|
|
367
|
+
UniHttpManager.store.next(newMap);
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Aggiorna il contatore delle chiamate pendenti per una specifica ref.
|
|
371
|
+
* Incrementa o decrementa 'pendingCount' garantendo che non scenda mai sotto lo zero.
|
|
372
|
+
* Se la ref non esiste, l'operazione viene ignorata.
|
|
373
|
+
*/
|
|
374
|
+
function updateIsLoading(id, delta) {
|
|
375
|
+
// Recupera l'ultimo stato (Map)
|
|
376
|
+
const oldMap = UniHttpManager.currentValue;
|
|
377
|
+
// Controllo: se non è presente l'item allora termina
|
|
378
|
+
const oldItem = oldMap.get(id);
|
|
379
|
+
if (!oldItem)
|
|
380
|
+
return;
|
|
381
|
+
// Aggiorna l'oggetto
|
|
382
|
+
const newItemMap = {
|
|
383
|
+
...oldItem,
|
|
384
|
+
pendingCount: Math.max(0, oldItem.pendingCount + delta),
|
|
385
|
+
};
|
|
386
|
+
// Crea una nuova istanza della Map
|
|
387
|
+
const newMap = new Map(oldMap);
|
|
388
|
+
newMap.set(id, newItemMap);
|
|
389
|
+
// Aggiorna il nuovo stato notificando l'observer
|
|
390
|
+
UniHttpManager.store.next(newMap);
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Aggiorna lo stato di errore per una specifica ref.
|
|
394
|
+
* Se il valore di 'hasError' è identico a quello attuale o se la ref non esiste,
|
|
395
|
+
* l'operazione viene interrotta per evitare aggiornamenti inutili.
|
|
396
|
+
*/
|
|
397
|
+
function updateHasError(id, hasError) {
|
|
398
|
+
// Recupera l'ultimo stato (Map)
|
|
399
|
+
const oldMap = UniHttpManager.currentValue;
|
|
400
|
+
// Controllo: se non è presente l'item allora termina
|
|
401
|
+
const oldItem = oldMap.get(id);
|
|
402
|
+
if (!oldItem)
|
|
403
|
+
return;
|
|
404
|
+
// Controllo: se con lo stesso valore, salta
|
|
405
|
+
if (oldItem.hasError === hasError)
|
|
406
|
+
return;
|
|
407
|
+
// Aggiorna l'oggetto
|
|
408
|
+
const newItemMap = { ...oldItem, hasError };
|
|
409
|
+
// Crea una nuova istanza della Map
|
|
410
|
+
const newMap = new Map(oldMap);
|
|
411
|
+
newMap.set(id, newItemMap);
|
|
412
|
+
// Aggiorna il nuovo stato notificando l'observer
|
|
413
|
+
UniHttpManager.store.next(newMap);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async function execute(url, init) {
|
|
417
|
+
try {
|
|
418
|
+
/* Esegue chiamata http */
|
|
419
|
+
const res = await fetch(url, init);
|
|
420
|
+
/* Nessun contenuto cons status 204 */
|
|
421
|
+
if (res.status === 204) {
|
|
422
|
+
return undefined;
|
|
423
|
+
}
|
|
424
|
+
/* Risposta ok */
|
|
425
|
+
if (res.ok) {
|
|
426
|
+
return res;
|
|
427
|
+
}
|
|
428
|
+
/* Errore HTTP (quindi risposta ricevuta ma non ok) */
|
|
429
|
+
let errorBody;
|
|
430
|
+
try {
|
|
431
|
+
/* Clona risposta per leggerla senza consumare la originale */
|
|
432
|
+
const resClone = res.clone();
|
|
433
|
+
/* Recupero se è un json */
|
|
434
|
+
const contentType = res.headers.get('content-type') ?? '';
|
|
435
|
+
const isJson = contentType.startsWith('application/json');
|
|
436
|
+
/* Aggiorna body con struttura errore */
|
|
437
|
+
errorBody = isJson ? await resClone.json() : await resClone.text();
|
|
438
|
+
}
|
|
439
|
+
catch {
|
|
440
|
+
// Se il clone o il parsing falliscono, si usa una stringa di fallback sicura
|
|
441
|
+
errorBody = `Failed to parse error response body (Status: ${res.status})`;
|
|
442
|
+
}
|
|
443
|
+
if (isHttpErrorBody(errorBody)) {
|
|
444
|
+
const type = errorBody.exceptionType.includes('HttpRequestException') ? 'be' : 'ice';
|
|
445
|
+
throw new UniHttpError(type, res.status, url, errorBody);
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
const error = new Error(`HTTP ${res.statusText}`);
|
|
449
|
+
throw new UniHttpError('base', res.status, url, {
|
|
450
|
+
exceptionMessage: error.message,
|
|
451
|
+
exceptionType: 'ErrorBase',
|
|
452
|
+
message: error.message,
|
|
453
|
+
stackTrace: error.stack ?? '',
|
|
454
|
+
});
|
|
735
455
|
}
|
|
736
|
-
// Convertiamo in stringa e applichiamo il padding
|
|
737
|
-
return value.toString().padStart(count, character);
|
|
738
456
|
}
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
return value.toFixed(decimalDigits);
|
|
457
|
+
catch (error) {
|
|
458
|
+
// Se l'errore è già un UniHttpError (lanciato sopra) bypassa tutto il resto
|
|
459
|
+
if (error instanceof UniHttpError) {
|
|
460
|
+
throw error;
|
|
744
461
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
// Itera dalla scala più grande alla più piccola per trovare la soglia corretta
|
|
753
|
-
for (const { threshold, suffix } of scales) {
|
|
754
|
-
if (Math.abs(value) >= threshold) {
|
|
755
|
-
const reduced = value / threshold;
|
|
756
|
-
// parseFloat(toFixed()) rimuove gli zeri decimali superflui (es: 1.50 -> 1.5)
|
|
757
|
-
// aggiungendo poi il relativo suffisso (K, M, B, T)
|
|
758
|
-
return Number.parseFloat(reduced.toFixed(decimalDigits)).toString() + suffix;
|
|
759
|
-
}
|
|
462
|
+
if (error instanceof TypeError) {
|
|
463
|
+
throw new UniHttpError('network', -1, url, {
|
|
464
|
+
exceptionMessage: `${error.name}: ${error.message}\n${error.stack}`,
|
|
465
|
+
exceptionType: error.name,
|
|
466
|
+
message: error.message,
|
|
467
|
+
stackTrace: error.stack ?? '',
|
|
468
|
+
});
|
|
760
469
|
}
|
|
761
|
-
// Fallback
|
|
762
|
-
|
|
470
|
+
// Fallback per qualsiasi altro tipo di errore sconosciuto
|
|
471
|
+
throw error;
|
|
763
472
|
}
|
|
764
473
|
}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
// Pulizia chiave
|
|
776
|
-
const fixedKey = key.trim();
|
|
777
|
-
// Unisce il prefisso
|
|
778
|
-
return prefix + this.toPascalCase(fixedKey);
|
|
779
|
-
}
|
|
780
|
-
/** Converte una stringa in PascalCase (es. "user_id" -> "UserId") */
|
|
781
|
-
static toPascalCase(key) {
|
|
782
|
-
// Se vuoto restituisce vuoto
|
|
783
|
-
if (!key)
|
|
784
|
-
return '';
|
|
785
|
-
// Pulizia chiave
|
|
786
|
-
const fixedKey = key.trim();
|
|
787
|
-
// Normalizza e pulisce
|
|
788
|
-
const parts = this.splitString(fixedKey);
|
|
789
|
-
if (parts.length === 0)
|
|
790
|
-
return '';
|
|
791
|
-
// Converte in PascalCase
|
|
792
|
-
const pascalCased = this.toPascalCaseParts(parts);
|
|
793
|
-
return pascalCased;
|
|
794
|
-
}
|
|
795
|
-
/** Converte una stringa in camelCase (es. "user_id" -> "userId") */
|
|
796
|
-
static toCamelCase(key) {
|
|
797
|
-
// Se vuoto restituisce vuoto
|
|
798
|
-
if (!key)
|
|
799
|
-
return '';
|
|
800
|
-
// Pulizia chiave
|
|
801
|
-
const fixedKey = key.trim();
|
|
802
|
-
// Normalizza e pulisce
|
|
803
|
-
const parts = this.splitString(fixedKey);
|
|
804
|
-
if (parts.length === 0)
|
|
805
|
-
return '';
|
|
806
|
-
// La prima parola resta minuscola, le successive PascalCase
|
|
807
|
-
const first = parts[0].toLowerCase();
|
|
808
|
-
const rest = parts.slice(1);
|
|
809
|
-
return first + this.toPascalCaseParts(rest);
|
|
810
|
-
}
|
|
811
|
-
/** Capitalizza solo la prima lettera della stringa */
|
|
812
|
-
static toCapitalize(key) {
|
|
813
|
-
// Se vuoto restituisce vuoto
|
|
814
|
-
if (!key)
|
|
815
|
-
return '';
|
|
816
|
-
// Pulizia chiave
|
|
817
|
-
const fixedKey = key.trim();
|
|
818
|
-
return fixedKey.charAt(0).toUpperCase() + fixedKey.slice(1);
|
|
819
|
-
}
|
|
820
|
-
/** Sostituisce sotto-stringhe all'interno della stringa */
|
|
821
|
-
static toReplace(key, values) {
|
|
822
|
-
// Se vuoto restituisce vuoto
|
|
823
|
-
if (!key)
|
|
824
|
-
return '';
|
|
825
|
-
// Pulizia chiave
|
|
826
|
-
let fixedKey = key.trim();
|
|
827
|
-
for (const value of values) {
|
|
828
|
-
fixedKey = fixedKey.replaceAll(value.searchValue, value.replaceValue);
|
|
829
|
-
}
|
|
830
|
-
return fixedKey;
|
|
474
|
+
async function executeHttp(url, init) {
|
|
475
|
+
/* Esegue la chiamata HTTP e restituisce il corpo decodificato come JSON */
|
|
476
|
+
const res = await execute(url, init);
|
|
477
|
+
if (!res)
|
|
478
|
+
return undefined;
|
|
479
|
+
/* Estrae il contenuto come testo */
|
|
480
|
+
const text = await res.text();
|
|
481
|
+
/* Controllo: se il testo è vuoto (''), ritorna undefined */
|
|
482
|
+
if (!text || text.trim().length === 0) {
|
|
483
|
+
return undefined;
|
|
831
484
|
}
|
|
832
|
-
/*
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
485
|
+
/* Parsa manualmente il testo, dato che lo stream è stato già letto */
|
|
486
|
+
return JSON.parse(text);
|
|
487
|
+
}
|
|
488
|
+
async function executeBlob(url, init) {
|
|
489
|
+
/* Esegue la chiamata HTTP */
|
|
490
|
+
const res = await execute(url, init);
|
|
491
|
+
if (!res)
|
|
492
|
+
return undefined;
|
|
493
|
+
/* Estrae il contenuto come Blob direttamente */
|
|
494
|
+
const blob = await res.blob();
|
|
495
|
+
/* Controllo: se il blob è vuoto (0 byte), ritorna undefined */
|
|
496
|
+
if (blob.size === 0) {
|
|
497
|
+
return undefined;
|
|
845
498
|
}
|
|
846
|
-
|
|
847
|
-
|
|
499
|
+
/* Estrae il nome del file dall'header */
|
|
500
|
+
const disposition = res.headers.get('Content-Disposition');
|
|
501
|
+
let fileName = 'download.pdf'; // Fallback di default
|
|
502
|
+
if (disposition) {
|
|
503
|
+
const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(disposition);
|
|
504
|
+
if (!!matches && matches[1]) {
|
|
505
|
+
fileName = matches[1].replaceAll(/['"]/g, '');
|
|
506
|
+
}
|
|
848
507
|
}
|
|
508
|
+
/* Genera l'URL temporaneo dal blob */
|
|
509
|
+
const fileUrl = URL.createObjectURL(blob);
|
|
510
|
+
return { url: fileUrl, name: fileName };
|
|
849
511
|
}
|
|
850
512
|
|
|
851
513
|
/**
|
|
852
514
|
* Costruisce un URL completo per l'API partendo dai parametri di configurazione.
|
|
853
515
|
*/
|
|
854
516
|
function getUrl(hostname, port, config) {
|
|
855
|
-
const {
|
|
517
|
+
const { pathParams, path, hasApiPrefix } = config;
|
|
856
518
|
// Costruzione url
|
|
857
|
-
const prefix = hasApiPrefix ? '
|
|
519
|
+
const prefix = hasApiPrefix === false ? '' : 'api';
|
|
858
520
|
const cleanPath = path.startsWith('/') ? path.slice(1) : path;
|
|
859
521
|
const pathFixed = prefix ? `${prefix}/${cleanPath}` : cleanPath;
|
|
860
522
|
const url = new URL(pathFixed, `http://${hostname}:${port}`);
|
|
861
|
-
if (
|
|
523
|
+
if (pathParams) {
|
|
862
524
|
// Regex per intercettare stringhe in formato ISO string
|
|
863
525
|
const isoDateRegex = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?)?$/;
|
|
864
|
-
for (const [key, rawValue] of Object.entries(
|
|
526
|
+
for (const [key, rawValue] of Object.entries(pathParams)) {
|
|
865
527
|
if (rawValue === undefined || rawValue === null) {
|
|
866
528
|
continue;
|
|
867
529
|
}
|
|
@@ -874,14 +536,14 @@ function getUrl(hostname, port, config) {
|
|
|
874
536
|
let formattedValue;
|
|
875
537
|
// Caso: Date nativo
|
|
876
538
|
if (item instanceof Date) {
|
|
877
|
-
formattedValue = UniTypeDateManager.toYYYYMMDD(item);
|
|
539
|
+
formattedValue = UniTypeDateManager$1.toYYYYMMDD(item);
|
|
878
540
|
}
|
|
879
541
|
// Caso: Date formato ISO string
|
|
880
542
|
else if (typeof item === 'string' && isoDateRegex.test(item)) {
|
|
881
543
|
const parsedDate = new Date(item);
|
|
882
544
|
formattedValue = Number.isNaN(parsedDate.getTime())
|
|
883
545
|
? item
|
|
884
|
-
: UniTypeDateManager.toYYYYMMDD(parsedDate);
|
|
546
|
+
: UniTypeDateManager$1.toYYYYMMDD(parsedDate);
|
|
885
547
|
}
|
|
886
548
|
// Default
|
|
887
549
|
else {
|
|
@@ -931,7 +593,10 @@ function normalizeHttpBody(body, isRoot = true) {
|
|
|
931
593
|
return Object.fromEntries(entries);
|
|
932
594
|
}
|
|
933
595
|
|
|
934
|
-
const CONFIG_TOAST_DEFAULT = {
|
|
596
|
+
const CONFIG_TOAST_DEFAULT = {
|
|
597
|
+
type: 'success',
|
|
598
|
+
label: 'OperationCompleted',
|
|
599
|
+
};
|
|
935
600
|
class UniHttpManager {
|
|
936
601
|
/* ------------------------------------------------------------------------------- */
|
|
937
602
|
/* --------------------------------- Metodi: get --------------------------------- */
|
|
@@ -962,12 +627,6 @@ class UniHttpManager {
|
|
|
962
627
|
this.hostname = hostname;
|
|
963
628
|
this.port = port;
|
|
964
629
|
}
|
|
965
|
-
/**
|
|
966
|
-
* Imposta o resetta lo stato di errore per una specifica reference.
|
|
967
|
-
*/
|
|
968
|
-
static updateHasError(id, hasError) {
|
|
969
|
-
updateHasError(id, hasError);
|
|
970
|
-
}
|
|
971
630
|
/* ------------------------------------------------------------------------------- */
|
|
972
631
|
/* -------------------------------- Metodi: CRUD --------------------------------- */
|
|
973
632
|
/* ------------------------------------------------------------------------------- */
|
|
@@ -981,11 +640,11 @@ class UniHttpManager {
|
|
|
981
640
|
...config.init,
|
|
982
641
|
method: 'GET',
|
|
983
642
|
};
|
|
984
|
-
/*
|
|
643
|
+
/* API */
|
|
985
644
|
const url = getUrl(this.hostname, this.port, config);
|
|
986
645
|
return http$(url, 'one', config, () => executeHttp(url, initCustom)).pipe(tap((res) => {
|
|
987
646
|
if (Array.isArray(res) && config.toast === undefined) {
|
|
988
|
-
UniToastManager.show({
|
|
647
|
+
UniToastManager$1.show({
|
|
989
648
|
label: 'ItemsFound',
|
|
990
649
|
params: { count: res.length },
|
|
991
650
|
});
|
|
@@ -1007,12 +666,12 @@ class UniHttpManager {
|
|
|
1007
666
|
},
|
|
1008
667
|
body: JSON.stringify(normalizeHttpBody(body)),
|
|
1009
668
|
};
|
|
1010
|
-
/*
|
|
669
|
+
/* Config custom (toast di default) */
|
|
1011
670
|
const configCustom = {
|
|
1012
671
|
...config,
|
|
1013
|
-
toast: config.
|
|
672
|
+
toast: config.hasToast === false ? undefined : (config.toast ?? CONFIG_TOAST_DEFAULT),
|
|
1014
673
|
};
|
|
1015
|
-
/*
|
|
674
|
+
/* API */
|
|
1016
675
|
const url = getUrl(this.hostname, this.port, config);
|
|
1017
676
|
return http$(url, 'one', configCustom, () => executeHttp(url, initCustom));
|
|
1018
677
|
}
|
|
@@ -1029,12 +688,12 @@ class UniHttpManager {
|
|
|
1029
688
|
};
|
|
1030
689
|
initCustom.body = JSON.stringify(normalizeHttpBody(body));
|
|
1031
690
|
}
|
|
1032
|
-
/*
|
|
691
|
+
/* Config custom (toast di default) */
|
|
1033
692
|
const configCustom = {
|
|
1034
693
|
...config,
|
|
1035
|
-
toast: config.
|
|
694
|
+
toast: config.hasToast === false ? undefined : (config.toast ?? CONFIG_TOAST_DEFAULT),
|
|
1036
695
|
};
|
|
1037
|
-
/*
|
|
696
|
+
/* API */
|
|
1038
697
|
const url = getUrl(this.hostname, this.port, config);
|
|
1039
698
|
return http$(url, 'one', configCustom, () => executeHttp(url, initCustom));
|
|
1040
699
|
}
|
|
@@ -1048,107 +707,459 @@ class UniHttpManager {
|
|
|
1048
707
|
...config.init,
|
|
1049
708
|
method: 'DELETE',
|
|
1050
709
|
};
|
|
1051
|
-
/*
|
|
710
|
+
/* Config custom (toast di default) */
|
|
1052
711
|
const configCustom = {
|
|
1053
712
|
...config,
|
|
1054
|
-
toast: config.
|
|
713
|
+
toast: config.hasToast === false ? undefined : (config.toast ?? CONFIG_TOAST_DEFAULT),
|
|
1055
714
|
};
|
|
1056
|
-
/*
|
|
715
|
+
/* API */
|
|
1057
716
|
const url = getUrl(this.hostname, this.port, config);
|
|
1058
717
|
return http$(url, 'one', configCustom, () => executeHttp(url, initCustom));
|
|
1059
718
|
}
|
|
1060
719
|
/* ------------------------------------------------------------------------------- */
|
|
1061
|
-
/* -------------------------- Metodi: CRUD file/image ---------------------------- */
|
|
720
|
+
/* -------------------------- Metodi: CRUD file/image ---------------------------- */
|
|
721
|
+
/* ------------------------------------------------------------------------------- */
|
|
722
|
+
/**
|
|
723
|
+
* Recupera un'immagine tramite una richiesta GET e la trasforma in un formato gestibile (es. Blob o Base64).
|
|
724
|
+
* Delega la logica di conversione alla funzione executeImage.
|
|
725
|
+
*/
|
|
726
|
+
static readImage$(config) {
|
|
727
|
+
/* Config */
|
|
728
|
+
const initCustom = {
|
|
729
|
+
...config.init,
|
|
730
|
+
method: 'GET',
|
|
731
|
+
};
|
|
732
|
+
/* API */
|
|
733
|
+
const url = getUrl(this.hostname, this.port, config);
|
|
734
|
+
return http$(url, 'image', config, () => executeBlob(url, initCustom));
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Recupera un file (es. PDF) tramite una richiesta GET e restituisce un Object URL temporaneo.
|
|
738
|
+
* Delega la logica di conversione alla funzione executeFile.
|
|
739
|
+
*/
|
|
740
|
+
static readFile$(config) {
|
|
741
|
+
/* Config */
|
|
742
|
+
const initCustom = {
|
|
743
|
+
...config.init,
|
|
744
|
+
method: 'GET',
|
|
745
|
+
};
|
|
746
|
+
/* Config custom (toast di default) */
|
|
747
|
+
const configCustom = {
|
|
748
|
+
...config,
|
|
749
|
+
toast: config.hasToast === false ? undefined : (config.toast ?? CONFIG_TOAST_DEFAULT),
|
|
750
|
+
};
|
|
751
|
+
/* API */
|
|
752
|
+
const url = getUrl(this.hostname, this.port, config);
|
|
753
|
+
return http$(url, 'file', configCustom, () => executeBlob(url, initCustom));
|
|
754
|
+
}
|
|
755
|
+
/* ------------------------------------------------------------------------------- */
|
|
756
|
+
/* ---------------------------- Metodi: CRUD polling ----------------------------- */
|
|
757
|
+
/* ------------------------------------------------------------------------------- */
|
|
758
|
+
/**
|
|
759
|
+
* Avvia un ciclo di polling basato su richieste GET.
|
|
760
|
+
* Continua a emettere valori in base alla configurazione di intervallo definita in HttpConfigPolling.
|
|
761
|
+
*/
|
|
762
|
+
static readPolling$(config) {
|
|
763
|
+
/* Config */
|
|
764
|
+
const initCustom = {
|
|
765
|
+
...config.init,
|
|
766
|
+
method: 'GET',
|
|
767
|
+
};
|
|
768
|
+
/* API */
|
|
769
|
+
const url = getUrl(this.hostname, this.port, config);
|
|
770
|
+
return httpPolling$(url, config, () => executeHttp(url, initCustom));
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Avvia un ciclo di polling basato su richieste POST.
|
|
774
|
+
* Invia il body specificato a ogni iterazione del ciclo.
|
|
775
|
+
*/
|
|
776
|
+
static createPolling$(config, body) {
|
|
777
|
+
/* Config */
|
|
778
|
+
const initCustom = {
|
|
779
|
+
...config.init,
|
|
780
|
+
method: 'POST',
|
|
781
|
+
headers: { 'Content-Type': 'application/json' },
|
|
782
|
+
body: JSON.stringify(body),
|
|
783
|
+
};
|
|
784
|
+
/* API */
|
|
785
|
+
const url = getUrl(this.hostname, this.port, config);
|
|
786
|
+
return httpPolling$(url, config, () => executeHttp(url, initCustom));
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Avvia un ciclo di polling basato su richieste PUT.
|
|
790
|
+
*/
|
|
791
|
+
static updatePolling$(config, body) {
|
|
792
|
+
/* Config */
|
|
793
|
+
const initCustom = {
|
|
794
|
+
...config.init,
|
|
795
|
+
method: 'PUT',
|
|
796
|
+
};
|
|
797
|
+
if (body !== undefined) {
|
|
798
|
+
initCustom.headers = {
|
|
799
|
+
...initCustom.headers,
|
|
800
|
+
'Content-Type': 'application/json',
|
|
801
|
+
};
|
|
802
|
+
initCustom.body = JSON.stringify(body);
|
|
803
|
+
}
|
|
804
|
+
/* API */
|
|
805
|
+
const url = getUrl(this.hostname, this.port, config);
|
|
806
|
+
return httpPolling$(url, config, () => executeHttp(url, initCustom));
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
class UniLocaleManager {
|
|
811
|
+
/* ------------------------------------------------------------------------------- */
|
|
812
|
+
/* ----------------------------------- Config ------------------------------------ */
|
|
813
|
+
/* ------------------------------------------------------------------------------- */
|
|
814
|
+
/** Codice lingua corrente (es. 'it-IT', 'en-US') usato per formattare date e numeri */
|
|
815
|
+
static { this._locale = navigator.language ?? 'en-US'; }
|
|
816
|
+
/** Elenco dei codici lingua supportati dall'applicazione (es. ['it-IT', 'en-US']) */
|
|
817
|
+
static { this._localesSupported = []; }
|
|
818
|
+
/** Prefisso globale applicato a tutte le chiavi di traduzione (es. 'APP_') */
|
|
819
|
+
static { this._prefix = undefined; }
|
|
820
|
+
/* ------------------------------------------------------------------------------- */
|
|
821
|
+
/* --------------------------------- Metodi: get --------------------------------- */
|
|
822
|
+
/* ------------------------------------------------------------------------------- */
|
|
823
|
+
/** Restituisce il codice lingua corrente */
|
|
824
|
+
static get locale() {
|
|
825
|
+
return this._locale;
|
|
826
|
+
}
|
|
827
|
+
/** Restituisce l'array contenente tutti i codici locale supportati.*/
|
|
828
|
+
static get localesSupported() {
|
|
829
|
+
return this._localesSupported;
|
|
830
|
+
}
|
|
831
|
+
/** Restituisce solo la lingua (es. 'it') */
|
|
832
|
+
static get language() {
|
|
833
|
+
return this._locale.split('-')[0];
|
|
834
|
+
}
|
|
835
|
+
/** Restituisce solo il paese (es. 'IT') */
|
|
836
|
+
static get region() {
|
|
837
|
+
const parts = this._locale.split('-');
|
|
838
|
+
return parts.length > 1 ? parts[1] : '';
|
|
839
|
+
}
|
|
840
|
+
/* ------------------------------------------------------------------------------- */
|
|
841
|
+
/* ------------------------------------ Store ------------------------------------ */
|
|
842
|
+
/* ------------------------------------------------------------------------------- */
|
|
843
|
+
/** Store privato (Subject) */
|
|
844
|
+
static { this.store = new BehaviorSubject({}); }
|
|
845
|
+
/** Store pubblico (Observable) */
|
|
846
|
+
static { this.store$ = this.store.asObservable(); }
|
|
847
|
+
/** Ottiene il dizionario attuale senza sottoscrizione */
|
|
848
|
+
static get currentValue() {
|
|
849
|
+
return this.store.getValue();
|
|
850
|
+
}
|
|
851
|
+
/* ------------------------------------------------------------------------------- */
|
|
852
|
+
/* -------------------------------- Metodi: setup -------------------------------- */
|
|
853
|
+
/* ------------------------------------------------------------------------------- */
|
|
854
|
+
/**
|
|
855
|
+
* Inizializza la configurazione di rete del manager.
|
|
856
|
+
* Deve essere chiamato prima di effettuare qualsiasi richiesta HTTP.
|
|
857
|
+
*/
|
|
858
|
+
static setup(locale, prefix) {
|
|
859
|
+
this._locale = locale ?? 'en-US';
|
|
860
|
+
this._prefix = prefix;
|
|
861
|
+
}
|
|
862
|
+
/** Imposta l'elenco dei codici lingua supportati dall'applicazione */
|
|
863
|
+
static setLocalesSupported(locales) {
|
|
864
|
+
this._localesSupported = locales ?? [];
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Aggiorna lo store locale con il dizionario delle traduzioni fornito.
|
|
868
|
+
* Se viene passato undefined, lo store viene inizializzato come oggetto vuoto.
|
|
869
|
+
*/
|
|
870
|
+
static setTranslations(translations) {
|
|
871
|
+
this.store.next(translations ?? {});
|
|
872
|
+
}
|
|
873
|
+
/* ------------------------------------------------------------------------------- */
|
|
874
|
+
/* ----------------------------- Metodi: traduzioni ------------------------------ */
|
|
875
|
+
/* ------------------------------------------------------------------------------- */
|
|
876
|
+
/**
|
|
877
|
+
* Traduce una label in base al dizionario caricato.
|
|
878
|
+
* Gestisce la composizione della chiave, i parametri dinamici (interpolazione) e il fallback.
|
|
879
|
+
*/
|
|
880
|
+
static translate(key, prefix = 'lbl', params) {
|
|
881
|
+
if (!key)
|
|
882
|
+
return '-';
|
|
883
|
+
// Costruzione chiave: prefissoGlobale + prefissoLocale + LabelConInizialeMaiuscola
|
|
884
|
+
const keyParts = key.trim().split(/(?=[A-Z])/);
|
|
885
|
+
const rootKeyParts = keyParts.filter((p) => p.toLowerCase() !== prefix.toLowerCase());
|
|
886
|
+
const cleanKey = rootKeyParts.length > 0 ? rootKeyParts.join('') : undefined;
|
|
887
|
+
const capitalizedKey = cleanKey ? cleanKey.charAt(0).toUpperCase() + cleanKey.slice(1) : '';
|
|
888
|
+
const finalKey = `${this._prefix ?? ''}${prefix}${capitalizedKey}`;
|
|
889
|
+
// Cerca la chiave in minuscolo (standardizzazione)
|
|
890
|
+
let translation = this.currentValue?.[finalKey.toLowerCase()];
|
|
891
|
+
// Log e fallback se la traduzione manca
|
|
892
|
+
if (!translation) {
|
|
893
|
+
console.warn(`Translation missing for key: ${finalKey}`);
|
|
894
|
+
return `🔑 ${finalKey}`;
|
|
895
|
+
}
|
|
896
|
+
// Interpolazione variabili
|
|
897
|
+
if (params) {
|
|
898
|
+
for (const [key, value] of Object.entries(params)) {
|
|
899
|
+
const displayValue = value instanceof Date ? value.toLocaleDateString(this._locale) : String(value);
|
|
900
|
+
translation = translation.replaceAll(`{{${key}}}`, displayValue);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
return translation;
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* Traduce una stringa che contiene parametri separati da un carattere specifico.
|
|
907
|
+
* Supporta ora un numero arbitrario di parametri in formato "label/param1/param2"
|
|
908
|
+
* o semplicemente "label".
|
|
909
|
+
*/
|
|
910
|
+
static translateInlineParams(keyWithParams, splitChar) {
|
|
911
|
+
if (!keyWithParams)
|
|
912
|
+
return '-';
|
|
913
|
+
const [label, ...params] = keyWithParams.split(splitChar);
|
|
914
|
+
// Se non ci sono parametri, esegui una traduzione semplice
|
|
915
|
+
if (params.length === 0) {
|
|
916
|
+
return this.translate(label);
|
|
917
|
+
}
|
|
918
|
+
// Mappa i parametri in un oggetto di interpolazione: { inlineParam0: val, inlineParam1: val, ... }
|
|
919
|
+
const interpolationParameters = {};
|
|
920
|
+
for (const [index, val] of params.entries()) {
|
|
921
|
+
interpolationParameters[`param${index}`] = val;
|
|
922
|
+
}
|
|
923
|
+
return this.translate(label, 'lbl', interpolationParameters);
|
|
924
|
+
}
|
|
925
|
+
/* ------------------------------------------------------------------------------- */
|
|
926
|
+
/* ------------------------------- Metodi: numeri -------------------------------- */
|
|
927
|
+
/* ------------------------------------------------------------------------------- */
|
|
928
|
+
/**
|
|
929
|
+
* Converte un valore numerico in una stringa formattata secondo il locale impostato.
|
|
930
|
+
* Disabilita i separatori delle migliaia e forza un numero fisso di decimali.
|
|
931
|
+
*/
|
|
932
|
+
static toStringNumber(value, decimal) {
|
|
933
|
+
return new Intl.NumberFormat(this._locale, {
|
|
934
|
+
useGrouping: true,
|
|
935
|
+
minimumFractionDigits: decimal,
|
|
936
|
+
maximumFractionDigits: decimal,
|
|
937
|
+
numberingSystem: 'latn',
|
|
938
|
+
}).format(value);
|
|
939
|
+
}
|
|
940
|
+
/* ------------------------------------------------------------------------------- */
|
|
941
|
+
/* -------------------------------- Metodi: date --------------------------------- */
|
|
1062
942
|
/* ------------------------------------------------------------------------------- */
|
|
1063
943
|
/**
|
|
1064
|
-
*
|
|
1065
|
-
*
|
|
944
|
+
* Formatta una data o una stringa in base al locale corrente.
|
|
945
|
+
* Gestisce tre modalità predefinite (date, time, full) e accetta opzioni personalizzate Intl.
|
|
1066
946
|
*/
|
|
1067
|
-
static
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
947
|
+
static toDate(date, mode = 'full', force) {
|
|
948
|
+
const dt = new Date(date);
|
|
949
|
+
// Controllo: validità data
|
|
950
|
+
if (Number.isNaN(dt.getTime())) {
|
|
951
|
+
console.error(`[UniLocaleManager] Data non valida fornita a formatDateTime:`, date);
|
|
952
|
+
return '';
|
|
953
|
+
}
|
|
954
|
+
// Controllo: locale da forzare
|
|
955
|
+
const locale = force && this._locale.startsWith(force.oldLang) ? force.newLocale : this._locale;
|
|
956
|
+
switch (mode) {
|
|
957
|
+
case 'date': {
|
|
958
|
+
return dt.toLocaleDateString(locale);
|
|
959
|
+
}
|
|
960
|
+
case 'time': {
|
|
961
|
+
return dt.toLocaleTimeString(locale);
|
|
962
|
+
}
|
|
963
|
+
case 'full': {
|
|
964
|
+
return dt.toLocaleString(locale);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
1076
967
|
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
971
|
+
class UniToastManager {
|
|
972
|
+
/* ------------------------------------------------------------------------------- */
|
|
973
|
+
/* -------------------------------- Metodi: setup -------------------------------- */
|
|
974
|
+
/* ------------------------------------------------------------------------------- */
|
|
1077
975
|
/**
|
|
1078
|
-
*
|
|
1079
|
-
* Delega la logica di conversione alla funzione executeFile.
|
|
976
|
+
* Inizializza il manager con una serie di operazioni
|
|
1080
977
|
*/
|
|
1081
|
-
static
|
|
1082
|
-
|
|
1083
|
-
const initCustom = {
|
|
1084
|
-
...config.init,
|
|
1085
|
-
method: 'GET',
|
|
1086
|
-
};
|
|
1087
|
-
/* Toast di default */
|
|
1088
|
-
const configCustom = {
|
|
1089
|
-
...config,
|
|
1090
|
-
toast: config.toast === null ? undefined : (config.toast ?? CONFIG_TOAST_DEFAULT),
|
|
1091
|
-
};
|
|
1092
|
-
/* Chiama API */
|
|
1093
|
-
const url = getUrl(this.hostname, this.port, config);
|
|
1094
|
-
return http$(url, 'file', configCustom, () => executeBlob(url, initCustom));
|
|
978
|
+
static setup(operations) {
|
|
979
|
+
this.manager = operations;
|
|
1095
980
|
}
|
|
1096
981
|
/* ------------------------------------------------------------------------------- */
|
|
1097
|
-
/*
|
|
982
|
+
/* -------------------------------- Metodi: show --------------------------------- */
|
|
1098
983
|
/* ------------------------------------------------------------------------------- */
|
|
1099
984
|
/**
|
|
1100
|
-
*
|
|
1101
|
-
* Continua a emettere valori in base alla configurazione di intervallo definita in HttpConfigPolling.
|
|
985
|
+
* Mostra un toast di successo basato su una risposta HTTP e una label di traduzione.
|
|
1102
986
|
*/
|
|
1103
|
-
static
|
|
1104
|
-
/*
|
|
1105
|
-
const
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
};
|
|
1109
|
-
/* Chiama API */
|
|
1110
|
-
const url = getUrl(this.hostname, this.port, config);
|
|
1111
|
-
return httpPolling$(url, config, () => executeHttp(url, initCustom));
|
|
987
|
+
static show(config) {
|
|
988
|
+
/* Messaggio tradotto */
|
|
989
|
+
const msg = UniLocaleManager$1.translate(config.label, 'toast', config.params);
|
|
990
|
+
const msgFixed = `${config.prefix ?? ''}${msg}${config.suffix ?? ''}`;
|
|
991
|
+
this.manager[config.type ?? 'info'](msgFixed, config);
|
|
1112
992
|
}
|
|
1113
993
|
/**
|
|
1114
|
-
*
|
|
1115
|
-
* Invia il body specificato a ogni iterazione del ciclo.
|
|
994
|
+
* Mostra un toast di successo basato su una risposta HTTP e una label di traduzione.
|
|
1116
995
|
*/
|
|
1117
|
-
static
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
996
|
+
static showHttp(config, res) {
|
|
997
|
+
const { params, resParams, formatters } = config;
|
|
998
|
+
/* Parametri statici di base */
|
|
999
|
+
const allParams = { ...params };
|
|
1000
|
+
if (res !== undefined && res !== null) {
|
|
1001
|
+
/* Se è un array aggiunge il parametro 'count' con la lunghezza dell'array */
|
|
1002
|
+
if (Array.isArray(res)) {
|
|
1003
|
+
allParams['count'] = res.length;
|
|
1004
|
+
}
|
|
1005
|
+
else if (typeof res === 'object' && resParams?.length) {
|
|
1006
|
+
// Se la proprietà NON esiste nella risposta, allora salta
|
|
1007
|
+
for (const resParam of resParams ?? []) {
|
|
1008
|
+
if (!(resParam in res))
|
|
1009
|
+
continue;
|
|
1010
|
+
// Se esiste un formatter per questa chiave lo si usa, altrimenti valore grezzo
|
|
1011
|
+
const rawValue = res[resParam];
|
|
1012
|
+
allParams[resParam] = formatters?.[resParam] ? formatters[resParam](rawValue) : rawValue;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
/* Messaggio tradotto */
|
|
1017
|
+
this.show({ ...config, params: allParams });
|
|
1128
1018
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/**
|
|
1022
|
+
* Classe di utilità per la gestione e formattazione delle date.
|
|
1023
|
+
* Fornisce metodi per convertire in modo sicuro valori di tipo Date, stringa o null in formati standardizzati.
|
|
1024
|
+
*/
|
|
1025
|
+
class UniTypeDateManager {
|
|
1026
|
+
static toYYYYMMDD(date) {
|
|
1027
|
+
if (!date)
|
|
1028
|
+
return '';
|
|
1029
|
+
const d = new Date(date);
|
|
1030
|
+
if (Number.isNaN(d.getTime()))
|
|
1031
|
+
return '';
|
|
1032
|
+
const year = d.getFullYear();
|
|
1033
|
+
const month = String(d.getMonth() + 1).padStart(2, '0');
|
|
1034
|
+
const day = String(d.getDate()).padStart(2, '0');
|
|
1035
|
+
return `${year}-${month}-${day}`;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
/**
|
|
1040
|
+
* Utility per la gestione e formattazione di valori numerici.
|
|
1041
|
+
*/
|
|
1042
|
+
class UniTypeNumberManager {
|
|
1043
|
+
/** Applica il padding a un numero o una stringa */
|
|
1044
|
+
static toPad(value, count, character = '0') {
|
|
1045
|
+
// Se il valore è null/undefined, riempiamo l'intero campo con il carattere scelto
|
|
1046
|
+
if (value === null || value === undefined) {
|
|
1047
|
+
return ''.padStart(count, character);
|
|
1144
1048
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1049
|
+
// Convertiamo in stringa e applichiamo il padding
|
|
1050
|
+
return value.toString().padStart(count, character);
|
|
1051
|
+
}
|
|
1052
|
+
/** Formatta un numero in una versione leggibile (es: 1000 -> 1K, 1000000 -> 1M) */
|
|
1053
|
+
static toTruncateAndAdUdm(value, decimalDigits = 0, maxIntegerDigits = 3) {
|
|
1054
|
+
// Nessun limite impostato o numero inferiore alla soglia definita
|
|
1055
|
+
if (Math.abs(value) < Math.pow(10, maxIntegerDigits)) {
|
|
1056
|
+
return value.toFixed(decimalDigits);
|
|
1057
|
+
}
|
|
1058
|
+
// Definizione delle scale di riduzione per grandi numeri
|
|
1059
|
+
const scales = [
|
|
1060
|
+
{ threshold: 1e12, suffix: 'T' }, // Trilioni
|
|
1061
|
+
{ threshold: 1e9, suffix: 'B' }, // Miliardi
|
|
1062
|
+
{ threshold: 1e6, suffix: 'M' }, // Milioni
|
|
1063
|
+
{ threshold: 1e3, suffix: 'K' }, // Migliaia
|
|
1064
|
+
];
|
|
1065
|
+
// Itera dalla scala più grande alla più piccola per trovare la soglia corretta
|
|
1066
|
+
for (const { threshold, suffix } of scales) {
|
|
1067
|
+
if (Math.abs(value) >= threshold) {
|
|
1068
|
+
const reduced = value / threshold;
|
|
1069
|
+
// parseFloat(toFixed()) rimuove gli zeri decimali superflui (es: 1.50 -> 1.5)
|
|
1070
|
+
// aggiungendo poi il relativo suffisso (K, M, B, T)
|
|
1071
|
+
return Number.parseFloat(reduced.toFixed(decimalDigits)).toString() + suffix;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
// Fallback: se il numero è grande ma non rientra nelle scale (caso raro con la logica attuale)
|
|
1075
|
+
return value.toFixed(decimalDigits);
|
|
1148
1076
|
}
|
|
1149
1077
|
}
|
|
1150
1078
|
|
|
1151
|
-
|
|
1079
|
+
/**
|
|
1080
|
+
* Utility per la manipolazione di stringhe
|
|
1081
|
+
*/
|
|
1082
|
+
class UniTypeStringManager {
|
|
1083
|
+
/** Converte una stringa in formato lblPascalCase (es. "user_id" -> "lblUserId") */
|
|
1084
|
+
static toLabelize(key, prefix = 'lbl') {
|
|
1085
|
+
// Se vuoto restituisce vuoto
|
|
1086
|
+
if (!key)
|
|
1087
|
+
return '';
|
|
1088
|
+
// Pulizia chiave
|
|
1089
|
+
const fixedKey = key.trim();
|
|
1090
|
+
// Unisce il prefisso
|
|
1091
|
+
return prefix + this.toPascalCase(fixedKey);
|
|
1092
|
+
}
|
|
1093
|
+
/** Converte una stringa in PascalCase (es. "user_id" -> "UserId") */
|
|
1094
|
+
static toPascalCase(key) {
|
|
1095
|
+
// Se vuoto restituisce vuoto
|
|
1096
|
+
if (!key)
|
|
1097
|
+
return '';
|
|
1098
|
+
// Pulizia chiave
|
|
1099
|
+
const fixedKey = key.trim();
|
|
1100
|
+
// Normalizza e pulisce
|
|
1101
|
+
const parts = this.splitString(fixedKey);
|
|
1102
|
+
if (parts.length === 0)
|
|
1103
|
+
return '';
|
|
1104
|
+
// Converte in PascalCase
|
|
1105
|
+
const pascalCased = this.toPascalCaseParts(parts);
|
|
1106
|
+
return pascalCased;
|
|
1107
|
+
}
|
|
1108
|
+
/** Converte una stringa in camelCase (es. "user_id" -> "userId") */
|
|
1109
|
+
static toCamelCase(key) {
|
|
1110
|
+
// Se vuoto restituisce vuoto
|
|
1111
|
+
if (!key)
|
|
1112
|
+
return '';
|
|
1113
|
+
// Pulizia chiave
|
|
1114
|
+
const fixedKey = key.trim();
|
|
1115
|
+
// Normalizza e pulisce
|
|
1116
|
+
const parts = this.splitString(fixedKey);
|
|
1117
|
+
if (parts.length === 0)
|
|
1118
|
+
return '';
|
|
1119
|
+
// La prima parola resta minuscola, le successive PascalCase
|
|
1120
|
+
const first = parts[0].toLowerCase();
|
|
1121
|
+
const rest = parts.slice(1);
|
|
1122
|
+
return first + this.toPascalCaseParts(rest);
|
|
1123
|
+
}
|
|
1124
|
+
/** Capitalizza solo la prima lettera della stringa */
|
|
1125
|
+
static toCapitalize(key) {
|
|
1126
|
+
// Se vuoto restituisce vuoto
|
|
1127
|
+
if (!key)
|
|
1128
|
+
return '';
|
|
1129
|
+
// Pulizia chiave
|
|
1130
|
+
const fixedKey = key.trim();
|
|
1131
|
+
return fixedKey.charAt(0).toUpperCase() + fixedKey.slice(1);
|
|
1132
|
+
}
|
|
1133
|
+
/** Sostituisce sotto-stringhe all'interno della stringa */
|
|
1134
|
+
static toReplace(key, values) {
|
|
1135
|
+
// Se vuoto restituisce vuoto
|
|
1136
|
+
if (!key)
|
|
1137
|
+
return '';
|
|
1138
|
+
// Pulizia chiave
|
|
1139
|
+
let fixedKey = key.trim();
|
|
1140
|
+
for (const value of values) {
|
|
1141
|
+
fixedKey = fixedKey.replaceAll(value.searchValue, value.replaceValue);
|
|
1142
|
+
}
|
|
1143
|
+
return fixedKey;
|
|
1144
|
+
}
|
|
1145
|
+
/* ----------------- Utils ----------------- */
|
|
1146
|
+
static splitString(key) {
|
|
1147
|
+
return (key
|
|
1148
|
+
// Pulisce eventuali caratteri di separazione rimasti in testa (es. "_tab_name" -> "tab_name")
|
|
1149
|
+
.replace(/^[-_\s]+/, '')
|
|
1150
|
+
// Isola il CamelCase inserendo un underscore tra minuscole e maiuscole (es. "userId" -> "user_Id")
|
|
1151
|
+
.replaceAll(/([a-z])([A-Z])/g, '$1_$2')
|
|
1152
|
+
// Isola gli acronimi attaccati a parole Normali (es. "VARAna" -> "VAR_Ana" grazie alla minuscola "na")
|
|
1153
|
+
.replaceAll(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
|
|
1154
|
+
// Applica il taglio definitivo usando come riferimento i trattini, gli underscore e gli spazi
|
|
1155
|
+
.split(/[-_\s]+/)
|
|
1156
|
+
// Rimuove dall'array finale eventuali stringhe vuote generate da separatori consecutivi
|
|
1157
|
+
.filter(Boolean));
|
|
1158
|
+
}
|
|
1159
|
+
static toPascalCaseParts(parts) {
|
|
1160
|
+
return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join('');
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1152
1163
|
|
|
1153
1164
|
/*
|
|
1154
1165
|
* Public API Surface of uni-manager
|