@mosa-ng/core 17.0.0

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.
Files changed (60) hide show
  1. package/.eslintrc.json +31 -0
  2. package/README.md +24 -0
  3. package/ng-package.json +10 -0
  4. package/package.json +12 -0
  5. package/src/assets/i18n/mosa-de-DE.json +60 -0
  6. package/src/assets/i18n/mosa-en-US.json +60 -0
  7. package/src/lib/constants/local-storage.constant.ts +4 -0
  8. package/src/lib/enums/browser.enum.ts +9 -0
  9. package/src/lib/enums/content-position.enum.ts +11 -0
  10. package/src/lib/enums/i18n-supported.enum.ts +10 -0
  11. package/src/lib/enums/theme.enum.ts +4 -0
  12. package/src/lib/models/api-options.model.ts +21 -0
  13. package/src/lib/models/dictionary-item.model.ts +8 -0
  14. package/src/lib/models/key-map.model.ts +3 -0
  15. package/src/lib/models/logger/log-event.model.ts +8 -0
  16. package/src/lib/models/logger/log-message-data.model.ts +5 -0
  17. package/src/lib/models/logger/log-type.model.ts +1 -0
  18. package/src/lib/models/logger/log.model.ts +10 -0
  19. package/src/lib/models/logger/logger-base-config.model.ts +8 -0
  20. package/src/lib/models/logger/logger-config.model.ts +9 -0
  21. package/src/lib/models/logger/logger-default-config.model.ts +5 -0
  22. package/src/lib/models/mosa-duration.model.ts +1 -0
  23. package/src/lib/models/sieve/sieve-filter.model.ts +10 -0
  24. package/src/lib/models/sieve/sieve-operator.model.ts +69 -0
  25. package/src/lib/models/sieve/sieve-options.model.ts +13 -0
  26. package/src/lib/models/sieve/sieve-response.model.ts +4 -0
  27. package/src/lib/models/sieve/sieve-sort.model.ts +7 -0
  28. package/src/lib/models/token-settings.model.ts +5 -0
  29. package/src/lib/models/transform-matrix.model.ts +5 -0
  30. package/src/lib/mosa-core.module.ts +117 -0
  31. package/src/lib/pipes/dictionary-item-pipe/dictionary-item-pipe.module.ts +15 -0
  32. package/src/lib/pipes/dictionary-item-pipe/dictionary-item.pipe.ts +14 -0
  33. package/src/lib/pipes/find-in-array/find-in-array-pipe.module.ts +17 -0
  34. package/src/lib/pipes/find-in-array/find-in-array.pipe.spec.ts +8 -0
  35. package/src/lib/pipes/find-in-array/find-in-array.pipe.ts +34 -0
  36. package/src/lib/pipes/join/join-pipe.module.ts +17 -0
  37. package/src/lib/pipes/join/join.pipe.spec.ts +8 -0
  38. package/src/lib/pipes/join/join.pipe.ts +20 -0
  39. package/src/lib/pipes/mosa-date-pipe/mosa-date-pipe.module.ts +14 -0
  40. package/src/lib/pipes/mosa-date-pipe/mosa-date.pipe.ts +102 -0
  41. package/src/lib/pipes/mosa-duration-pipe/mosa-duration-pipe.module.ts +13 -0
  42. package/src/lib/pipes/mosa-duration-pipe/mosa-duration.pipe.ts +106 -0
  43. package/src/lib/services/api.service.ts +270 -0
  44. package/src/lib/services/core-logger.service.ts +219 -0
  45. package/src/lib/services/guards/can-deactivate.guard.ts +18 -0
  46. package/src/lib/services/mosa-socket.service.ts +46 -0
  47. package/src/lib/services/translation/assets-i18n-loader.service.ts +67 -0
  48. package/src/lib/services/translation/custom-translate-loader.service.ts +27 -0
  49. package/src/lib/services/translation/translate-collector.service.ts +60 -0
  50. package/src/lib/utils/commons.util.ts +100 -0
  51. package/src/lib/utils/dictionary.util.ts +140 -0
  52. package/src/lib/utils/item.util.ts +4 -0
  53. package/src/lib/utils/promise.util.ts +3 -0
  54. package/src/lib/utils/prototypes.util.ts +167 -0
  55. package/src/lib/utils/sieve.util.ts +169 -0
  56. package/src/lib/utils/size.util.ts +4 -0
  57. package/src/public-api.ts +1 -0
  58. package/tsconfig.lib.json +16 -0
  59. package/tsconfig.lib.prod.json +10 -0
  60. package/tsconfig.spec.json +14 -0
