@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.
- package/.eslintrc.json +31 -0
- package/README.md +24 -0
- package/ng-package.json +10 -0
- package/package.json +12 -0
- package/src/assets/i18n/mosa-de-DE.json +60 -0
- package/src/assets/i18n/mosa-en-US.json +60 -0
- package/src/lib/constants/local-storage.constant.ts +4 -0
- package/src/lib/enums/browser.enum.ts +9 -0
- package/src/lib/enums/content-position.enum.ts +11 -0
- package/src/lib/enums/i18n-supported.enum.ts +10 -0
- package/src/lib/enums/theme.enum.ts +4 -0
- package/src/lib/models/api-options.model.ts +21 -0
- package/src/lib/models/dictionary-item.model.ts +8 -0
- package/src/lib/models/key-map.model.ts +3 -0
- package/src/lib/models/logger/log-event.model.ts +8 -0
- package/src/lib/models/logger/log-message-data.model.ts +5 -0
- package/src/lib/models/logger/log-type.model.ts +1 -0
- package/src/lib/models/logger/log.model.ts +10 -0
- package/src/lib/models/logger/logger-base-config.model.ts +8 -0
- package/src/lib/models/logger/logger-config.model.ts +9 -0
- package/src/lib/models/logger/logger-default-config.model.ts +5 -0
- package/src/lib/models/mosa-duration.model.ts +1 -0
- package/src/lib/models/sieve/sieve-filter.model.ts +10 -0
- package/src/lib/models/sieve/sieve-operator.model.ts +69 -0
- package/src/lib/models/sieve/sieve-options.model.ts +13 -0
- package/src/lib/models/sieve/sieve-response.model.ts +4 -0
- package/src/lib/models/sieve/sieve-sort.model.ts +7 -0
- package/src/lib/models/token-settings.model.ts +5 -0
- package/src/lib/models/transform-matrix.model.ts +5 -0
- package/src/lib/mosa-core.module.ts +117 -0
- package/src/lib/pipes/dictionary-item-pipe/dictionary-item-pipe.module.ts +15 -0
- package/src/lib/pipes/dictionary-item-pipe/dictionary-item.pipe.ts +14 -0
- package/src/lib/pipes/find-in-array/find-in-array-pipe.module.ts +17 -0
- package/src/lib/pipes/find-in-array/find-in-array.pipe.spec.ts +8 -0
- package/src/lib/pipes/find-in-array/find-in-array.pipe.ts +34 -0
- package/src/lib/pipes/join/join-pipe.module.ts +17 -0
- package/src/lib/pipes/join/join.pipe.spec.ts +8 -0
- package/src/lib/pipes/join/join.pipe.ts +20 -0
- package/src/lib/pipes/mosa-date-pipe/mosa-date-pipe.module.ts +14 -0
- package/src/lib/pipes/mosa-date-pipe/mosa-date.pipe.ts +102 -0
- package/src/lib/pipes/mosa-duration-pipe/mosa-duration-pipe.module.ts +13 -0
- package/src/lib/pipes/mosa-duration-pipe/mosa-duration.pipe.ts +106 -0
- package/src/lib/services/api.service.ts +270 -0
- package/src/lib/services/core-logger.service.ts +219 -0
- package/src/lib/services/guards/can-deactivate.guard.ts +18 -0
- package/src/lib/services/mosa-socket.service.ts +46 -0
- package/src/lib/services/translation/assets-i18n-loader.service.ts +67 -0
- package/src/lib/services/translation/custom-translate-loader.service.ts +27 -0
- package/src/lib/services/translation/translate-collector.service.ts +60 -0
- package/src/lib/utils/commons.util.ts +100 -0
- package/src/lib/utils/dictionary.util.ts +140 -0
- package/src/lib/utils/item.util.ts +4 -0
- package/src/lib/utils/promise.util.ts +3 -0
- package/src/lib/utils/prototypes.util.ts +167 -0
- package/src/lib/utils/sieve.util.ts +169 -0
- package/src/lib/utils/size.util.ts +4 -0
- package/src/public-api.ts +1 -0
- package/tsconfig.lib.json +16 -0
- package/tsconfig.lib.prod.json +10 -0
- 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
|
+
}
|