@shival99/z-ui 1.0.11 → 1.0.13
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/shival99-z-ui-components-z-calendar.mjs +11 -2
- package/fesm2022/shival99-z-ui-components-z-calendar.mjs.map +1 -1
- package/fesm2022/shival99-z-ui-components-z-table.mjs +2 -2
- package/fesm2022/shival99-z-ui-components-z-table.mjs.map +1 -1
- package/fesm2022/shival99-z-ui-i18n.mjs +20 -6
- package/fesm2022/shival99-z-ui-i18n.mjs.map +1 -1
- package/fesm2022/shival99-z-ui-providers.mjs +0 -12
- package/fesm2022/shival99-z-ui-providers.mjs.map +1 -1
- package/fesm2022/shival99-z-ui-services.mjs +444 -163
- package/fesm2022/shival99-z-ui-services.mjs.map +1 -1
- package/fesm2022/shival99-z-ui-utils.mjs +44 -68
- package/fesm2022/shival99-z-ui-utils.mjs.map +1 -1
- package/fesm2022/z-ui.mjs +0 -1
- package/fesm2022/z-ui.mjs.map +1 -1
- package/package.json +1 -1
- package/types/shival99-z-ui-components-z-calendar.d.ts +5 -5
- package/types/shival99-z-ui-components-z-input.d.ts +3 -3
- package/types/shival99-z-ui-components-z-modal.d.ts +1 -1
- package/types/shival99-z-ui-i18n.d.ts +0 -6
- package/types/shival99-z-ui-providers.d.ts +0 -10
- package/types/shival99-z-ui-services.d.ts +163 -129
- package/types/shival99-z-ui-utils.d.ts +15 -50
- package/types/z-ui.d.ts +0 -1
|
@@ -1,20 +1,15 @@
|
|
|
1
|
+
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
|
|
1
2
|
import * as i0 from '@angular/core';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { inject, signal, Injectable, InjectionToken, PLATFORM_ID, effect } from '@angular/core';
|
|
4
|
+
import { Router } from '@angular/router';
|
|
5
|
+
import { firstValueFrom, from, switchMap, of, catchError, throwError, tap, map, defer, BehaviorSubject, Subject } from 'rxjs';
|
|
5
6
|
import { TranslateService } from '@ngx-translate/core';
|
|
7
|
+
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
|
|
6
8
|
|
|
7
|
-
/**
|
|
8
|
-
* Z-Cache Service
|
|
9
|
-
* A localStorage-based cache service with optional encryption
|
|
10
|
-
*/
|
|
11
9
|
const Z_CACHE_DEFAULT_PREFIX = 'z_';
|
|
12
10
|
class ZCacheService {
|
|
13
11
|
static _prefix = Z_CACHE_DEFAULT_PREFIX;
|
|
14
12
|
static _encrypt = true;
|
|
15
|
-
/**
|
|
16
|
-
* Configure the cache service
|
|
17
|
-
*/
|
|
18
13
|
static configure(config) {
|
|
19
14
|
if (config.prefix) {
|
|
20
15
|
ZCacheService._prefix = config.prefix;
|
|
@@ -161,18 +156,12 @@ class ZCacheService {
|
|
|
161
156
|
}
|
|
162
157
|
}
|
|
163
158
|
|
|
164
|
-
/**
|
|
165
|
-
* Z-IndexDB Service
|
|
166
|
-
* A robust IndexedDB service with multi-store support, encryption, and retry logic
|
|
167
|
-
*/
|
|
168
|
-
/** Default configuration */
|
|
169
159
|
const Z_INDEXDB_DEFAULT_CONFIG = {
|
|
170
160
|
dbName: 'ZDatabase',
|
|
171
161
|
version: 1,
|
|
172
162
|
mode: 'readwrite',
|
|
173
163
|
defaultStore: 'ZStore',
|
|
174
164
|
};
|
|
175
|
-
/** Batch size for bulk operations */
|
|
176
165
|
const Z_INDEXDB_BATCH_SIZE = 100;
|
|
177
166
|
class ZIndexDbService {
|
|
178
167
|
_db = null;
|
|
@@ -561,10 +550,444 @@ class ZIndexDbService {
|
|
|
561
550
|
}
|
|
562
551
|
}
|
|
563
552
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
553
|
+
const LANG_CACHE_KEY = 'Z_LANGUAGE';
|
|
554
|
+
const DEFAULT_LANG = 'vi';
|
|
555
|
+
class ZTranslateService {
|
|
556
|
+
_translate = inject(TranslateService);
|
|
557
|
+
currentLang = signal(DEFAULT_LANG, ...(ngDevMode ? [{ debugName: "currentLang" }] : []));
|
|
558
|
+
availableLangs = signal(['vi', 'en'], ...(ngDevMode ? [{ debugName: "availableLangs" }] : []));
|
|
559
|
+
constructor() {
|
|
560
|
+
this._initialize();
|
|
561
|
+
}
|
|
562
|
+
_initialize() {
|
|
563
|
+
const savedLang = ZCacheService.get(LANG_CACHE_KEY) ?? DEFAULT_LANG;
|
|
564
|
+
this._translate.setFallbackLang(DEFAULT_LANG);
|
|
565
|
+
this._translate.use(savedLang);
|
|
566
|
+
this.currentLang.set(savedLang);
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Change the current language
|
|
570
|
+
* @param lang - Language code
|
|
571
|
+
*/
|
|
572
|
+
use(lang) {
|
|
573
|
+
this._translate.use(lang);
|
|
574
|
+
ZCacheService.set(LANG_CACHE_KEY, lang);
|
|
575
|
+
this.currentLang.set(lang);
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Get the current language code
|
|
579
|
+
*/
|
|
580
|
+
getLang() {
|
|
581
|
+
return this._translate.getCurrentLang() ?? DEFAULT_LANG;
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Get translation synchronously (returns key if not found)
|
|
585
|
+
* @param key - Translation key
|
|
586
|
+
* @param params - Interpolation parameters
|
|
587
|
+
*/
|
|
588
|
+
instant(key, params) {
|
|
589
|
+
if (!key) {
|
|
590
|
+
return '';
|
|
591
|
+
}
|
|
592
|
+
return this._translate.instant(key, params);
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Get translation asynchronously
|
|
596
|
+
* @param key - Translation key
|
|
597
|
+
* @param params - Interpolation parameters
|
|
598
|
+
*/
|
|
599
|
+
async get(key, params) {
|
|
600
|
+
if (!key) {
|
|
601
|
+
return '';
|
|
602
|
+
}
|
|
603
|
+
return firstValueFrom(this._translate.get(key, params));
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Get translation as observable
|
|
607
|
+
* @param key - Translation key
|
|
608
|
+
* @param params - Interpolation parameters
|
|
609
|
+
*/
|
|
610
|
+
get$(key, params) {
|
|
611
|
+
return this._translate.get(key, params);
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Stream translations (re-emits on language change)
|
|
615
|
+
* @param key - Translation key
|
|
616
|
+
* @param params - Interpolation parameters
|
|
617
|
+
*/
|
|
618
|
+
stream$(key, params) {
|
|
619
|
+
return this._translate.stream(key, params);
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Get multiple translations at once
|
|
623
|
+
* @param keys - Array of translation keys
|
|
624
|
+
* @param params - Interpolation parameters (applied to all)
|
|
625
|
+
*/
|
|
626
|
+
async getMany(keys, params) {
|
|
627
|
+
const results = {};
|
|
628
|
+
await Promise.all(keys.map(async (key) => {
|
|
629
|
+
results[key] = await this.get(key, params);
|
|
630
|
+
}));
|
|
631
|
+
return results;
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Check if a translation key exists
|
|
635
|
+
* @param key - Translation key
|
|
636
|
+
*/
|
|
637
|
+
has(key) {
|
|
638
|
+
const translation = this._translate.instant(key);
|
|
639
|
+
return translation !== key;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Add translations dynamically
|
|
643
|
+
* @param lang - Language code
|
|
644
|
+
* @param translations - Translation object
|
|
645
|
+
* @param shouldMerge - Whether to merge with existing translations
|
|
646
|
+
*/
|
|
647
|
+
setTranslation(lang, translations, shouldMerge = true) {
|
|
648
|
+
this._translate.setTranslation(lang, translations, shouldMerge);
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Get the underlying TranslateService for advanced usage
|
|
652
|
+
*/
|
|
653
|
+
getNgxTranslate() {
|
|
654
|
+
return this._translate;
|
|
655
|
+
}
|
|
656
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ZTranslateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
657
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ZTranslateService, providedIn: 'root' });
|
|
658
|
+
}
|
|
659
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ZTranslateService, decorators: [{
|
|
660
|
+
type: Injectable,
|
|
661
|
+
args: [{
|
|
662
|
+
providedIn: 'root',
|
|
663
|
+
}]
|
|
664
|
+
}], ctorParameters: () => [] });
|
|
665
|
+
|
|
666
|
+
const Z_HTTP_DEFAULT_CONFIG = {
|
|
667
|
+
defaultCacheTime: 3 * 60 * 1000,
|
|
668
|
+
networkCheckCacheTime: 10000,
|
|
669
|
+
tokenKey: 'Z_TOKEN',
|
|
670
|
+
userInfoKey: 'Z_USER_INFO',
|
|
671
|
+
lastUrlKey: 'Z_LAST_URL',
|
|
672
|
+
loginRoute: '/login',
|
|
673
|
+
homeRoute: '/',
|
|
674
|
+
};
|
|
675
|
+
class ZHttpAbstractService {
|
|
676
|
+
http = inject(HttpClient);
|
|
677
|
+
router = inject(Router);
|
|
678
|
+
cacheService = ZCacheService;
|
|
679
|
+
translateService = inject(ZTranslateService);
|
|
680
|
+
indexDbService;
|
|
681
|
+
config = Z_HTTP_DEFAULT_CONFIG;
|
|
682
|
+
_networkCache = null;
|
|
683
|
+
constructor() {
|
|
684
|
+
this.indexDbService = new ZIndexDbService({
|
|
685
|
+
dbName: 'ZHttpCache',
|
|
686
|
+
defaultStore: 'HttpCache',
|
|
687
|
+
stores: [{ name: 'HttpCache', encrypt: false }],
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
get(endpoint, options) {
|
|
691
|
+
const httpCall = () => {
|
|
692
|
+
const { headers, params } = this._buildRequest(options);
|
|
693
|
+
const httpOptions = this._buildHttpOptions({ ...options, headers, params });
|
|
694
|
+
const url = `${this.resolveBaseUrl(options?.api)}${endpoint}`;
|
|
695
|
+
return this.http.get(url, httpOptions);
|
|
696
|
+
};
|
|
697
|
+
return this._handleCacheAndRequest('GET', endpoint, httpCall, options);
|
|
698
|
+
}
|
|
699
|
+
post(endpoint, body, options) {
|
|
700
|
+
const httpCall = () => {
|
|
701
|
+
const { headers, body: transformedBody } = this._buildRequest(options, body);
|
|
702
|
+
const httpOptions = this._buildHttpOptions({ ...options, headers });
|
|
703
|
+
const url = `${this.resolveBaseUrl(options?.api)}${endpoint}`;
|
|
704
|
+
return this.http.post(url, transformedBody, httpOptions);
|
|
705
|
+
};
|
|
706
|
+
return this._handleCacheAndRequest('POST', endpoint, httpCall, options, body);
|
|
707
|
+
}
|
|
708
|
+
put(endpoint, body, options) {
|
|
709
|
+
const httpCall = () => {
|
|
710
|
+
const { headers, body: transformedBody } = this._buildRequest(options, body);
|
|
711
|
+
const httpOptions = this._buildHttpOptions({ ...options, headers });
|
|
712
|
+
const url = `${this.resolveBaseUrl(options?.api)}${endpoint}`;
|
|
713
|
+
return this.http.put(url, transformedBody, httpOptions);
|
|
714
|
+
};
|
|
715
|
+
return this._handleCacheAndRequest('PUT', endpoint, httpCall, options, body);
|
|
716
|
+
}
|
|
717
|
+
patch(endpoint, body, options) {
|
|
718
|
+
const httpCall = () => {
|
|
719
|
+
const { headers, body: transformedBody } = this._buildRequest(options, body);
|
|
720
|
+
const httpOptions = this._buildHttpOptions({ ...options, headers });
|
|
721
|
+
const url = `${this.resolveBaseUrl(options?.api)}${endpoint}`;
|
|
722
|
+
return this.http.patch(url, transformedBody, httpOptions);
|
|
723
|
+
};
|
|
724
|
+
return this._handleCacheAndRequest('PATCH', endpoint, httpCall, options, body);
|
|
725
|
+
}
|
|
726
|
+
delete(endpoint, body, options) {
|
|
727
|
+
const httpCall = () => {
|
|
728
|
+
const { headers, body: transformedBody } = this._buildRequest(options, body);
|
|
729
|
+
const httpOptions = this._buildHttpOptions({
|
|
730
|
+
...options,
|
|
731
|
+
headers,
|
|
732
|
+
body: transformedBody,
|
|
733
|
+
});
|
|
734
|
+
if ('transferCache' in httpOptions) {
|
|
735
|
+
delete httpOptions['transferCache'];
|
|
736
|
+
}
|
|
737
|
+
const url = `${this.resolveBaseUrl(options?.api)}${endpoint}`;
|
|
738
|
+
return this.http.delete(url, httpOptions);
|
|
739
|
+
};
|
|
740
|
+
return this._handleCacheAndRequest('DELETE', endpoint, httpCall, options, body);
|
|
741
|
+
}
|
|
742
|
+
_buildRequest(options, body) {
|
|
743
|
+
let headers = new HttpHeaders().set('Accept', 'application/json');
|
|
744
|
+
const contentType = options?.contentType ?? 'json';
|
|
745
|
+
let finalBody = body;
|
|
746
|
+
let params;
|
|
747
|
+
if (contentType === 'json') {
|
|
748
|
+
headers = headers.set('Content-Type', 'application/json');
|
|
749
|
+
if (body !== undefined && body !== null) {
|
|
750
|
+
finalBody = JSON.stringify(body);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
if (contentType === 'urlencoded') {
|
|
754
|
+
headers = headers.set('Content-Type', 'application/x-www-form-urlencoded');
|
|
755
|
+
if (body && typeof body === 'object') {
|
|
756
|
+
const urlParams = new URLSearchParams();
|
|
757
|
+
Object.entries(body).forEach(([key, value]) => {
|
|
758
|
+
urlParams.set(key, String(value));
|
|
759
|
+
});
|
|
760
|
+
finalBody = urlParams.toString();
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
if (contentType === 'form-data') {
|
|
764
|
+
if (body instanceof FormData) {
|
|
765
|
+
finalBody = body;
|
|
766
|
+
}
|
|
767
|
+
if (!(body instanceof FormData) && body && typeof body === 'object') {
|
|
768
|
+
const formData = new FormData();
|
|
769
|
+
Object.entries(body).forEach(([key, value]) => {
|
|
770
|
+
formData.append(key, value);
|
|
771
|
+
});
|
|
772
|
+
finalBody = formData;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
if (options?.headers && !(options.headers instanceof HttpHeaders)) {
|
|
776
|
+
const headersObj = options.headers;
|
|
777
|
+
Object.keys(headersObj).forEach(key => {
|
|
778
|
+
const value = headersObj[key];
|
|
779
|
+
headers = headers.set(key, Array.isArray(value) ? value.join(',') : value);
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
const token = this.cacheService.get(this.config.tokenKey);
|
|
783
|
+
if (token && !options?.skipToken) {
|
|
784
|
+
headers = headers.set('Authorization', `Bearer ${token}`);
|
|
785
|
+
}
|
|
786
|
+
if (options?.params && !(options.params instanceof Object.getPrototypeOf(URLSearchParams).constructor)) {
|
|
787
|
+
const paramsObj = options.params;
|
|
788
|
+
params = {};
|
|
789
|
+
Object.keys(paramsObj).forEach(key => {
|
|
790
|
+
const value = paramsObj[key];
|
|
791
|
+
if (value !== undefined && value !== null) {
|
|
792
|
+
params[key] = String(value);
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
return { headers, body: finalBody, params };
|
|
797
|
+
}
|
|
798
|
+
_buildHttpOptions(options) {
|
|
799
|
+
const httpOptions = { ...options };
|
|
800
|
+
delete httpOptions['enableCache'];
|
|
801
|
+
delete httpOptions['timeCache'];
|
|
802
|
+
delete httpOptions['contentType'];
|
|
803
|
+
delete httpOptions['api'];
|
|
804
|
+
delete httpOptions['skipToken'];
|
|
805
|
+
delete httpOptions['skipErrorToast'];
|
|
806
|
+
return httpOptions;
|
|
807
|
+
}
|
|
808
|
+
_handleCacheAndRequest(method, endpoint, httpCall, options, body) {
|
|
809
|
+
if (!options?.enableCache) {
|
|
810
|
+
return this._executeHttpCall(httpCall, options, method, endpoint, body);
|
|
811
|
+
}
|
|
812
|
+
const cacheKey = this._generateCacheKey(method, endpoint, options, body);
|
|
813
|
+
return from(this._getCachedData(cacheKey)).pipe(switchMap(cachedData => {
|
|
814
|
+
if (cachedData !== null) {
|
|
815
|
+
return of(cachedData);
|
|
816
|
+
}
|
|
817
|
+
return this._executeHttpCall(httpCall, options, method, endpoint, body);
|
|
818
|
+
}), catchError(() => this._executeHttpCall(httpCall, options, method, endpoint, body)));
|
|
819
|
+
}
|
|
820
|
+
_executeHttpCall(httpCall, options, method, endpoint, body) {
|
|
821
|
+
return this._detectNetworkStatus().pipe(switchMap(isOnline => {
|
|
822
|
+
if (!isOnline) {
|
|
823
|
+
const error = {
|
|
824
|
+
message: this.translateService.instant('i18n_z_ui_http_network_offline'),
|
|
825
|
+
status: 0,
|
|
826
|
+
};
|
|
827
|
+
return throwError(() => error);
|
|
828
|
+
}
|
|
829
|
+
return httpCall().pipe(catchError(err => this._handleError(err, options, method, endpoint, body)), tap(data => {
|
|
830
|
+
if (options?.enableCache && method && endpoint) {
|
|
831
|
+
const cacheKey = this._generateCacheKey(method, endpoint, options, body);
|
|
832
|
+
const ttl = options.timeCache ?? this.config.defaultCacheTime;
|
|
833
|
+
this._cacheData(cacheKey, data, ttl).catch(console.warn);
|
|
834
|
+
}
|
|
835
|
+
}));
|
|
836
|
+
}));
|
|
837
|
+
}
|
|
838
|
+
_generateCacheKey(method, endpoint, options, body) {
|
|
839
|
+
const api = options?.api ?? 'default';
|
|
840
|
+
const params = options?.params ? JSON.stringify(options.params) : '';
|
|
841
|
+
const bodyStr = body ? JSON.stringify(body) : '';
|
|
842
|
+
return `${method}_${api}_${endpoint}_${params}_${bodyStr}`;
|
|
843
|
+
}
|
|
844
|
+
async _cacheData(key, data, ttl) {
|
|
845
|
+
const cacheEntry = { data, timestamp: Date.now(), ttl };
|
|
846
|
+
await this.indexDbService.set(key, cacheEntry, 'HttpCache');
|
|
847
|
+
}
|
|
848
|
+
async _getCachedData(key) {
|
|
849
|
+
try {
|
|
850
|
+
const cached = await this.indexDbService.get(key, undefined, 'HttpCache');
|
|
851
|
+
if (!cached) {
|
|
852
|
+
return null;
|
|
853
|
+
}
|
|
854
|
+
if (Date.now() - cached.timestamp > cached.ttl) {
|
|
855
|
+
await this.indexDbService.delete(key, 'HttpCache');
|
|
856
|
+
return null;
|
|
857
|
+
}
|
|
858
|
+
return cached.data;
|
|
859
|
+
}
|
|
860
|
+
catch {
|
|
861
|
+
return null;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
_detectNetworkStatus() {
|
|
865
|
+
const now = Date.now();
|
|
866
|
+
if (this._networkCache && now - this._networkCache.time < this.config.networkCheckCacheTime) {
|
|
867
|
+
return of(this._networkCache.isOnline);
|
|
868
|
+
}
|
|
869
|
+
if (!navigator.onLine) {
|
|
870
|
+
this._networkCache = { time: now, isOnline: false };
|
|
871
|
+
return of(false);
|
|
872
|
+
}
|
|
873
|
+
return this._checkNetworkWithRetry().pipe(map(isOnline => {
|
|
874
|
+
this._networkCache = { time: Date.now(), isOnline };
|
|
875
|
+
return isOnline;
|
|
876
|
+
}), catchError(() => {
|
|
877
|
+
this._networkCache = { time: Date.now(), isOnline: false };
|
|
878
|
+
return of(false);
|
|
879
|
+
}));
|
|
880
|
+
}
|
|
881
|
+
_checkNetworkWithRetry(retries = 2) {
|
|
882
|
+
return defer(() => this._performNetworkCheck()).pipe(catchError(() => {
|
|
883
|
+
if (retries > 0) {
|
|
884
|
+
return this._checkNetworkWithRetry(retries - 1);
|
|
885
|
+
}
|
|
886
|
+
return of(false);
|
|
887
|
+
}));
|
|
888
|
+
}
|
|
889
|
+
_performNetworkCheck() {
|
|
890
|
+
const controller = new AbortController();
|
|
891
|
+
const timeoutId = setTimeout(() => controller.abort(), 3000);
|
|
892
|
+
return from(fetch('data:,', { method: 'HEAD', signal: controller.signal })).pipe(tap(() => clearTimeout(timeoutId)), map(() => true), catchError(() => {
|
|
893
|
+
clearTimeout(timeoutId);
|
|
894
|
+
throw new Error('Network check failed');
|
|
895
|
+
}));
|
|
896
|
+
}
|
|
897
|
+
_handleError(error, options, method, endpoint, body) {
|
|
898
|
+
const statusCode = error instanceof HttpErrorResponse ? error.status : 500;
|
|
899
|
+
const fullEndpoint = `${this.resolveBaseUrl(options?.api)}${endpoint}`;
|
|
900
|
+
const httpError = this._buildErrorObject(error, statusCode, fullEndpoint, method, body, options);
|
|
901
|
+
const localizedError = this._localizeError(httpError, statusCode);
|
|
902
|
+
console.error('[ZHttp] Request failed:', {
|
|
903
|
+
method,
|
|
904
|
+
endpoint: fullEndpoint,
|
|
905
|
+
status: statusCode,
|
|
906
|
+
error: localizedError,
|
|
907
|
+
});
|
|
908
|
+
this._handleStatusCode(statusCode);
|
|
909
|
+
return throwError(() => localizedError);
|
|
910
|
+
}
|
|
911
|
+
_buildErrorObject(error, statusCode, endpoint, method, body, options) {
|
|
912
|
+
const baseError = { message: '', status: statusCode, endpoint, method, body, options };
|
|
913
|
+
if (!(error instanceof HttpErrorResponse)) {
|
|
914
|
+
return {
|
|
915
|
+
...baseError,
|
|
916
|
+
error: error.message,
|
|
917
|
+
message: error.message || this.translateService.instant('i18n_z_ui_http_error_default'),
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
const errorBody = error.error;
|
|
921
|
+
if (typeof errorBody?.message === 'string') {
|
|
922
|
+
const message = errorBody.message === 'Failed to fetch'
|
|
923
|
+
? this.translateService.instant('i18n_z_ui_http_error_failed_to_fetch', { url: endpoint })
|
|
924
|
+
: errorBody.message;
|
|
925
|
+
return { ...baseError, error: errorBody, message };
|
|
926
|
+
}
|
|
927
|
+
if (Array.isArray(errorBody?.errors) && errorBody.errors.length > 0) {
|
|
928
|
+
const message = errorBody.errors
|
|
929
|
+
.map((err) => (typeof err === 'string' ? err : JSON.stringify(err)))
|
|
930
|
+
.join(', ');
|
|
931
|
+
return { ...baseError, error: errorBody, message };
|
|
932
|
+
}
|
|
933
|
+
if (typeof errorBody === 'object' &&
|
|
934
|
+
errorBody !== null &&
|
|
935
|
+
!Array.isArray(errorBody) &&
|
|
936
|
+
Object.keys(errorBody).length > 0) {
|
|
937
|
+
return { ...baseError, error: errorBody, message: JSON.stringify(errorBody) };
|
|
938
|
+
}
|
|
939
|
+
if (typeof errorBody === 'string') {
|
|
940
|
+
return { ...baseError, error: errorBody, message: errorBody };
|
|
941
|
+
}
|
|
942
|
+
return {
|
|
943
|
+
...baseError,
|
|
944
|
+
error: error.message,
|
|
945
|
+
message: error.message || this.translateService.instant('i18n_z_ui_http_error_default'),
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
_localizeError(error, statusCode) {
|
|
949
|
+
const localizedError = { ...error };
|
|
950
|
+
const errorMessages = {
|
|
951
|
+
400: error.message || this.translateService.instant('i18n_z_ui_http_error_400'),
|
|
952
|
+
401: this._get401Message(),
|
|
953
|
+
403: this.translateService.instant('i18n_z_ui_http_error_403'),
|
|
954
|
+
404: this.translateService.instant('i18n_z_ui_http_error_404'),
|
|
955
|
+
500: this.translateService.instant('i18n_z_ui_http_error_500'),
|
|
956
|
+
};
|
|
957
|
+
if (errorMessages[statusCode]) {
|
|
958
|
+
localizedError.message = errorMessages[statusCode];
|
|
959
|
+
}
|
|
960
|
+
return localizedError;
|
|
961
|
+
}
|
|
962
|
+
_get401Message() {
|
|
963
|
+
const userInfo = this.cacheService.get(this.config.userInfoKey);
|
|
964
|
+
const isExpired = userInfo?.timeExpired ? Date.now() >= userInfo.timeExpired : false;
|
|
965
|
+
if (isExpired) {
|
|
966
|
+
return this.translateService.instant('i18n_z_ui_http_error_401_expired');
|
|
967
|
+
}
|
|
968
|
+
return this.translateService.instant('i18n_z_ui_http_error_401_permission');
|
|
969
|
+
}
|
|
970
|
+
_handleStatusCode(statusCode) {
|
|
971
|
+
if (statusCode !== 401) {
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
const userInfo = this.cacheService.get(this.config.userInfoKey);
|
|
975
|
+
const isExpired = userInfo?.timeExpired ? Date.now() >= userInfo.timeExpired : false;
|
|
976
|
+
if (isExpired) {
|
|
977
|
+
this.indexDbService.clear('HttpCache').catch(console.error);
|
|
978
|
+
this.cacheService.deleteMultiple([this.config.tokenKey, this.config.userInfoKey]);
|
|
979
|
+
this.router.navigate([this.config.loginRoute]).catch(console.error);
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
this.router.navigate([this.config.homeRoute]).catch(console.error);
|
|
983
|
+
}
|
|
984
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ZHttpAbstractService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
985
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ZHttpAbstractService });
|
|
986
|
+
}
|
|
987
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ZHttpAbstractService, decorators: [{
|
|
988
|
+
type: Injectable
|
|
989
|
+
}], ctorParameters: () => [] });
|
|
990
|
+
|
|
568
991
|
class ZSubjectService {
|
|
569
992
|
_subjects = new Map();
|
|
570
993
|
_behaviorSubjects = new Map();
|
|
@@ -681,9 +1104,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
681
1104
|
}]
|
|
682
1105
|
}] });
|
|
683
1106
|
|
|
684
|
-
/**
|
|
685
|
-
* Z-Theme Types
|
|
686
|
-
*/
|
|
687
1107
|
const Z_THEME_CONFIG = new InjectionToken('Z_THEME_CONFIG');
|
|
688
1108
|
const Z_DEFAULT_THEME = 'neutral';
|
|
689
1109
|
const Z_THEME_CACHE_KEY = 'z-theme';
|
|
@@ -813,145 +1233,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
813
1233
|
}]
|
|
814
1234
|
}], ctorParameters: () => [] });
|
|
815
1235
|
|
|
816
|
-
/**
|
|
817
|
-
* Z-Translate Service
|
|
818
|
-
* A translation service with async support and caching
|
|
819
|
-
*/
|
|
820
|
-
const LANG_CACHE_KEY = 'Z_LANGUAGE';
|
|
821
|
-
const DEFAULT_LANG = 'vi';
|
|
822
|
-
class ZTranslateService {
|
|
823
|
-
_translate = inject(TranslateService);
|
|
824
|
-
/** Current language signal */
|
|
825
|
-
currentLang = signal(DEFAULT_LANG, ...(ngDevMode ? [{ debugName: "currentLang" }] : []));
|
|
826
|
-
/** Available languages */
|
|
827
|
-
availableLangs = signal(['vi', 'en'], ...(ngDevMode ? [{ debugName: "availableLangs" }] : []));
|
|
828
|
-
constructor() {
|
|
829
|
-
this._initialize();
|
|
830
|
-
}
|
|
831
|
-
_initialize() {
|
|
832
|
-
const savedLang = ZCacheService.get(LANG_CACHE_KEY) ?? DEFAULT_LANG;
|
|
833
|
-
this._translate.setFallbackLang(DEFAULT_LANG);
|
|
834
|
-
this._translate.use(savedLang);
|
|
835
|
-
this.currentLang.set(savedLang);
|
|
836
|
-
}
|
|
837
|
-
/**
|
|
838
|
-
* Change the current language
|
|
839
|
-
* @param lang - Language code
|
|
840
|
-
*/
|
|
841
|
-
use(lang) {
|
|
842
|
-
this._translate.use(lang);
|
|
843
|
-
ZCacheService.set(LANG_CACHE_KEY, lang);
|
|
844
|
-
this.currentLang.set(lang);
|
|
845
|
-
}
|
|
846
|
-
/**
|
|
847
|
-
* Get the current language code
|
|
848
|
-
*/
|
|
849
|
-
getLang() {
|
|
850
|
-
return this._translate.getCurrentLang() ?? DEFAULT_LANG;
|
|
851
|
-
}
|
|
852
|
-
/**
|
|
853
|
-
* Get translation synchronously (returns key if not found)
|
|
854
|
-
* @param key - Translation key
|
|
855
|
-
* @param params - Interpolation parameters
|
|
856
|
-
*/
|
|
857
|
-
instant(key, params) {
|
|
858
|
-
if (!key) {
|
|
859
|
-
return '';
|
|
860
|
-
}
|
|
861
|
-
return this._translate.instant(key, params);
|
|
862
|
-
}
|
|
863
|
-
/**
|
|
864
|
-
* Get translation asynchronously
|
|
865
|
-
* @param key - Translation key
|
|
866
|
-
* @param params - Interpolation parameters
|
|
867
|
-
*/
|
|
868
|
-
async get(key, params) {
|
|
869
|
-
if (!key) {
|
|
870
|
-
return '';
|
|
871
|
-
}
|
|
872
|
-
return firstValueFrom(this._translate.get(key, params));
|
|
873
|
-
}
|
|
874
|
-
/**
|
|
875
|
-
* Get translation as observable
|
|
876
|
-
* @param key - Translation key
|
|
877
|
-
* @param params - Interpolation parameters
|
|
878
|
-
*/
|
|
879
|
-
get$(key, params) {
|
|
880
|
-
return this._translate.get(key, params);
|
|
881
|
-
}
|
|
882
|
-
/**
|
|
883
|
-
* Stream translations (re-emits on language change)
|
|
884
|
-
* @param key - Translation key
|
|
885
|
-
* @param params - Interpolation parameters
|
|
886
|
-
*/
|
|
887
|
-
stream$(key, params) {
|
|
888
|
-
return this._translate.stream(key, params);
|
|
889
|
-
}
|
|
890
|
-
/**
|
|
891
|
-
* Get multiple translations at once
|
|
892
|
-
* @param keys - Array of translation keys
|
|
893
|
-
* @param params - Interpolation parameters (applied to all)
|
|
894
|
-
*/
|
|
895
|
-
async getMany(keys, params) {
|
|
896
|
-
const results = {};
|
|
897
|
-
await Promise.all(keys.map(async (key) => {
|
|
898
|
-
results[key] = await this.get(key, params);
|
|
899
|
-
}));
|
|
900
|
-
return results;
|
|
901
|
-
}
|
|
902
|
-
/**
|
|
903
|
-
* Check if a translation key exists
|
|
904
|
-
* @param key - Translation key
|
|
905
|
-
*/
|
|
906
|
-
has(key) {
|
|
907
|
-
const translation = this._translate.instant(key);
|
|
908
|
-
return translation !== key;
|
|
909
|
-
}
|
|
910
|
-
/**
|
|
911
|
-
* Add translations dynamically
|
|
912
|
-
* @param lang - Language code
|
|
913
|
-
* @param translations - Translation object
|
|
914
|
-
* @param shouldMerge - Whether to merge with existing translations
|
|
915
|
-
*/
|
|
916
|
-
setTranslation(lang, translations, shouldMerge = true) {
|
|
917
|
-
this._translate.setTranslation(lang, translations, shouldMerge);
|
|
918
|
-
}
|
|
919
|
-
/**
|
|
920
|
-
* Get the underlying TranslateService for advanced usage
|
|
921
|
-
*/
|
|
922
|
-
getNgxTranslate() {
|
|
923
|
-
return this._translate;
|
|
924
|
-
}
|
|
925
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ZTranslateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
926
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ZTranslateService, providedIn: 'root' });
|
|
927
|
-
}
|
|
928
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ZTranslateService, decorators: [{
|
|
929
|
-
type: Injectable,
|
|
930
|
-
args: [{
|
|
931
|
-
providedIn: 'root',
|
|
932
|
-
}]
|
|
933
|
-
}], ctorParameters: () => [] });
|
|
934
|
-
|
|
935
|
-
/**
|
|
936
|
-
* Z-Cache Types
|
|
937
|
-
*/
|
|
938
|
-
|
|
939
|
-
/**
|
|
940
|
-
* Z-Http Types
|
|
941
|
-
*/
|
|
942
|
-
|
|
943
|
-
/**
|
|
944
|
-
* Z-IndexDB Types
|
|
945
|
-
*/
|
|
946
|
-
|
|
947
|
-
/**
|
|
948
|
-
* Z-Translate Types
|
|
949
|
-
*/
|
|
950
|
-
|
|
951
|
-
/**
|
|
952
|
-
* Services types index
|
|
953
|
-
*/
|
|
954
|
-
|
|
955
1236
|
/**
|
|
956
1237
|
* Services index
|
|
957
1238
|
*/
|
|
@@ -960,5 +1241,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
960
1241
|
* Generated bundle index. Do not edit.
|
|
961
1242
|
*/
|
|
962
1243
|
|
|
963
|
-
export { ZCacheService, ZIndexDbService, ZSubjectService, ZThemeService, ZTranslateService, Z_DARK_MODE_CACHE_KEY, Z_DEFAULT_THEME, Z_THEME_CACHE_KEY, Z_THEME_CONFIG, Z_THEME_CSS_MAP };
|
|
1244
|
+
export { ZCacheService, ZHttpAbstractService, ZIndexDbService, ZSubjectService, ZThemeService, ZTranslateService, Z_DARK_MODE_CACHE_KEY, Z_DEFAULT_THEME, Z_THEME_CACHE_KEY, Z_THEME_CONFIG, Z_THEME_CSS_MAP };
|
|
964
1245
|
//# sourceMappingURL=shival99-z-ui-services.mjs.map
|