@@ -0,0 +1,219 @@
1
+ import { HttpErrorResponse } from '@angular/common/http';
2
+ import { Injectable } from '@angular/core';
3
+ import { BehaviorSubject, Observable } from 'rxjs';
4
+ import { ILogEvent } from '../models/logger/log-event.model';
5
+ import { LogType } from '../models/logger/log-type.model';
6
+ import { ILoggerConfig } from '../models/logger/logger-config.model';
7
+ import { ILoggerDefaultConfig } from '../models/logger/logger-default-config.model';
8
+ import { isNullOrUndefined } from '../utils/commons.util';
9
+
10
+ @Injectable({
11
+ providedIn: 'root',
12
+ })
13
+ export class CoreLoggerService {
14
+
15
+ public defaultConfig: ILoggerDefaultConfig;
16
+
17
+ private readonly uiLogs$: BehaviorSubject<ILogEvent>;
18
+
19
+ /**
20
+ * Default constructor
21
+ */
22
+ constructor() {
23
+ this.uiLogs$ = new BehaviorSubject(null);
24
+ }
25
+
26
+ public subUiLogs(): Observable<ILogEvent> {
27
+ return this.uiLogs$.asObservable();
28
+ }
29
+
30
+ /**
31
+ * Gets the error message from any type of object
32
+ */
33
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
+ public getErrorMsg(err: any): string {
35
+ let msg: string = 'Unknown Error';
36
+
37
+ if (!err) {
38
+ return msg;
39
+ }
40
+
41
+ if (err instanceof HttpErrorResponse) {
42
+ if (typeof err.error !== 'string') {
43
+ return `mosa.commons.errors.status[${ err.status }].message`;
44
+ }
45
+
46
+ return err.error;
47
+ }
48
+
49
+ if (typeof err === 'string') {
50
+ msg = err;
51
+ } else if (err.msg) {
52
+ msg = err.msg;
53
+ } else if (err.error?.msg) {
54
+ msg = err.error.msg;
55
+ } else if (err.error?.message) {
56
+ msg = err.error.message;
57
+ } else if (err.error?.errors && err.error?.errors[ 0 ]?.message) {
58
+ msg = err.error.errors[ 0 ].message;
59
+ } else if (err.message) {
60
+ msg = err.message;
61
+ } else if (err.Message) {
62
+ msg = err.Message;
63
+ }
64
+
65
+ if (typeof err.error === 'string') {
66
+ msg = err.error;
67
+ }
68
+
69
+ return msg;
70
+ }
71
+
72
+ /**
73
+ * Get the title to be shown in logger toast
74
+ * @param err
75
+ */
76
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
77
+ public getErrorTitle(err: any): string {
78
+ let title: string = 'mosa.commons.logs.error.label';
79
+ if (err?.label) {
80
+ title = err.label;
81
+ } else if (err?.title) {
82
+ title = err.title;
83
+ } else if (err?.error?.label) {
84
+ title = err.error.label;
85
+ } else if (err?.error?.title) {
86
+ title = err.error.title;
87
+ }
88
+ return title;
89
+ }
90
+
91
+ /**
92
+ * Logs an error to the user
93
+ * @param data
94
+ * @param showUser
95
+ * @param config
96
+ */
97
+ public logError(config: ILoggerConfig): void {
98
+ config = this.serializeConfig(config);
99
+ config.msg = this.getErrorMsg(config.msg);
100
+
101
+ if (!config.title) {
102
+ config.title = this.getErrorTitle(config.msg);
103
+ }
104
+
105
+ config.className = 'snackbar-error';
106
+
107
+ this.show(config, 'error');
108
+ }
109
+
110
+ /**
111
+ * Logs a success message to the user
112
+ * @param data
113
+ * @param config
114
+ */
115
+ public logSuccess(config: ILoggerConfig): void {
116
+ config = this.serializeConfig(config);
117
+ config.className = 'snackbar-success';
118
+
119
+ this.show(config, 'success');
120
+ }
121
+
122
+ /**
123
+ * Logs a warning to the user
124
+ * @param data
125
+ * @param showUser
126
+ * @param config
127
+ */
128
+ public logWarning(config: ILoggerConfig): void {
129
+ config = this.serializeConfig(config);
130
+ config.className = 'snackbar-warning';
131
+
132
+ this.show(config, 'warning');
133
+ }
134
+
135
+ /**
136
+ * Logs an info to the user
137
+ * @param data
138
+ * @param config
139
+ */
140
+ public logInfo(config: ILoggerConfig): void {
141
+ config = this.serializeConfig(config);
142
+ config.className = 'snackbar-info';
143
+
144
+ this.show(config, 'info');
145
+ }
146
+
147
+ /**
148
+ * Logs any kind of message to the user
149
+ * @param data
150
+ * @param config
151
+ */
152
+ public log(config: ILoggerConfig): void {
153
+ config = this.serializeConfig(config);
154
+ config.className = 'snackbar-default';
155
+
156
+ this.show(config, 'default');
157
+ }
158
+
159
+ /**
160
+ * Show the log
161
+ * @requires @mosa-ng/material
162
+ * @param data
163
+ * @param type
164
+ * @param config
165
+ */
166
+ public show(config: ILoggerConfig, type: LogType): void {
167
+ this.uiLogs$.next({ title: config.title, msg: config.msg, type, config });
168
+ }
169
+
170
+ /**
171
+ * Serializes empty values, so no error is being returned
172
+ * @param config
173
+ * @private
174
+ */
175
+ private serializeConfig(config: ILoggerConfig): ILoggerConfig {
176
+ // Check if user has default config
177
+ if (!this.defaultConfig) {
178
+ this.defaultConfig = {
179
+ debug: true,
180
+ };
181
+ }
182
+
183
+ if (!config) {
184
+ // Get user default config
185
+ config = {
186
+ showDismiss: this.defaultConfig.showDismiss,
187
+ closeOnClick: isNullOrUndefined(this.defaultConfig.closeOnClick) ? true : this.defaultConfig.closeOnClick,
188
+ className: this.defaultConfig.className,
189
+ duration: this.defaultConfig.duration || 4,
190
+ };
191
+ }
192
+
193
+ // Serialize configuration
194
+ config.duration = CoreLoggerService.getValue<number>(config.duration, this.defaultConfig.duration, 4);
195
+ config.className = CoreLoggerService.getValue<string>(config.className, this.defaultConfig.className, null);
196
+ config.showDismiss = CoreLoggerService.getValue<boolean>(config.showDismiss, this.defaultConfig.showDismiss, false);
197
+ config.closeOnClick = CoreLoggerService.getValue<boolean>(config.closeOnClick, this.defaultConfig.closeOnClick, true);
198
+
199
+ return config;
200
+ }
201
+
202
+ /**
203
+ * Gets a value which is not undefined or null
204
+ * @param val
205
+ * @param val1
206
+ * @param def
207
+ * @private
208
+ */
209
+ private static getValue<T>(val: T, val1: T, def: T): T {
210
+ if (isNullOrUndefined(val)) {
211
+ if (isNullOrUndefined(val1)) {
212
+ return def;
213
+ }
214
+ return val1;
215
+ }
216
+ return val;
217
+ }
218
+
219
+ }
@@ -0,0 +1,18 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { CanDeactivate } from '@angular/router';
3
+ import { Observable } from 'rxjs';
4
+
5
+ export interface CanComponentDeactivate {
6
+ canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
7
+ }
8
+
9
+ @Injectable({
10
+ providedIn: 'root',
11
+ })
12
+ export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> {
13
+
14
+ public canDeactivate(component: CanComponentDeactivate): boolean {
15
+ return component.canDeactivate ? component.canDeactivate() as boolean : true;
16
+ }
17
+
18
+ }
@@ -0,0 +1,46 @@
1
+ import { Injectable } from '@angular/core';
2
+
3
+ import { Observable, Observer, Subject } from 'rxjs';
4
+
5
+ @Injectable({
6
+ providedIn: 'root',
7
+ })
8
+ export class MosaSocketService {
9
+
10
+ private subject: Subject<MessageEvent>;
11
+
12
+ constructor() {
13
+ }
14
+
15
+ public connect(url: string): Subject<MessageEvent> {
16
+ if (!this.subject) {
17
+ this.subject = this.create(url);
18
+ }
19
+ return this.subject;
20
+ }
21
+
22
+ public send(data: MessageEvent): void {
23
+ this.subject.next(data);
24
+ }
25
+
26
+ private create(url: string): Subject<MessageEvent> {
27
+ const ws = new WebSocket(url);
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ const observable = new Observable((obs: Observer<MessageEvent>): any => {
30
+ ws.onmessage = obs.next.bind(obs);
31
+ ws.onerror = obs.error.bind(obs);
32
+ ws.onclose = obs.complete.bind(obs);
33
+ return ws.close.bind(ws);
34
+ });
35
+ const observer = {
36
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
37
+ next: (data: any): void => {
38
+ if (ws.readyState === WebSocket.OPEN) {
39
+ ws.send(JSON.stringify(data));
40
+ }
41
+ },
42
+ };
43
+ return Subject.create(observer, observable);
44
+ }
45
+
46
+ }
@@ -0,0 +1,67 @@
1
+ import { HttpClient } from '@angular/common/http';
2
+ import { Injectable } from '@angular/core';
3
+ import { firstValueFrom, Observable, of } from 'rxjs';
4
+ import { catchError, share, tap } from 'rxjs/operators';
5
+ import { IKeyMap } from '../../models/key-map.model';
6
+ import { getRandomString } from '../../utils/commons.util';
7
+ import { TranslateCollectorService } from './translate-collector.service';
8
+
9
+ @Injectable({
10
+ providedIn: 'root',
11
+ })
12
+ export class AssetsI18nLoaderService {
13
+
14
+ protected priority: number = 0;
15
+ protected supportedLanguages: string[];
16
+
17
+ private subLanguages: Observable<{ languages: string[] }>;
18
+
19
+ constructor(
20
+ protected myHttpClient: HttpClient,
21
+ protected myTranslateCollectorService: TranslateCollectorService,
22
+ ) {
23
+ }
24
+
25
+ public init(path?: string, libName?: string, priority?: number): Promise<void> {
26
+ return new Promise(async (resolve: () => void): Promise<void> => {
27
+ if (this.supportedLanguages) {
28
+ for (let i: number = 0; i < this.supportedLanguages.length; i++) {
29
+ await firstValueFrom(this.loadTranslations(this.supportedLanguages[ i ], path, libName, priority));
30
+ }
31
+ resolve();
32
+ return;
33
+ }
34
+
35
+ if (!this.subLanguages) {
36
+ this.subLanguages = this.myHttpClient.get<{ languages: string[] }>(`assets/i18n/supported-languages.json?v=${getRandomString()}`)
37
+ .pipe(share());
38
+ }
39
+ this.subLanguages.subscribe(async (res: { languages: string[] }) => {
40
+ this.subLanguages = null;
41
+ this.supportedLanguages = res.languages;
42
+
43
+ for (let i: number = 0; i < this.supportedLanguages.length; i++) {
44
+ await firstValueFrom(this.loadTranslations(this.supportedLanguages[ i ], path, libName, priority));
45
+ }
46
+ resolve();
47
+ }, () => {
48
+ this.subLanguages = null;
49
+ console.error('Missing file -> assets/i18n/supported-languages.json <-- FIX THIS!');
50
+ resolve();
51
+ });
52
+ });
53
+ }
54
+
55
+ protected loadTranslations(lang: string, path?: string, libName?: string, priority?: number): Observable<Record<string, string>> {
56
+ const filename: string = libName ? `${libName}-${lang}.json` : `${lang}.json`;
57
+ return this.myHttpClient.get<Record<string, string>>(`${(path || './assets/i18n/') + filename}?v=${getRandomString()}`)
58
+ .pipe(
59
+ tap((response: Record<string, string>) =>
60
+ this.myTranslateCollectorService.addTranslations(response, lang, priority || this.priority)),
61
+ catchError(() => {
62
+ console.error(`Missing file -> ${filename} <-- FIX THIS!`);
63
+ return of(null);
64
+ }),
65
+ );
66
+ }
67
+ }
@@ -0,0 +1,27 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { Observable, Observer } from 'rxjs';
3
+ import { MOSA_FALLBACK_TRANSLATION, MOSA_MERGED_TRANSLATION } from '../../constants/local-storage.constant';
4
+ import { IKeyMap } from '../../models/key-map.model';
5
+ import { isNullOrEmpty, tryJsonParse } from '../../utils/commons.util';
6
+
7
+ @Injectable({
8
+ providedIn: 'root',
9
+ })
10
+ export class CustomTranslateLoaderService {
11
+
12
+ public getTranslation(lang: string): Observable<IKeyMap<string>> {
13
+ return new Observable<IKeyMap<string>>((observer: Observer<IKeyMap<string>>) => {
14
+ const item: string = localStorage.getItem(MOSA_MERGED_TRANSLATION(lang));
15
+ const responseObj: IKeyMap<string> = tryJsonParse(item, () => this.loadFallbackLanguage());
16
+
17
+ observer.next(responseObj);
18
+ observer.complete();
19
+ });
20
+ }
21
+
22
+ private loadFallbackLanguage(): IKeyMap<string> {
23
+ const defaultTranslation: string = localStorage.getItem(MOSA_FALLBACK_TRANSLATION);
24
+ return isNullOrEmpty(defaultTranslation) ? {} : JSON.parse(defaultTranslation);
25
+ }
26
+
27
+ }
@@ -0,0 +1,60 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { TranslateService } from '@ngx-translate/core';
3
+ import { MOSA_MERGED_TRANSLATION } from '../../constants/local-storage.constant';
4
+ import { IKeyMap } from '../../models/key-map.model';
5
+ import { isNullOrUndefined } from '../../utils/commons.util';
6
+
7
+ export interface ITranslationMap {
8
+ priority: number;
9
+ translation: IKeyMap<string>;
10
+ }
11
+
12
+ @Injectable({
13
+ providedIn: 'root',
14
+ })
15
+ export class TranslateCollectorService {
16
+
17
+ private translationMap: IKeyMap<ITranslationMap[]> = {};
18
+
19
+ constructor(private readonly myTranslateService: TranslateService) {
20
+ }
21
+
22
+ public addTranslations(translation: Record<string, string>, lang: string, priority: number): void {
23
+ if (isNullOrUndefined(this.translationMap[ lang ])) {
24
+ this.translationMap[ lang ] = [];
25
+ }
26
+
27
+ this.translationMap[ lang ].push({ priority, translation });
28
+ this.translationMap[ lang ].sort((a: ITranslationMap, b: ITranslationMap) => a.priority - b.priority);
29
+ const merged: IKeyMap<string> = JSON.parse(localStorage.getItem(MOSA_MERGED_TRANSLATION(lang))) || {};
30
+
31
+ for (const translationsData of this.translationMap[ lang ]) {
32
+ const translations: IKeyMap<string> = translationsData.translation;
33
+ if (translations != null) {
34
+ Object.keys(translations).forEach((key: string) => void (merged[ key ] = translations[ key ]));
35
+ }
36
+ }
37
+
38
+ let trans: boolean = true;
39
+ let count: number = 0;
40
+ while (trans) {
41
+ count++;
42
+ trans = false;
43
+ if (count >= 10) {
44
+ console.warn('Limit reached! Please check translation file! Last replaced values:');
45
+ }
46
+ Object.keys(merged).forEach((key: string) => {
47
+ if (merged[ key ].startsWith('@:')) {
48
+ merged[ key ] = merged[ merged[ key ].substring(2, merged[ key ].length) ];
49
+ trans = count < 10;
50
+ if (count > 10) {
51
+ console.warn(merged[ key ]);
52
+ }
53
+ }
54
+ });
55
+ }
56
+ localStorage.setItem(MOSA_MERGED_TRANSLATION(lang), JSON.stringify(merged));
57
+ this.myTranslateService.reloadLang(lang);
58
+ }
59
+
60
+ }
@@ -0,0 +1,100 @@
1
+ import { ITransformMatrix } from '../models/transform-matrix.model';
2
+
3
+ /**
4
+ * Checks if an object is null or undefined. Does not check for
5
+ * empty strings, or false booleans
6
+ * @param val
7
+ */
8
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
+ export function isNullOrUndefined(val: any): boolean {
10
+ return val === null || val === undefined;
11
+ }
12
+
13
+ /**
14
+ * Checks if a string is null or empty
15
+ * @param str
16
+ */
17
+ export function isNullOrEmpty(str: string): boolean {
18
+ return !str?.trim();
19
+ }
20
+
21
+ /**
22
+ * Checks if a string or number is zero or higher
23
+ * @param num
24
+ */
25
+ export function isZeroOrHigher(num: number | string): boolean {
26
+ return num !== null && +num >= 0;
27
+ }
28
+
29
+ /**
30
+ * Checks if an object is a number
31
+ * @param num
32
+ */
33
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
+ export function isNumber(num: any): boolean {
35
+ return num !== null && !isNaN(+num);
36
+ }
37
+
38
+ /**
39
+ * Round to nearest number given
40
+ * @param value
41
+ * @param round
42
+ */
43
+ export function roundNearest(value: number): number {
44
+ return Math.ceil(value / 5) * 5;
45
+ }
46
+
47
+ /**
48
+ * Merge a two dimensional array
49
+ * @param array
50
+ */
51
+ export function mergeArray<T>(array: T[][]): T[] {
52
+ return array.reduce((flat: T[], toFlatten: T[] | T[][]): T[] =>
53
+ flat.concat(Array.isArray(toFlatten) ? mergeArray<T>(toFlatten as T[][]) : toFlatten), []);
54
+ }
55
+
56
+ /**
57
+ * Get CSS Transform matrix from an element
58
+ * @param element
59
+ */
60
+ export function getTransformMatrix(element: HTMLElement): ITransformMatrix {
61
+ const values = element.style.transform.split(/\w+\(|\);?/);
62
+ const transform = values[ 1 ].split(/,\s?/g).map((numStr: string): number => parseInt(numStr, 10));
63
+
64
+ return {
65
+ x: transform[ 0 ],
66
+ y: transform[ 1 ],
67
+ z: transform[ 2 ],
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Generates a random string with numbers and letters
73
+ * @param length
74
+ */
75
+ export function getRandomString(length: number = 10): string {
76
+ const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
77
+ let result: string = '';
78
+ for (let i: number = 0; i < length; i++) {
79
+ result += characters.charAt(Math.floor(Math.random() * characters.length));
80
+ }
81
+ return result;
82
+ }
83
+
84
+ /**
85
+ * Trys to convert a json string. If it fails it returns [null] or a given fallback of [T]
86
+ */
87
+ export function tryJsonParse<T>(data: string, fallback?: () => T): T | null {
88
+ try {
89
+ if (!isNullOrEmpty(data)) {
90
+ return JSON.parse(data);
91
+ }
92
+ } catch (err: unknown) {
93
+ // Ignore - go on with fallback check
94
+ }
95
+
96
+ if (fallback) {
97
+ return fallback();
98
+ }
99
+ return null;
100
+ }
@@ -0,0 +1,140 @@
1
+ import { BehaviorSubject, Observable } from 'rxjs';
2
+ import { map } from 'rxjs/operators';
3
+ import { IDictionaryItem } from '../models/dictionary-item.model';
4
+ import { IKeyMap } from '../models/key-map.model';
5
+
6
+ export class Dictionary<Key, Value> {
7
+
8
+ private readonly dictionaryItems$: BehaviorSubject<IDictionaryItem<Key, Value>[]>;
9
+
10
+ /**
11
+ * Default constructor.
12
+ * @param data Requires an array of IDictionaryItem with a key and a value
13
+ */
14
+ constructor(...data: IDictionaryItem<Key, Value>[]) {
15
+ this.dictionaryItems$ = new BehaviorSubject<IDictionaryItem<Key, Value>[]>(data);
16
+ }
17
+
18
+ /**
19
+ * Observable to subscribe to dictionary changes
20
+ */
21
+ public valueChanges(): Observable<IDictionaryItem<Key, Value>[]> {
22
+ return this.dictionaryItems$.asObservable();
23
+ }
24
+
25
+ /**
26
+ * Observable to subscribe to dictionary as object changes
27
+ */
28
+ public objectArray(): Observable<IKeyMap<Value>> {
29
+ return this.dictionaryItems$.asObservable()
30
+ .pipe(
31
+ map((items: IDictionaryItem<Key, Value>[]): IKeyMap<Value> => this.toObject(items)),
32
+ );
33
+ }
34
+
35
+ /**
36
+ * Gets the items of the dictionary
37
+ * @returns dictionary items
38
+ */
39
+ public get items(): IDictionaryItem<Key, Value>[] {
40
+ return this.dictionaryItems$.value;
41
+ }
42
+
43
+ /**
44
+ * Gets a dictionary item by key
45
+ * @param key
46
+ * @returns single dictionary item
47
+ */
48
+ public get(key: Key): IDictionaryItem<Key, Value> {
49
+ return this.dictionaryItems$.value.find((item: IDictionaryItem<Key, Value>): boolean => item.key === key);
50
+ }
51
+
52
+ /**
53
+ * Checks if dictionary contains item
54
+ * @param key
55
+ */
56
+ public contains(key: Key): boolean {
57
+ return this.get(key) != null;
58
+ }
59
+
60
+ /**
61
+ * Adds a dictionary item
62
+ * @param key
63
+ * @param value
64
+ */
65
+ public add(key: Key, value: Value): void {
66
+ const dictionaryItems: IDictionaryItem<Key, Value>[] = [ ...this.dictionaryItems$.value ];
67
+ dictionaryItems.push({ key, value });
68
+ this.updateItems(dictionaryItems);
69
+ }
70
+
71
+ /**
72
+ * Updates a dictionary item
73
+ * @param data
74
+ * @param index
75
+ */
76
+ public update(data: IDictionaryItem<Key, Value>, index?: number): void {
77
+ const dictionaryItems: IDictionaryItem<Key, Value>[] = [ ...this.dictionaryItems$.value ];
78
+ if (!index) {
79
+ index = this.getIndex(data.key);
80
+ }
81
+
82
+ if (index !== -1) {
83
+ dictionaryItems[ index ] = data;
84
+ } else {
85
+ console.warn('Dictionary: Index not found');
86
+ }
87
+ this.updateItems(dictionaryItems);
88
+ }
89
+
90
+ /**
91
+ * Gets an index with a specific key
92
+ * @param key
93
+ */
94
+ public getIndex(key: Key): number {
95
+ return this.dictionaryItems$.value.findIndex((val: IDictionaryItem<Key, Value>): boolean => val.key === key);
96
+ }
97
+
98
+ /**
99
+ * Removes an item from the dictionary
100
+ * @param value
101
+ * @param index
102
+ */
103
+ public remove(value: Key, index?: number): void {
104
+ const dictionaryItems: IDictionaryItem<Key, Value>[] = [ ...this.dictionaryItems$.value ];
105
+ if (!index) {
106
+ index = this.getIndex(value);
107
+ }
108
+
109
+ if (index !== -1) {
110
+ dictionaryItems.splice(index, 1);
111
+ } else {
112
+ console.warn('Dictionary: Index not found');
113
+ }
114
+ this.updateItems(dictionaryItems);
115
+ }
116
+
117
+ /**
118
+ * Private function to cast array to an object
119
+ * @param items
120
+ * @private
121
+ */
122
+ private toObject(items: IDictionaryItem<Key, Value>[]): IKeyMap<Value> {
123
+ const obj: IKeyMap<Value> = {};
124
+ for (const item of items) {
125
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
126
+ obj[ item.key as any ] = item.value;
127
+ }
128
+ return obj;
129
+ }
130
+
131
+ /**
132
+ * Private function to update dictionary items
133
+ * @param items
134
+ * @private
135
+ */
136
+ private updateItems(items: IDictionaryItem<Key, Value>[]): void {
137
+ this.dictionaryItems$.next(items);
138
+ }
139
+
140
+ }
@@ -0,0 +1,4 @@
1
+ export interface IItem {
2
+ key: number;
3
+ label: string;
4
+ }
@@ -0,0 +1,3 @@
1
+ export type PromiseResolve<T> = (value?: T | PromiseLike<T>) => void;
2
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3
+ export type PromiseReject = (reason?: any) => void;