@libs-ui/utils 0.1.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/README.md +3 -0
- package/base64.d.ts +5 -0
- package/cache.d.ts +42 -0
- package/color.d.ts +11 -0
- package/communicate-micro.d.ts +16 -0
- package/constants.d.ts +10 -0
- package/crypto-3rd.d.ts +7 -0
- package/crypto.d.ts +8 -0
- package/dangerous-object.d.ts +79 -0
- package/date.d.ts +44 -0
- package/dom.d.ts +52 -0
- package/download.d.ts +3 -0
- package/esm2022/base64.mjs +43 -0
- package/esm2022/cache.mjs +388 -0
- package/esm2022/color.mjs +133 -0
- package/esm2022/communicate-micro.mjs +149 -0
- package/esm2022/constants.mjs +11 -0
- package/esm2022/crypto-3rd.mjs +38 -0
- package/esm2022/crypto.mjs +41 -0
- package/esm2022/dangerous-object.mjs +149 -0
- package/esm2022/date.mjs +191 -0
- package/esm2022/dom.mjs +256 -0
- package/esm2022/download.mjs +41 -0
- package/esm2022/file.mjs +90 -0
- package/esm2022/format-number.mjs +66 -0
- package/esm2022/format-text.mjs +149 -0
- package/esm2022/function-check-embed-frame.mjs +10 -0
- package/esm2022/get-smart-axis-scale.mjs +174 -0
- package/esm2022/helpers.mjs +651 -0
- package/esm2022/http-params.mjs +80 -0
- package/esm2022/index.mjs +30 -0
- package/esm2022/inject-token.mjs +5 -0
- package/esm2022/key-cache.mjs +31 -0
- package/esm2022/key-code.mjs +123 -0
- package/esm2022/language.mjs +70 -0
- package/esm2022/libs-ui-utils.mjs +5 -0
- package/esm2022/pattern.mjs +62 -0
- package/esm2022/random.mjs +42 -0
- package/esm2022/two-way-signal-object.mjs +131 -0
- package/esm2022/uri.mjs +25 -0
- package/esm2022/url-search-params.mjs +99 -0
- package/esm2022/uuid.mjs +18 -0
- package/esm2022/xss-filter.mjs +10 -0
- package/fesm2022/libs-ui-utils.mjs +3234 -0
- package/fesm2022/libs-ui-utils.mjs.map +1 -0
- package/file.d.ts +18 -0
- package/format-number.d.ts +2 -0
- package/format-text.d.ts +11 -0
- package/function-check-embed-frame.d.ts +2 -0
- package/get-smart-axis-scale.d.ts +34 -0
- package/helpers.d.ts +270 -0
- package/http-params.d.ts +37 -0
- package/index.d.ts +29 -0
- package/inject-token.d.ts +4 -0
- package/key-cache.d.ts +1 -0
- package/key-code.d.ts +122 -0
- package/language.d.ts +37 -0
- package/package.json +29 -0
- package/pattern.d.ts +20 -0
- package/random.d.ts +3 -0
- package/two-way-signal-object.d.ts +15 -0
- package/uri.d.ts +5 -0
- package/url-search-params.d.ts +25 -0
- package/uuid.d.ts +1 -0
- package/xss-filter.d.ts +3 -0
|
@@ -0,0 +1,3234 @@
|
|
|
1
|
+
import DeviceDetector from 'device-detector-js';
|
|
2
|
+
import Quill from 'quill';
|
|
3
|
+
import { Observable, Subject, fromEvent, takeUntil, filter, tap, mergeMap, startWith, finalize, lastValueFrom, timer } from 'rxjs';
|
|
4
|
+
import { HttpParams } from '@angular/common/http';
|
|
5
|
+
import { TemplateRef, ElementRef, isSignal, signal, InjectionToken } from '@angular/core';
|
|
6
|
+
import dayjs from 'dayjs';
|
|
7
|
+
import 'dayjs/locale/en';
|
|
8
|
+
import 'dayjs/locale/vi';
|
|
9
|
+
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
|
10
|
+
import localeData from 'dayjs/plugin/localeData';
|
|
11
|
+
import timezone from 'dayjs/plugin/timezone';
|
|
12
|
+
import updateLocale from 'dayjs/plugin/updateLocale';
|
|
13
|
+
import utc from 'dayjs/plugin/utc';
|
|
14
|
+
import CryptoES from 'crypto-es';
|
|
15
|
+
|
|
16
|
+
const ERROR_MESSAGE_EMPTY_VALID = 'i18n_valid_empty_message';
|
|
17
|
+
const ERROR_MESSAGE_PATTEN_VALID = 'i18n_valid_pattern_message';
|
|
18
|
+
const ERROR_MESSAGE_MIN_VALID = 'i18n_message_error_input_min_value';
|
|
19
|
+
const ERROR_MESSAGE_MAX_VALID = 'i18n_message_error_input_max_value';
|
|
20
|
+
const ERROR_MESSAGE_MIN_LENGTH = 'i18n_message_error_input_min_length';
|
|
21
|
+
const ERROR_MESSAGE_MAX_LENGTH = 'i18n_message_error_input_max_length';
|
|
22
|
+
const CHARACTER_DATA_EMPTY = '__';
|
|
23
|
+
const DEFAULT_START_PAGE_0 = 'defaultStartPage0';
|
|
24
|
+
const COMMUNICATE_MICRO_PREFIX_TYPE = '3RD_INTEGRATE_MICRO_SITE_';
|
|
25
|
+
const COMMUNICATE_MICRO_KEY_GET_ALL_MESSAGE = 'MICRO_SITES_ALL_MESSAGE';
|
|
26
|
+
|
|
27
|
+
/* eslint-disable no-useless-escape */
|
|
28
|
+
const patternEmail = () => {
|
|
29
|
+
return /^[A-z0-9]+[A-z0-9\_\.\+\-]*[A-z0-9]@[A-z0-9]+[A-z0-9\_\.\+\-]*[A-z0-9]\.[A-z0-9]+[A-z0-9\_\.\+\-]*[A-z0-9]$/;
|
|
30
|
+
};
|
|
31
|
+
const patternUrl = () => {
|
|
32
|
+
return /^(http|https|ftp):(\/){2}[^\s]+[.]{1}[^\s]+$/;
|
|
33
|
+
};
|
|
34
|
+
const patternHostUrl = () => {
|
|
35
|
+
return /^((https|http|ftp):[/]{2}[^/\s]+)/;
|
|
36
|
+
};
|
|
37
|
+
const patternDomain = () => {
|
|
38
|
+
return /^([a-zA-Z0-9])(([a-z0-9-]{1,61})?[a-z0-9]{1})?(\.[a-z0-9](([a-z0-9-]{1,61})?[a-z0-9]{1})?)?(\.[a-zA-Z]{2,4})+$/;
|
|
39
|
+
};
|
|
40
|
+
const patternMobilePhone = () => {
|
|
41
|
+
return /^(\+?84|0|84)([0-9]{9})$/;
|
|
42
|
+
};
|
|
43
|
+
const patternPhone = () => {
|
|
44
|
+
return /^(\+?84|[0-9]|84)([0-9]{2,9})$/;
|
|
45
|
+
};
|
|
46
|
+
const patternPhoneNormal = () => {
|
|
47
|
+
return /^(\+?84|[0-9]|84)([0-9]{9,10})$/;
|
|
48
|
+
};
|
|
49
|
+
const patternNumber = () => {
|
|
50
|
+
return /\d+/g;
|
|
51
|
+
};
|
|
52
|
+
const patternEncodeUri = () => {
|
|
53
|
+
return /%([0-9A-F]{2})/g;
|
|
54
|
+
};
|
|
55
|
+
const patternName = () => {
|
|
56
|
+
return /^\w+[A-Za-z\s\d]+$/;
|
|
57
|
+
};
|
|
58
|
+
const patternNameUtf8 = () => {
|
|
59
|
+
return /^[ àáảãạăắằẵặẳâầấậẫẩđèéẻẽẹêềếểễệìíỉĩịòóỏõọôồốổỗộơờớởỡợùúủũụưừứửữựỳýỷỹÀÁẢÃẠĂẮẰẴẶẲÂẦẤẬẪẨĐÈÉẺẼẸÊỀẾỂỄỆÌÍỈĨỊÒÓỎÕỌÔỒỐỔỖỘƠỜỚỞỠỢÙÚỦŨỤƯỪỨỬỮỰỲÝỶỸA-Za-z0-9]+$/;
|
|
60
|
+
};
|
|
61
|
+
const patternNameSpecial = () => {
|
|
62
|
+
return /[~!@#$%^&*()-+=<>,?\/\\:;"']/;
|
|
63
|
+
};
|
|
64
|
+
const patternNameProfile = () => {
|
|
65
|
+
return /([\w\W\d\s]+)+/;
|
|
66
|
+
};
|
|
67
|
+
const patternEmoji = () => {
|
|
68
|
+
return /(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/g;
|
|
69
|
+
};
|
|
70
|
+
const patternRuleFieldReplace = () => {
|
|
71
|
+
return /[{]{2}[a-zA-Z_-]+[}]{2}/g;
|
|
72
|
+
};
|
|
73
|
+
const patternGetFieldByRuleFieldReplace = () => {
|
|
74
|
+
return /[a-zA-Z_-]+/g;
|
|
75
|
+
};
|
|
76
|
+
const patternPem = () => {
|
|
77
|
+
return /^(0|1):([0-9]{1,2}):(\{\{path-api\}\}):([a-zA-Z0-9\/]+)$/g;
|
|
78
|
+
};
|
|
79
|
+
const patternTax = () => {
|
|
80
|
+
return /^([0-9]){10}(-[0-9]{3})?$/;
|
|
81
|
+
};
|
|
82
|
+
const patternKey = () => {
|
|
83
|
+
return /^([0-9]){10}(-[0-9]{3})?$/;
|
|
84
|
+
};
|
|
85
|
+
const patternAccount = () => {
|
|
86
|
+
return /^(?=.*@)[a-z0-9@._-]{2,63}$/;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const highlightByKeyword = (value, search, ignoreHighlight, classHightLight) => {
|
|
90
|
+
if (!value) {
|
|
91
|
+
return CHARACTER_DATA_EMPTY;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
if (!search || ignoreHighlight) {
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
const keysReplace = getValueReplace(search, value);
|
|
98
|
+
if (!keysReplace || !keysReplace.length) {
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
if (!classHightLight) {
|
|
102
|
+
classHightLight = 'bg-[#19344a] text-white';
|
|
103
|
+
}
|
|
104
|
+
keysReplace.forEach((key) => {
|
|
105
|
+
const regExp = new RegExp(key, 'gi');
|
|
106
|
+
value = value?.replace(regExp, `<span class="${classHightLight}">$&</span>`);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
console.log(error);
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
// eslint-disable-next-line no-unsafe-finally
|
|
114
|
+
return value;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const getValueReplace = (search, value) => {
|
|
118
|
+
const searchConvert = deleteUnicode(search).toLowerCase();
|
|
119
|
+
const valueConvert = deleteUnicode(value).toLowerCase();
|
|
120
|
+
const keys = new Set();
|
|
121
|
+
let i = 0;
|
|
122
|
+
while ((i = valueConvert.indexOf(searchConvert, i)) >= 0) {
|
|
123
|
+
const endIndex = i + search.length;
|
|
124
|
+
keys.add(value.substring(i, endIndex));
|
|
125
|
+
i += +valueConvert.length;
|
|
126
|
+
}
|
|
127
|
+
return Array.from(keys);
|
|
128
|
+
};
|
|
129
|
+
const formatTextCompare = (text, options) => {
|
|
130
|
+
if (!text) {
|
|
131
|
+
return text;
|
|
132
|
+
}
|
|
133
|
+
text = text.normalize('NFC');
|
|
134
|
+
return formatByOptions(text, options);
|
|
135
|
+
};
|
|
136
|
+
const fullNameFormat = (value) => {
|
|
137
|
+
if (!value) {
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
return capitalize(value, { lowercaseOtherCharacter: true, trim: true, removeMultipleSpace: true, removeEmoji: true });
|
|
141
|
+
};
|
|
142
|
+
const capitalize = (text, options) => {
|
|
143
|
+
if (!text) {
|
|
144
|
+
return text;
|
|
145
|
+
}
|
|
146
|
+
text = formatByOptions(text, options);
|
|
147
|
+
return text
|
|
148
|
+
.split(' ')
|
|
149
|
+
.map((word) => firstLetterToUpperCase(word))
|
|
150
|
+
.join(' ');
|
|
151
|
+
};
|
|
152
|
+
const firstLetterToUpperCase = (text, options) => {
|
|
153
|
+
if (!text) {
|
|
154
|
+
return text;
|
|
155
|
+
}
|
|
156
|
+
return uppercaseByPosition(text, 0, options);
|
|
157
|
+
};
|
|
158
|
+
const uppercaseByPosition = (text, position, options) => {
|
|
159
|
+
if (!text) {
|
|
160
|
+
return text;
|
|
161
|
+
}
|
|
162
|
+
text = formatByOptions(text, options);
|
|
163
|
+
return `${text.substring(0, position)}${text.charAt(position).toUpperCase()}${text.substring(position + 1)}`;
|
|
164
|
+
};
|
|
165
|
+
const formatByOptions = (text, options) => {
|
|
166
|
+
if (!text || !options) {
|
|
167
|
+
return text;
|
|
168
|
+
}
|
|
169
|
+
if (options?.uppercaseOtherCharacter) {
|
|
170
|
+
text = text.toUpperCase();
|
|
171
|
+
}
|
|
172
|
+
if (options?.lowercaseOtherCharacter) {
|
|
173
|
+
text = text.toLowerCase();
|
|
174
|
+
}
|
|
175
|
+
if (options?.removeMultipleSpace) {
|
|
176
|
+
text = text.replace(/\u200B|\u00A0/g, '');
|
|
177
|
+
text = text.replace(/\s+/g, ' ');
|
|
178
|
+
}
|
|
179
|
+
if (options?.removeEmoji) {
|
|
180
|
+
text = removeEmoji(text);
|
|
181
|
+
}
|
|
182
|
+
if (options?.removeUnicode) {
|
|
183
|
+
text = deleteUnicode(text);
|
|
184
|
+
}
|
|
185
|
+
if (options?.trim) {
|
|
186
|
+
text = text.trim();
|
|
187
|
+
}
|
|
188
|
+
return text;
|
|
189
|
+
};
|
|
190
|
+
const escapeHtml = (str) => {
|
|
191
|
+
if (!str || typeof str !== 'string') {
|
|
192
|
+
return str;
|
|
193
|
+
}
|
|
194
|
+
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
195
|
+
};
|
|
196
|
+
const decodeEscapeHtml = (str) => {
|
|
197
|
+
const htmlTag = document.createElement('textarea');
|
|
198
|
+
htmlTag.innerHTML = str;
|
|
199
|
+
while (str) {
|
|
200
|
+
if (str === htmlTag.value) {
|
|
201
|
+
str = htmlTag.value;
|
|
202
|
+
htmlTag.remove();
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
str = htmlTag.value;
|
|
206
|
+
htmlTag.innerHTML = str;
|
|
207
|
+
}
|
|
208
|
+
return str;
|
|
209
|
+
};
|
|
210
|
+
const deleteUnicode = (str) => {
|
|
211
|
+
str = str.replace(/à|á|ạ|ả|ã|â|ầ|ấ|ậ|ẩ|ẫ|ă|ằ|ắ|ặ|ẳ|ẵ/g, 'a');
|
|
212
|
+
str = str.replace(/è|é|ẹ|ẻ|ẽ|ê|ề|ế|ệ|ể|ễ|ễ/g, 'e');
|
|
213
|
+
str = str.replace(/ì|í|ị|ỉ|ĩ/g, 'i');
|
|
214
|
+
str = str.replace(/ò|ó|ọ|ỏ|õ|ô|ồ|ố|ộ|ổ|ỗ|ơ|ờ|ớ|ợ|ở|ỡ/g, 'o');
|
|
215
|
+
str = str.replace(/ù|ú|ụ|ủ|ũ|ư|ừ|ứ|ự|ử|ữ/g, 'u');
|
|
216
|
+
str = str.replace(/ỳ|ý|ỵ|ỷ|ỹ/g, 'y');
|
|
217
|
+
str = str.replace(/đ/g, 'd');
|
|
218
|
+
str = str.replace(/À|Á|Ạ|Ả|Ã|Â|Ầ|Ấ|Ậ|Ẩ|Ẫ|Ă|Ằ|Ắ|Ặ|Ẳ|Ẵ/g, 'A');
|
|
219
|
+
str = str.replace(/È|É|Ẹ|Ẻ|Ẽ|Ê|Ề|Ế|Ệ|Ể|Ễ/g, 'E');
|
|
220
|
+
str = str.replace(/Ì|Í|Ị|Ỉ|Ĩ/g, 'I');
|
|
221
|
+
str = str.replace(/Ò|Ó|Ọ|Ỏ|Õ|Ô|Ồ|Ố|Ộ|Ổ|Ỗ|Ơ|Ờ|Ớ|Ợ|Ở|Ỡ/g, 'O');
|
|
222
|
+
str = str.replace(/Ù|Ú|Ụ|Ủ|Ũ|Ư|Ừ|Ứ|Ự|Ử|Ữ/g, 'U');
|
|
223
|
+
str = str.replace(/Ỳ|Ý|Ỵ|Ỷ|Ỹ/g, 'Y');
|
|
224
|
+
str = str.replace(/Đ/g, 'D');
|
|
225
|
+
str = str.replace(/\u0300|\u0301|\u0303|\u0309|\u0323/g, ''); // ̀ ́ ̃ ̉ ̣ huyền, sắc, ngã, hỏi, nặng
|
|
226
|
+
str = str.replace(/\u02C6|\u0306|\u031B/g, ''); // ˆ ̆ ̛ Â, Ê, Ă, Ơ, Ư
|
|
227
|
+
return str.normalize('NFC');
|
|
228
|
+
};
|
|
229
|
+
const removeEmoji = (text) => {
|
|
230
|
+
if (!text || !text.trim()) {
|
|
231
|
+
return text;
|
|
232
|
+
}
|
|
233
|
+
return text.replace(patternEmoji(), '');
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
237
|
+
/**
|
|
238
|
+
* Danh sách các constructor name nguy hiểm
|
|
239
|
+
*/
|
|
240
|
+
const DANGEROUS_CONSTRUCTOR_NAMES = ['Window', 'Document', 'HTMLDocument', 'HTMLElement', 'Element', 'Node', 'Location', 'Navigator', 'Screen', 'History', 'Storage', 'Console', 'Performance'];
|
|
241
|
+
/**
|
|
242
|
+
* Kiểm tra xem đối tượng có phải là browser global object không
|
|
243
|
+
*/
|
|
244
|
+
const isBrowserGlobalObject = (obj) => {
|
|
245
|
+
if (obj === null || typeof obj !== 'object') {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
return obj instanceof Window || obj instanceof Document || obj === window || obj === document || obj === globalThis;
|
|
249
|
+
};
|
|
250
|
+
/**
|
|
251
|
+
* Kiểm tra xem đối tượng có phải là DOM object không
|
|
252
|
+
*/
|
|
253
|
+
const isDOMObject = (obj) => {
|
|
254
|
+
if (obj === null || typeof obj !== 'object') {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
return obj instanceof HTMLElement || obj instanceof Node || obj instanceof Element;
|
|
258
|
+
};
|
|
259
|
+
/**
|
|
260
|
+
* Kiểm tra xem đối tượng có phải là browser API object không
|
|
261
|
+
*/
|
|
262
|
+
const isBrowserAPIObject = (obj) => {
|
|
263
|
+
if (obj === null || typeof obj !== 'object' || typeof window === 'undefined') {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
return obj === window.location || obj === window.navigator || obj === window.screen || obj === window.history || obj === window.localStorage || obj === window.sessionStorage || obj === window.console || obj === window.performance;
|
|
267
|
+
};
|
|
268
|
+
/**
|
|
269
|
+
* Kiểm tra constructor name có nằm trong danh sách nguy hiểm không
|
|
270
|
+
*/
|
|
271
|
+
const hasDangerousConstructorName = (obj) => {
|
|
272
|
+
if (obj === null || typeof obj !== 'object') {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
const constructorName = obj.constructor?.name;
|
|
276
|
+
return constructorName && DANGEROUS_CONSTRUCTOR_NAMES.includes(constructorName);
|
|
277
|
+
};
|
|
278
|
+
/**
|
|
279
|
+
* Kiểm tra xem đối tượng có phải là DOM object hoặc browser object nguy hiểm không
|
|
280
|
+
* Những đối tượng này có thể gây ra circular reference và maximum call stack
|
|
281
|
+
*/
|
|
282
|
+
const isDangerousObject = (obj) => {
|
|
283
|
+
if (obj === null || typeof obj !== 'object') {
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
return isBrowserGlobalObject(obj) || isDOMObject(obj) || isBrowserAPIObject(obj) || hasDangerousConstructorName(obj);
|
|
287
|
+
};
|
|
288
|
+
/**
|
|
289
|
+
* Kiểm tra đối tượng có phải là Angular/Framework object không
|
|
290
|
+
*/
|
|
291
|
+
const isFrameworkObject = (obj) => {
|
|
292
|
+
if (obj === null || typeof obj !== 'object') {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
return obj instanceof TemplateRef || obj instanceof ElementRef;
|
|
296
|
+
};
|
|
297
|
+
/**
|
|
298
|
+
* Kiểm tra đối tượng có phải là File/Blob object không
|
|
299
|
+
*/
|
|
300
|
+
const isFile = (obj) => {
|
|
301
|
+
if (obj === null || typeof obj !== 'object') {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
return obj instanceof File || obj instanceof Blob || Object.prototype.toString.call(obj) === '[object File]';
|
|
305
|
+
};
|
|
306
|
+
/**
|
|
307
|
+
* Kiểm tra đối tượng có phải là Built-in object không
|
|
308
|
+
*/
|
|
309
|
+
const isBuiltInObject = (obj) => {
|
|
310
|
+
if (obj === null || typeof obj !== 'object') {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
return obj instanceof Date || isRegExp(obj) || obj instanceof HttpParams || ('hasOwnProperty' in obj && '__ngContext__' in obj);
|
|
314
|
+
};
|
|
315
|
+
/**
|
|
316
|
+
* Kiểm tra đối tượng có phải là RegExp object không
|
|
317
|
+
*/
|
|
318
|
+
const isRegExp = (obj) => {
|
|
319
|
+
return obj instanceof RegExp;
|
|
320
|
+
};
|
|
321
|
+
/**
|
|
322
|
+
* Kiểm tra đối tượng có phải là Async object không
|
|
323
|
+
*/
|
|
324
|
+
const isAsyncObject = (obj) => {
|
|
325
|
+
if (obj === null || typeof obj !== 'object') {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
return obj instanceof Promise || obj instanceof Observable;
|
|
329
|
+
};
|
|
330
|
+
/**
|
|
331
|
+
* Kiểm tra đối tượng có phải là Special object cần bỏ qua không
|
|
332
|
+
* Bao gồm: Framework objects, File objects, Built-in objects, Async objects
|
|
333
|
+
*/
|
|
334
|
+
const isSpecialObject = (obj) => {
|
|
335
|
+
return isFrameworkObject(obj) || isFile(obj) || isBuiltInObject(obj) || isAsyncObject(obj);
|
|
336
|
+
};
|
|
337
|
+
/**
|
|
338
|
+
* Kiểm tra đối tượng có phải là dayjs object không
|
|
339
|
+
*/
|
|
340
|
+
const isDayjsObject = (obj) => {
|
|
341
|
+
return dayjs.isDayjs(obj);
|
|
342
|
+
};
|
|
343
|
+
/**
|
|
344
|
+
* Kiểm tra đối tượng có phải là object cần bỏ qua trong quá trình convert không
|
|
345
|
+
*/
|
|
346
|
+
const isSkippableObject = (obj) => {
|
|
347
|
+
return isDangerousObject(obj) || isSpecialObject(obj);
|
|
348
|
+
};
|
|
349
|
+
/**
|
|
350
|
+
* Kiểm tra đối tượng có phải là Map object không
|
|
351
|
+
*/
|
|
352
|
+
const isMap = (obj) => {
|
|
353
|
+
return obj instanceof Map;
|
|
354
|
+
};
|
|
355
|
+
/**
|
|
356
|
+
* Kiểm tra đối tượng có phải là Set object không
|
|
357
|
+
*/
|
|
358
|
+
const isSet = (obj) => {
|
|
359
|
+
return obj instanceof Set;
|
|
360
|
+
};
|
|
361
|
+
/**
|
|
362
|
+
* Kiểm tra đối tượng có phải là Array object không
|
|
363
|
+
*/
|
|
364
|
+
const isArray = (obj) => {
|
|
365
|
+
return Array.isArray(obj);
|
|
366
|
+
};
|
|
367
|
+
/**
|
|
368
|
+
* Kiểm tra đối tượng có phải là object cần trả về nguyên trạng không
|
|
369
|
+
* Bao gồm: dangerous objects, special objects, dayjs objects
|
|
370
|
+
*/
|
|
371
|
+
const isReturnAsIsObject = (obj) => {
|
|
372
|
+
return isSkippableObject(obj) || isDayjsObject(obj);
|
|
373
|
+
};
|
|
374
|
+
/**
|
|
375
|
+
* Kiểm tra xem đối tượng có an toàn để clone/convert không
|
|
376
|
+
*/
|
|
377
|
+
const isSafeToProcess = (obj) => {
|
|
378
|
+
return !isDangerousObject(obj);
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
let key$1 = '12~@#loqwsxacva(3rdhaq12';
|
|
382
|
+
/**
|
|
383
|
+
* @description Thiết lập key mã hóa
|
|
384
|
+
* @param value key mã hóa, độ dài bằng 24 hoặc 32 ký tự.
|
|
385
|
+
*/
|
|
386
|
+
const setKeyCrypto = (value) => {
|
|
387
|
+
if (value.length !== 24 && value.length !== 32) {
|
|
388
|
+
throw Error(`key.length = ${value.length}; Key phải là 1 chuỗi dài 24 hoặc 32 ký tự`);
|
|
389
|
+
}
|
|
390
|
+
key$1 = value;
|
|
391
|
+
};
|
|
392
|
+
const keyStore$1 = () => {
|
|
393
|
+
return key$1;
|
|
394
|
+
};
|
|
395
|
+
const encrypt = (plainData) => {
|
|
396
|
+
if (!keyStore$1()) {
|
|
397
|
+
throw Error('lỗi chưa setup key mã hóa');
|
|
398
|
+
}
|
|
399
|
+
const key = CryptoES.enc.Hex.parse(keyStore$1());
|
|
400
|
+
const iv = CryptoES.enc.Hex.parse(keyStore$1());
|
|
401
|
+
const mode = CryptoES.mode.CBC;
|
|
402
|
+
const padding = CryptoES.pad.Pkcs7;
|
|
403
|
+
const options = { iv: iv, mode: mode, padding: padding };
|
|
404
|
+
return CryptoES.AES.encrypt(plainData, key, options).toString();
|
|
405
|
+
};
|
|
406
|
+
const decrypt = (encryptedData) => {
|
|
407
|
+
if (!keyStore$1()) {
|
|
408
|
+
throw Error('lỗi chưa setup key mã hóa');
|
|
409
|
+
}
|
|
410
|
+
const key = CryptoES.enc.Hex.parse(keyStore$1());
|
|
411
|
+
const iv = CryptoES.enc.Hex.parse(keyStore$1());
|
|
412
|
+
const mode = CryptoES.mode.CBC;
|
|
413
|
+
const padding = CryptoES.pad.Pkcs7;
|
|
414
|
+
const options = { iv: iv, mode: mode, padding: padding };
|
|
415
|
+
return CryptoES.AES.decrypt(encryptedData, key, options).toString(CryptoES.enc.Utf8);
|
|
416
|
+
};
|
|
417
|
+
const md5 = (plainData) => {
|
|
418
|
+
return CryptoES.MD5(plainData).toString();
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
let key = '12~@#loqwsxacva(3rdhaq12';
|
|
422
|
+
/**
|
|
423
|
+
* @description Thiết lập key mã hóa
|
|
424
|
+
* @param value key mã hóa, độ dài bằng 24 hoặc 32 ký tự.
|
|
425
|
+
*/
|
|
426
|
+
const setKeyCrypto3rd = (value) => {
|
|
427
|
+
if (value.length !== 24 && value.length !== 32) {
|
|
428
|
+
throw Error(`key.length = ${value.length}; Key phải là 1 chuỗi dài 24 hoặc 32 ký tự`);
|
|
429
|
+
}
|
|
430
|
+
key = value;
|
|
431
|
+
};
|
|
432
|
+
const keyStore = () => {
|
|
433
|
+
return key;
|
|
434
|
+
};
|
|
435
|
+
const encrypt3rd = (plainData) => {
|
|
436
|
+
if (!keyStore()) {
|
|
437
|
+
throw Error('lỗi chưa setup key mã hóa');
|
|
438
|
+
}
|
|
439
|
+
const key = CryptoES.enc.Hex.parse(keyStore());
|
|
440
|
+
const iv = CryptoES.enc.Hex.parse(keyStore());
|
|
441
|
+
const mode = CryptoES.mode.CBC;
|
|
442
|
+
const padding = CryptoES.pad.Pkcs7;
|
|
443
|
+
const options = { iv: iv, mode: mode, padding: padding };
|
|
444
|
+
return CryptoES.AES.encrypt(plainData, key, options).toString();
|
|
445
|
+
};
|
|
446
|
+
const decrypt3rd = (encryptedData) => {
|
|
447
|
+
if (!keyStore()) {
|
|
448
|
+
throw Error('lỗi chưa setup key mã hóa');
|
|
449
|
+
}
|
|
450
|
+
const key = CryptoES.enc.Hex.parse(keyStore());
|
|
451
|
+
const iv = CryptoES.enc.Hex.parse(keyStore());
|
|
452
|
+
const mode = CryptoES.mode.CBC;
|
|
453
|
+
const padding = CryptoES.pad.Pkcs7;
|
|
454
|
+
const options = { iv: iv, mode: mode, padding: padding };
|
|
455
|
+
return CryptoES.AES.decrypt(encryptedData, key, options).toString(CryptoES.enc.Utf8);
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
let functionCheck = () => {
|
|
459
|
+
return window.parent !== window.top;
|
|
460
|
+
};
|
|
461
|
+
const updateFunctionCheckEmbedFrame = (functionCustom) => {
|
|
462
|
+
functionCheck = functionCustom;
|
|
463
|
+
};
|
|
464
|
+
const isEmbedFrame = () => {
|
|
465
|
+
return functionCheck();
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
469
|
+
class UtilsCommunicateMicroKeyGlobal {
|
|
470
|
+
static KEY_MESSAGE_MODAL = 'LIBS_UI_MODEL_EVENT';
|
|
471
|
+
}
|
|
472
|
+
class UtilsCommunicateMicro {
|
|
473
|
+
static initdEvent;
|
|
474
|
+
static subs = new Map();
|
|
475
|
+
static allMessageSub = new Subject();
|
|
476
|
+
static initEvent(currentWindow, onDestroy) {
|
|
477
|
+
if (this.initdEvent) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
this.initdEvent = true;
|
|
481
|
+
if (!this.subs.get(COMMUNICATE_MICRO_KEY_GET_ALL_MESSAGE)) {
|
|
482
|
+
this.subs.set(COMMUNICATE_MICRO_KEY_GET_ALL_MESSAGE, UtilsCommunicateMicro.allMessageSub);
|
|
483
|
+
}
|
|
484
|
+
fromEvent(currentWindow, 'message')
|
|
485
|
+
.pipe(takeUntil(onDestroy))
|
|
486
|
+
.subscribe((e) => {
|
|
487
|
+
const event = { data: { ...e.data } };
|
|
488
|
+
const data = event.data;
|
|
489
|
+
const type = data.type;
|
|
490
|
+
if (!type) {
|
|
491
|
+
return this.allMessageSub.next(event);
|
|
492
|
+
}
|
|
493
|
+
const sub = UtilsCommunicateMicro.GetMessage(type);
|
|
494
|
+
if (!type || !data.response) {
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
try {
|
|
498
|
+
if (type.includes(COMMUNICATE_MICRO_PREFIX_TYPE)) {
|
|
499
|
+
data.response = JSON.parse(decrypt3rd(data.response));
|
|
500
|
+
sub.next(event);
|
|
501
|
+
this.allMessageSub.next(event);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
data.response = JSON.parse(decrypt(data.response));
|
|
505
|
+
sub.next(event);
|
|
506
|
+
this.allMessageSub.next(event);
|
|
507
|
+
}
|
|
508
|
+
catch (error) {
|
|
509
|
+
console.log(error);
|
|
510
|
+
sub.next(event);
|
|
511
|
+
this.allMessageSub.next(event);
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
UtilsCommunicateMicro.GetMessage(UtilsCache.typeKeyClearLocalStorage)
|
|
515
|
+
.pipe(filter((e) => e.data.response !== UtilsCache.idService), takeUntil(onDestroy))
|
|
516
|
+
.subscribe(() => {
|
|
517
|
+
console.log('clear all cache by event');
|
|
518
|
+
UtilsCache.ClearAll();
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
static PostMessageToParent(data) {
|
|
522
|
+
data = this.convertData(data);
|
|
523
|
+
try {
|
|
524
|
+
if (isEmbedFrame()) {
|
|
525
|
+
window?.parent?.postMessage(data, '*');
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
window?.top?.postMessage(data, '*');
|
|
529
|
+
}
|
|
530
|
+
catch (error) {
|
|
531
|
+
console.log(error);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
static PostMessageToChildren(data) {
|
|
535
|
+
data = this.convertData(data);
|
|
536
|
+
const iframes = document.querySelectorAll('iframe');
|
|
537
|
+
Array.from(iframes).forEach((iframe) => {
|
|
538
|
+
iframe?.contentWindow?.postMessage(data, '*');
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
static PostMessageToOpener(data) {
|
|
542
|
+
if (!window.opener) {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
data = this.convertData(data);
|
|
546
|
+
window.opener.postMessage(data, '*');
|
|
547
|
+
}
|
|
548
|
+
static convertData(data) {
|
|
549
|
+
if (!data || !data.type || !data.response) {
|
|
550
|
+
return data;
|
|
551
|
+
}
|
|
552
|
+
data = { ...data };
|
|
553
|
+
const type = data.type;
|
|
554
|
+
if (type.includes(COMMUNICATE_MICRO_PREFIX_TYPE)) {
|
|
555
|
+
try {
|
|
556
|
+
JSON.parse(decrypt3rd(data.response));
|
|
557
|
+
return data;
|
|
558
|
+
}
|
|
559
|
+
catch (_) {
|
|
560
|
+
data.response = encrypt3rd(JSON.stringify(data.response));
|
|
561
|
+
return data;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
try {
|
|
565
|
+
JSON.parse(decrypt(data.response));
|
|
566
|
+
return data;
|
|
567
|
+
}
|
|
568
|
+
catch (_) {
|
|
569
|
+
data.response = encrypt(JSON.stringify(data.response));
|
|
570
|
+
return data;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
static GetMessage(messageType) {
|
|
574
|
+
if (!this.initdEvent) {
|
|
575
|
+
throw Error('chưa khơi tạo hàm lắng nghe sự kiện, gọi UtilsCommunicateMicro.initEvent(window) tại root component');
|
|
576
|
+
}
|
|
577
|
+
if (typeof messageType === 'string') {
|
|
578
|
+
let sub = this.subs.get(messageType);
|
|
579
|
+
if (!sub) {
|
|
580
|
+
sub = new Subject();
|
|
581
|
+
this.subs.set(messageType, sub);
|
|
582
|
+
return sub;
|
|
583
|
+
}
|
|
584
|
+
return sub;
|
|
585
|
+
}
|
|
586
|
+
if (!Array.isArray(messageType) || !messageType.length) {
|
|
587
|
+
throw new Error('message type empty');
|
|
588
|
+
}
|
|
589
|
+
if (messageType.length === 1) {
|
|
590
|
+
return this.GetMessage(messageType[0]);
|
|
591
|
+
}
|
|
592
|
+
const types = messageType.sort().join(';');
|
|
593
|
+
let sub = this.subs.get(types);
|
|
594
|
+
if (sub) {
|
|
595
|
+
return sub;
|
|
596
|
+
}
|
|
597
|
+
sub = new Subject();
|
|
598
|
+
this.subs.set(types, sub);
|
|
599
|
+
this.initSubject(sub, messageType);
|
|
600
|
+
return sub;
|
|
601
|
+
}
|
|
602
|
+
static initSubject(subRoot, messageType) {
|
|
603
|
+
messageType.forEach((key) => {
|
|
604
|
+
this.GetMessage(key).subscribe((e) => {
|
|
605
|
+
subRoot.next(e);
|
|
606
|
+
});
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
class UtilsLanguageConstants {
|
|
612
|
+
static VI = 'vi'; // Tiếng Việt
|
|
613
|
+
static EN = 'en'; // Tiếng Anh
|
|
614
|
+
static FR = 'fr'; // Tiếng Pháp
|
|
615
|
+
static DE = 'de'; // Tiếng Đức
|
|
616
|
+
static ES = 'es'; // Tiếng Tây Ban Nha
|
|
617
|
+
static ZH = 'zh'; // Tiếng Trung (Giản thể)
|
|
618
|
+
static ZH_TW = 'zh-TW'; // Tiếng Trung (Phồn thể)
|
|
619
|
+
static JA = 'ja'; // Tiếng Nhật
|
|
620
|
+
static KO = 'ko'; // Tiếng Hàn
|
|
621
|
+
static RU = 'ru'; // Tiếng Nga
|
|
622
|
+
static IT = 'it'; // Tiếng Ý
|
|
623
|
+
static PT = 'pt'; // Tiếng Bồ Đào Nha
|
|
624
|
+
static TH = 'th'; // Tiếng Thái
|
|
625
|
+
static ID = 'id'; // Tiếng Indonesia
|
|
626
|
+
static MS = 'ms'; // Tiếng Malaysia
|
|
627
|
+
static AR = 'ar'; // Tiếng Ả Rập
|
|
628
|
+
static HI = 'hi'; // Tiếng Hindi
|
|
629
|
+
static BN = 'bn'; // Tiếng Bengal
|
|
630
|
+
static TR = 'tr'; // Tiếng Thổ Nhĩ Kỳ
|
|
631
|
+
static NL = 'nl'; // Tiếng Hà Lan
|
|
632
|
+
static KM = 'km'; // Tiếng Khmer (Campuchia)
|
|
633
|
+
static LO = 'lo'; // Tiếng Lào
|
|
634
|
+
static MY = 'my'; // Tiếng Miến Điện (Myanmar)
|
|
635
|
+
static TL = 'tl'; // Tiếng Tagalog (Philippines)
|
|
636
|
+
static CEB = 'ceb'; // Tiếng Cebuano (Philippines)
|
|
637
|
+
static JV = 'jv'; // Tiếng Java (Indonesia)
|
|
638
|
+
static SU = 'su'; // Tiếng Sundanese (Indonesia)
|
|
639
|
+
// Ngôn ngữ mặc định
|
|
640
|
+
static defaultLang = UtilsLanguageConstants.VI;
|
|
641
|
+
// Danh sách các ngôn ngữ được hỗ trợ
|
|
642
|
+
static supportedLanguages = new Set([
|
|
643
|
+
UtilsLanguageConstants.VI,
|
|
644
|
+
UtilsLanguageConstants.EN,
|
|
645
|
+
UtilsLanguageConstants.FR,
|
|
646
|
+
UtilsLanguageConstants.DE,
|
|
647
|
+
UtilsLanguageConstants.ES,
|
|
648
|
+
UtilsLanguageConstants.ZH,
|
|
649
|
+
UtilsLanguageConstants.ZH_TW,
|
|
650
|
+
UtilsLanguageConstants.JA,
|
|
651
|
+
UtilsLanguageConstants.KO,
|
|
652
|
+
UtilsLanguageConstants.RU,
|
|
653
|
+
UtilsLanguageConstants.IT,
|
|
654
|
+
UtilsLanguageConstants.PT,
|
|
655
|
+
UtilsLanguageConstants.TH,
|
|
656
|
+
UtilsLanguageConstants.ID,
|
|
657
|
+
UtilsLanguageConstants.MS,
|
|
658
|
+
UtilsLanguageConstants.AR,
|
|
659
|
+
UtilsLanguageConstants.HI,
|
|
660
|
+
UtilsLanguageConstants.BN,
|
|
661
|
+
UtilsLanguageConstants.TR,
|
|
662
|
+
UtilsLanguageConstants.NL,
|
|
663
|
+
UtilsLanguageConstants.KM,
|
|
664
|
+
UtilsLanguageConstants.LO,
|
|
665
|
+
UtilsLanguageConstants.MY,
|
|
666
|
+
UtilsLanguageConstants.TL,
|
|
667
|
+
UtilsLanguageConstants.CEB,
|
|
668
|
+
UtilsLanguageConstants.JV,
|
|
669
|
+
UtilsLanguageConstants.SU,
|
|
670
|
+
]);
|
|
671
|
+
/**
|
|
672
|
+
* Kiểm tra xem ngôn ngữ đầu vào có được hỗ trợ không
|
|
673
|
+
* @param lang Mã ngôn ngữ cần kiểm tra
|
|
674
|
+
* @returns True nếu được hỗ trợ, False nếu không
|
|
675
|
+
*/
|
|
676
|
+
static isSupported(lang) {
|
|
677
|
+
return UtilsLanguageConstants.supportedLanguages.has(lang);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
const uuid = () => {
|
|
682
|
+
const timestamp = performance.now() * 1000;
|
|
683
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;:,.<>?/~';
|
|
684
|
+
let randomString = '';
|
|
685
|
+
for (let i = 0; i < 5; i++) {
|
|
686
|
+
randomString += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
687
|
+
}
|
|
688
|
+
const baseString = `${Math.floor(timestamp)}-${randomString}`;
|
|
689
|
+
const charArray = baseString.split('');
|
|
690
|
+
for (let i = charArray.length - 1; i > 0; i--) {
|
|
691
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
692
|
+
[charArray[i], charArray[j]] = [charArray[j], charArray[i]];
|
|
693
|
+
}
|
|
694
|
+
const shuffledString = charArray.join('');
|
|
695
|
+
return md5(shuffledString);
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
/* eslint-disable no-async-promise-executor */
|
|
699
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
700
|
+
class UtilsCache {
|
|
701
|
+
static CACHE_EXPIRE_TIME_DEFAULT = 5 * 60;
|
|
702
|
+
static CACHE_EXPIRE_NONE = -1;
|
|
703
|
+
static idService = uuid();
|
|
704
|
+
static typeKeyClearLocalStorage = 'LIBS_UI_CLEAR_LOCAL_STORAGE_EVENT';
|
|
705
|
+
static languageKeyCache = 'SR3xQKxHgffiCevPQRralg';
|
|
706
|
+
static listKeyKeepWhenClearALll = Array();
|
|
707
|
+
static initdEvent;
|
|
708
|
+
static storage;
|
|
709
|
+
static dbName = 'libs-ui-cache';
|
|
710
|
+
static itemIndexByKey = 'key';
|
|
711
|
+
static dbVersion = 1;
|
|
712
|
+
static db = null;
|
|
713
|
+
static init(config) {
|
|
714
|
+
if (this.initdEvent) {
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
this.initdEvent = true;
|
|
718
|
+
if (config.indexedDBName) {
|
|
719
|
+
this.dbName = config.indexedDBName;
|
|
720
|
+
}
|
|
721
|
+
if (config.typeKeyClearLocalStorage) {
|
|
722
|
+
this.typeKeyClearLocalStorage = config.typeKeyClearLocalStorage;
|
|
723
|
+
}
|
|
724
|
+
if (config.listKeyKeepWhenClearAll) {
|
|
725
|
+
this.listKeyKeepWhenClearALll = config.listKeyKeepWhenClearAll;
|
|
726
|
+
}
|
|
727
|
+
if (config.languageKeyCache) {
|
|
728
|
+
this.languageKeyCache = config.languageKeyCache;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
static setLang(lang) {
|
|
732
|
+
if (!UtilsLanguageConstants.isSupported(lang)) {
|
|
733
|
+
throw Error(`Language not support ${lang}`);
|
|
734
|
+
}
|
|
735
|
+
this.Set(this.languageKeyCache, lang, this.CACHE_EXPIRE_NONE);
|
|
736
|
+
}
|
|
737
|
+
static getLang() {
|
|
738
|
+
return this.Get(this.languageKeyCache, UtilsLanguageConstants.defaultLang);
|
|
739
|
+
}
|
|
740
|
+
static openDB() {
|
|
741
|
+
return new Promise((resolve) => {
|
|
742
|
+
const request = indexedDB.open(this.dbName, this.dbVersion);
|
|
743
|
+
request.onupgradeneeded = (event) => {
|
|
744
|
+
const db = event.target.result;
|
|
745
|
+
if (!db.objectStoreNames.contains(this.dbName)) {
|
|
746
|
+
const objectStore = db.createObjectStore(this.dbName, { keyPath: this.itemIndexByKey });
|
|
747
|
+
objectStore.createIndex(this.itemIndexByKey, this.itemIndexByKey, { unique: true });
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
request.onsuccess = () => {
|
|
751
|
+
this.db = request.result;
|
|
752
|
+
resolve(true);
|
|
753
|
+
};
|
|
754
|
+
request.onerror = (event) => {
|
|
755
|
+
console.trace('Error opening IndexedDB:', event);
|
|
756
|
+
resolve(false);
|
|
757
|
+
};
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
static async getObjectStore() {
|
|
761
|
+
if (!this.db) {
|
|
762
|
+
await this.openDB();
|
|
763
|
+
}
|
|
764
|
+
const transaction = this.db?.transaction([this.dbName], 'readwrite');
|
|
765
|
+
if (!transaction) {
|
|
766
|
+
return null;
|
|
767
|
+
}
|
|
768
|
+
return transaction.objectStore(this.dbName);
|
|
769
|
+
}
|
|
770
|
+
static get LocalStorage() {
|
|
771
|
+
try {
|
|
772
|
+
if (typeof window.localStorage !== 'undefined') {
|
|
773
|
+
const OS = getPlatFromBrowser();
|
|
774
|
+
if (OS.includes('Safari') && parseFloat(OS.split(' ').pop() || '0') >= 16) {
|
|
775
|
+
return this.getLocalStorageFakeOnSafari();
|
|
776
|
+
}
|
|
777
|
+
return localStorage;
|
|
778
|
+
}
|
|
779
|
+
return this.getLocalStorageFake();
|
|
780
|
+
}
|
|
781
|
+
catch (error) {
|
|
782
|
+
console.trace(`LocalStorage `, error);
|
|
783
|
+
return this.getLocalStorageFake();
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
static getLocalStorageFakeOnSafari() {
|
|
787
|
+
if (typeof window.localStorage !== 'undefined' && !this.storage && Object.keys(localStorage).length) {
|
|
788
|
+
this.storage = { ...localStorage };
|
|
789
|
+
}
|
|
790
|
+
return {
|
|
791
|
+
setItem: (key, value) => {
|
|
792
|
+
localStorage.setItem(key, value);
|
|
793
|
+
this.storage[key] = value;
|
|
794
|
+
},
|
|
795
|
+
getItem: (key) => {
|
|
796
|
+
const value = localStorage.getItem(key);
|
|
797
|
+
if (value) {
|
|
798
|
+
return value;
|
|
799
|
+
}
|
|
800
|
+
if (!this.storage || isNil(this.storage[key])) {
|
|
801
|
+
return null;
|
|
802
|
+
}
|
|
803
|
+
localStorage.setItem(key, this.storage[key]);
|
|
804
|
+
return this.storage?.[key];
|
|
805
|
+
},
|
|
806
|
+
removeItem: (key) => {
|
|
807
|
+
delete this.storage?.[key];
|
|
808
|
+
localStorage.removeItem(key);
|
|
809
|
+
},
|
|
810
|
+
clear: () => {
|
|
811
|
+
this.storage = {};
|
|
812
|
+
localStorage.clear();
|
|
813
|
+
},
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
static getLocalStorageFake() {
|
|
817
|
+
if (!this.storage) {
|
|
818
|
+
this.storage = {};
|
|
819
|
+
}
|
|
820
|
+
return {
|
|
821
|
+
setItem: (key, value) => {
|
|
822
|
+
this.storage[key] = value;
|
|
823
|
+
},
|
|
824
|
+
getItem: (key) => {
|
|
825
|
+
return this.storage?.[key];
|
|
826
|
+
},
|
|
827
|
+
removeItem: (key) => {
|
|
828
|
+
delete this.storage?.[key];
|
|
829
|
+
},
|
|
830
|
+
clear: () => {
|
|
831
|
+
this.storage = {};
|
|
832
|
+
},
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
static async GetAsync(key, default_value, isKeyMD5 = false) {
|
|
836
|
+
key = isKeyMD5 ? key : md5(key);
|
|
837
|
+
return new Promise(async (resolve) => {
|
|
838
|
+
const objectStore = await this.getObjectStore();
|
|
839
|
+
if (!objectStore) {
|
|
840
|
+
return resolve(default_value);
|
|
841
|
+
}
|
|
842
|
+
const request = objectStore.get(key);
|
|
843
|
+
request.onsuccess = () => {
|
|
844
|
+
if (!request.result) {
|
|
845
|
+
return resolve(default_value);
|
|
846
|
+
}
|
|
847
|
+
const data = JSON.parse(decrypt(request.result.value));
|
|
848
|
+
if (data.expire === this.CACHE_EXPIRE_NONE) {
|
|
849
|
+
return resolve(data.json);
|
|
850
|
+
}
|
|
851
|
+
const currentMillisecond = new Date().valueOf() / 1000;
|
|
852
|
+
if (data.expire < currentMillisecond) {
|
|
853
|
+
return resolve(default_value);
|
|
854
|
+
}
|
|
855
|
+
return resolve(data.json);
|
|
856
|
+
};
|
|
857
|
+
request.onerror = () => {
|
|
858
|
+
console.trace(`Get key ${key} Error, return default value: ${default_value}`);
|
|
859
|
+
return resolve(default_value);
|
|
860
|
+
};
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
static Get(key, default_value) {
|
|
864
|
+
// support cho những file không thể inject UtilsCache
|
|
865
|
+
if (!key) {
|
|
866
|
+
return this.GetDefaultValueBySpecificKey(key, default_value);
|
|
867
|
+
}
|
|
868
|
+
const cachedData = this.LocalStorage.getItem(key);
|
|
869
|
+
if (!cachedData) {
|
|
870
|
+
return this.GetDefaultValueBySpecificKey(key, default_value);
|
|
871
|
+
}
|
|
872
|
+
try {
|
|
873
|
+
const data = JSON.parse(decrypt(cachedData));
|
|
874
|
+
if (data.expire === this.CACHE_EXPIRE_NONE) {
|
|
875
|
+
return data.value ?? default_value;
|
|
876
|
+
}
|
|
877
|
+
const currentMillisecond = new Date().valueOf() / 1000;
|
|
878
|
+
if (data.expire < currentMillisecond) {
|
|
879
|
+
return this.GetDefaultValueBySpecificKey(key, default_value);
|
|
880
|
+
}
|
|
881
|
+
return data.value;
|
|
882
|
+
}
|
|
883
|
+
catch (error) {
|
|
884
|
+
console.trace(`Get key ${key} Error, return default value: ${default_value}`, error);
|
|
885
|
+
return this.GetDefaultValueBySpecificKey(key, default_value);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
static GetDefaultValueBySpecificKey(key, default_value) {
|
|
889
|
+
return default_value;
|
|
890
|
+
}
|
|
891
|
+
static async SetAsync(key, value, expireTimeBySecond = this.CACHE_EXPIRE_TIME_DEFAULT, isKeyMD5 = false) {
|
|
892
|
+
// support inject UtilsCache
|
|
893
|
+
return new Promise(async (resolve) => {
|
|
894
|
+
const objectStore = await this.getObjectStore();
|
|
895
|
+
key = isKeyMD5 ? key : md5(key);
|
|
896
|
+
try {
|
|
897
|
+
const currentMillisecond = expireTimeBySecond === this.CACHE_EXPIRE_NONE ? this.CACHE_EXPIRE_NONE : new Date().valueOf() / 1000 + expireTimeBySecond;
|
|
898
|
+
const data = {
|
|
899
|
+
value: encrypt(JSON.stringify({ json: value, expire: currentMillisecond })),
|
|
900
|
+
};
|
|
901
|
+
data[this.itemIndexByKey] = key;
|
|
902
|
+
if (!objectStore) {
|
|
903
|
+
console.trace(`Can not open object store`);
|
|
904
|
+
return resolve({ key, messageError: 'Can not open object store' });
|
|
905
|
+
}
|
|
906
|
+
const request = objectStore?.put(data);
|
|
907
|
+
request.onsuccess = () => {
|
|
908
|
+
console.log(`Set key ${key} Success`);
|
|
909
|
+
resolve(request.result);
|
|
910
|
+
};
|
|
911
|
+
request.onerror = (error) => {
|
|
912
|
+
console.trace(`Set key ${key} Error`);
|
|
913
|
+
resolve({ key, messageError: get(error, 'message') });
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
catch (error) {
|
|
917
|
+
console.trace(`Set key ${key} Error`);
|
|
918
|
+
resolve({ key, messageError: get(error, 'message') });
|
|
919
|
+
}
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
static Set(key, value, expireTimeBySecond = this.CACHE_EXPIRE_TIME_DEFAULT) {
|
|
923
|
+
// support cho những file không inject UtilsCache
|
|
924
|
+
const currentMillisecond = expireTimeBySecond === this.CACHE_EXPIRE_NONE ? this.CACHE_EXPIRE_NONE : new Date().valueOf() / 1000 + expireTimeBySecond;
|
|
925
|
+
const data = {
|
|
926
|
+
value: value,
|
|
927
|
+
expire: currentMillisecond,
|
|
928
|
+
};
|
|
929
|
+
try {
|
|
930
|
+
this.LocalStorage.setItem(key, encrypt(JSON.stringify(data)));
|
|
931
|
+
return true;
|
|
932
|
+
}
|
|
933
|
+
catch (error) {
|
|
934
|
+
console.trace(`Set key ${key} Error`, error);
|
|
935
|
+
return false;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
static async ClearAsync(key, isMD5 = false) {
|
|
939
|
+
return new Promise(async (resolve) => {
|
|
940
|
+
const objectStore = await this.getObjectStore();
|
|
941
|
+
if (!objectStore) {
|
|
942
|
+
return resolve();
|
|
943
|
+
}
|
|
944
|
+
const request = objectStore.delete(isMD5 ? key : md5(key));
|
|
945
|
+
request.onsuccess = () => {
|
|
946
|
+
resolve({ clearSuccess: true });
|
|
947
|
+
};
|
|
948
|
+
request.onerror = (event) => {
|
|
949
|
+
console.trace('Error deleting Key:', get(event.target.error, 'message'));
|
|
950
|
+
resolve({ messageError: get(event.target.error, 'message'), clearSuccess: false });
|
|
951
|
+
};
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
static Clear(key) {
|
|
955
|
+
if (key.includes('kc-callback-')) {
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
this.LocalStorage.removeItem(key);
|
|
959
|
+
}
|
|
960
|
+
static ClearAllAsync() {
|
|
961
|
+
return new Promise(async (resolve) => {
|
|
962
|
+
const objectStore = await this.getObjectStore();
|
|
963
|
+
if (!objectStore) {
|
|
964
|
+
return resolve();
|
|
965
|
+
}
|
|
966
|
+
const request = objectStore.clear();
|
|
967
|
+
request.onsuccess = () => {
|
|
968
|
+
console.log('clear all successfully');
|
|
969
|
+
resolve({ clearSuccess: true });
|
|
970
|
+
};
|
|
971
|
+
request.onerror = (event) => {
|
|
972
|
+
console.trace('Error deleting key:', get(event.target.error, 'message'));
|
|
973
|
+
resolve({ messageError: get(event.target.error, 'message'), clearSuccess: false });
|
|
974
|
+
};
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
static ClearAll() {
|
|
978
|
+
if (isEmbedFrame()) {
|
|
979
|
+
const data = {
|
|
980
|
+
type: this.typeKeyClearLocalStorage,
|
|
981
|
+
response: {
|
|
982
|
+
idEvent: this.idService,
|
|
983
|
+
},
|
|
984
|
+
};
|
|
985
|
+
UtilsCommunicateMicro.PostMessageToParent(data);
|
|
986
|
+
}
|
|
987
|
+
const keys = [...this.listKeyKeepWhenClearALll];
|
|
988
|
+
Object.keys(this.LocalStorage).forEach((key) => {
|
|
989
|
+
if (key.includes('kc-callback-')) {
|
|
990
|
+
keys.push(key);
|
|
991
|
+
}
|
|
992
|
+
});
|
|
993
|
+
const stores = this.GetDataByKeys(Array.from(keys));
|
|
994
|
+
this.LocalStorage.clear();
|
|
995
|
+
this.SetDataByKey(stores);
|
|
996
|
+
}
|
|
997
|
+
static GetDataByKeys(keys) {
|
|
998
|
+
const stores = new Map();
|
|
999
|
+
keys.forEach((key) => {
|
|
1000
|
+
if (key.includes('kc-callback-')) {
|
|
1001
|
+
stores.set(key, this.LocalStorage.getItem(key));
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
stores.set(key, this.Get(key));
|
|
1005
|
+
});
|
|
1006
|
+
return stores;
|
|
1007
|
+
}
|
|
1008
|
+
static SetDataByKey(stores) {
|
|
1009
|
+
stores.forEach((value, key) => {
|
|
1010
|
+
if (key.includes('kc-callback-')) {
|
|
1011
|
+
this.LocalStorage.setItem(key, value);
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
if (key === this.languageKeyCache) {
|
|
1015
|
+
return this.setLang(value);
|
|
1016
|
+
}
|
|
1017
|
+
this.Set(key, value, this.CACHE_EXPIRE_NONE);
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
static DeleteKeyStartWithAsync(keyCacheStartWith, isKeyMD5 = false) {
|
|
1021
|
+
return new Promise(async (resolve) => {
|
|
1022
|
+
const objectStore = await this.getObjectStore();
|
|
1023
|
+
if (!objectStore) {
|
|
1024
|
+
return resolve({});
|
|
1025
|
+
}
|
|
1026
|
+
// Lấy tất cả các keys từ index
|
|
1027
|
+
const request = objectStore.getAll();
|
|
1028
|
+
keyCacheStartWith = isKeyMD5 ? keyCacheStartWith : md5(keyCacheStartWith);
|
|
1029
|
+
request.onsuccess = (e) => {
|
|
1030
|
+
const data = e.target.result;
|
|
1031
|
+
if (!Array.isArray(data)) {
|
|
1032
|
+
return resolve({});
|
|
1033
|
+
}
|
|
1034
|
+
data.forEach((obj) => {
|
|
1035
|
+
if (obj[this.itemIndexByKey].startsWith(keyCacheStartWith)) {
|
|
1036
|
+
this.ClearAsync(obj[this.itemIndexByKey], true);
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
1039
|
+
return resolve({});
|
|
1040
|
+
};
|
|
1041
|
+
request.onerror = () => {
|
|
1042
|
+
return resolve({});
|
|
1043
|
+
};
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
static DeleteKeyStartWith(keyCache, isMD5 = false) {
|
|
1047
|
+
keyCache = isMD5 ? md5(keyCache) : keyCache;
|
|
1048
|
+
const keys = Object.keys(this.LocalStorage);
|
|
1049
|
+
if (!keys || !keys.length) {
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
keys.forEach((key) => {
|
|
1053
|
+
if (key.startsWith(keyCache)) {
|
|
1054
|
+
this.Clear(key);
|
|
1055
|
+
}
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
static DeleteDatabaseIndexDB(dbName) {
|
|
1059
|
+
return new Promise((resolve) => {
|
|
1060
|
+
dbName = (dbName || this.dbName);
|
|
1061
|
+
const request = indexedDB.deleteDatabase(dbName);
|
|
1062
|
+
request.onsuccess = () => {
|
|
1063
|
+
console.trace('Database deleted successfully');
|
|
1064
|
+
resolve({ deleteSuccess: true });
|
|
1065
|
+
};
|
|
1066
|
+
request.onerror = (event) => {
|
|
1067
|
+
const error = event.target.error;
|
|
1068
|
+
console.trace('Error deleting database:', error);
|
|
1069
|
+
resolve({ messageError: get(error || {}, 'message'), deleteSuccess: false });
|
|
1070
|
+
};
|
|
1071
|
+
request.onblocked = () => {
|
|
1072
|
+
console.trace('Delete request is blocked');
|
|
1073
|
+
resolve({ messageError: 'Delete request is blocked', deleteSuccess: false });
|
|
1074
|
+
};
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
dayjs.extend(localeData);
|
|
1080
|
+
dayjs.extend(updateLocale);
|
|
1081
|
+
dayjs.extend(utc);
|
|
1082
|
+
dayjs.extend(timezone);
|
|
1083
|
+
dayjs.extend(customParseFormat);
|
|
1084
|
+
let timeZoneSetup = 'Asia/Ho_Chi_Minh';
|
|
1085
|
+
const setDefaultTimeZone = (localeZone = timeZoneSetup) => {
|
|
1086
|
+
timeZoneSetup = localeZone;
|
|
1087
|
+
dayjs.tz.setDefault(localeZone);
|
|
1088
|
+
};
|
|
1089
|
+
let functionFormatDate = undefined;
|
|
1090
|
+
const updateFunctionFormatDate = (functionCustom) => {
|
|
1091
|
+
functionFormatDate = functionCustom;
|
|
1092
|
+
};
|
|
1093
|
+
/**
|
|
1094
|
+
* @description Lấy đối tượng dayjs theo config
|
|
1095
|
+
* @param config nếu không có config sẽ trả về đối tượng dayjs là thời gian hiện tại
|
|
1096
|
+
* @param config.date thời gian cần lấy
|
|
1097
|
+
* @param config.returnDayjsIfConfigDateNotExist true nếu muốn trả về đối tượng dayjs nếu config.date không có
|
|
1098
|
+
* @param config.utc true nếu muốn lấy thời gian UTC
|
|
1099
|
+
* @param config.formatOfDate định dạng thời gian đang được truyền vào
|
|
1100
|
+
*/
|
|
1101
|
+
const getDayjs = (config) => {
|
|
1102
|
+
// helper cast để tránh lặp lại kiểu điều kiện ở các return
|
|
1103
|
+
const out = (v) => v;
|
|
1104
|
+
if (!config) {
|
|
1105
|
+
return out(dayjs().tz());
|
|
1106
|
+
}
|
|
1107
|
+
config.date = !config.date && config.returnDayjsIfConfigDateNotExist ? dayjs().tz() : config.date;
|
|
1108
|
+
if (typeof config.date === 'number') {
|
|
1109
|
+
config.date = dayjs.unix(config.date);
|
|
1110
|
+
}
|
|
1111
|
+
if (!config.date) {
|
|
1112
|
+
return out(undefined);
|
|
1113
|
+
}
|
|
1114
|
+
let { date, utc, formatOfDate } = config;
|
|
1115
|
+
while (isSignal(date)) {
|
|
1116
|
+
date = date();
|
|
1117
|
+
}
|
|
1118
|
+
while (isSignal(utc)) {
|
|
1119
|
+
utc = utc();
|
|
1120
|
+
}
|
|
1121
|
+
while (isSignal(formatOfDate)) {
|
|
1122
|
+
formatOfDate = formatOfDate();
|
|
1123
|
+
}
|
|
1124
|
+
if (utc) {
|
|
1125
|
+
if (formatOfDate) {
|
|
1126
|
+
return out(dayjs(date, formatOfDate).utc());
|
|
1127
|
+
}
|
|
1128
|
+
const dateInputIsUTC = (dayjs.isDayjs(date) && date.isUTC()) || (typeof date === 'string' && date.includes('Z'));
|
|
1129
|
+
if (dateInputIsUTC) {
|
|
1130
|
+
return out(dayjs(date));
|
|
1131
|
+
}
|
|
1132
|
+
return out(dayjs(date).utc());
|
|
1133
|
+
}
|
|
1134
|
+
if (typeof date === 'string' && !date.includes('Z') && !date.includes('+')) {
|
|
1135
|
+
return out(formatOfDate ? dayjs.tz(date, formatOfDate, config.localeZone || timeZoneSetup) : dayjs.tz(date, config.localeZone || timeZoneSetup));
|
|
1136
|
+
}
|
|
1137
|
+
return out((formatOfDate ? dayjs(date, formatOfDate) : dayjs(date)).tz());
|
|
1138
|
+
};
|
|
1139
|
+
/**
|
|
1140
|
+
* @description Kiểm tra xem hai ngày có khác nhau không (khác ngày trong tuần)
|
|
1141
|
+
* @param date1 ngày thứ nhất cần so sánh
|
|
1142
|
+
* @param date2 ngày thứ hai cần so sánh
|
|
1143
|
+
* @returns true nếu hai ngày khác nhau, false nếu giống nhau hoặc không thể so sánh
|
|
1144
|
+
*/
|
|
1145
|
+
const isDifferenceDay = (date1, date2) => {
|
|
1146
|
+
if (isDifferenceMonth(date1, date2)) {
|
|
1147
|
+
return true;
|
|
1148
|
+
}
|
|
1149
|
+
const day1 = getDayjs({ date: date1 })?.day();
|
|
1150
|
+
const day2 = getDayjs({ date: date2 })?.day();
|
|
1151
|
+
return day1 !== day2;
|
|
1152
|
+
};
|
|
1153
|
+
/**
|
|
1154
|
+
* @description Kiểm tra xem hai ngày có khác tháng không
|
|
1155
|
+
* @param date1 ngày thứ nhất cần so sánh
|
|
1156
|
+
* @param date2 ngày thứ hai cần so sánh
|
|
1157
|
+
* @returns true nếu hai ngày khác tháng, false nếu cùng tháng hoặc không thể so sánh
|
|
1158
|
+
*/
|
|
1159
|
+
const isDifferenceMonth = (date1, date2) => {
|
|
1160
|
+
if (isDifferenceYear(date1, date2)) {
|
|
1161
|
+
return true;
|
|
1162
|
+
}
|
|
1163
|
+
const month1 = getDayjs({ date: date1 })?.month();
|
|
1164
|
+
const month2 = getDayjs({ date: date2 })?.month();
|
|
1165
|
+
return month1 !== month2;
|
|
1166
|
+
};
|
|
1167
|
+
/**
|
|
1168
|
+
* @description Kiểm tra xem hai ngày có khác năm không
|
|
1169
|
+
* @param date1 ngày thứ nhất cần so sánh
|
|
1170
|
+
* @param date2 ngày thứ hai cần so sánh
|
|
1171
|
+
* @returns true nếu hai ngày khác năm, false nếu cùng năm hoặc không thể so sánh
|
|
1172
|
+
*/
|
|
1173
|
+
const isDifferenceYear = (date1, date2) => {
|
|
1174
|
+
const year1 = getDayjs({ date: date1 })?.year();
|
|
1175
|
+
const year2 = getDayjs({ date: date2 })?.year();
|
|
1176
|
+
return year1 !== year2;
|
|
1177
|
+
};
|
|
1178
|
+
/**
|
|
1179
|
+
* @description Lấy ra chuỗi thời gian hiển thị theo định dạng và ngôn ngữ
|
|
1180
|
+
* @param date thời gian cần định dạng
|
|
1181
|
+
* @param format định dạng thời gian muốn lấy ra
|
|
1182
|
+
* @param lang lấy theo ngôn ngữ
|
|
1183
|
+
*/
|
|
1184
|
+
const formatDate = (date, formatOutput = 'YYYY/MM/DD HH:mm', lang, formatInput) => {
|
|
1185
|
+
if (!date) {
|
|
1186
|
+
return '';
|
|
1187
|
+
}
|
|
1188
|
+
lang = lang || UtilsCache.getLang();
|
|
1189
|
+
if (functionFormatDate) {
|
|
1190
|
+
return functionFormatDate(date, formatOutput, lang);
|
|
1191
|
+
}
|
|
1192
|
+
date = getDayjs({ date, formatOfDate: formatInput }).locale(lang);
|
|
1193
|
+
if (lang === 'vi') {
|
|
1194
|
+
dayjs.updateLocale('vi', {
|
|
1195
|
+
monthsShort: 'Thg 1_Thg 2_Thg 3_Thg 4_Thg 5_Thg 6_Thg 7_Thg 8_Thg 9_Thg 10_Thg 11_Thg 12'.split('_'),
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1198
|
+
return date.format(getFormatData(getTypeByFormat(formatOutput), lang)) || '';
|
|
1199
|
+
};
|
|
1200
|
+
const getTypeByFormat = (format) => {
|
|
1201
|
+
if (format === 'dm' || format === 'dmy' || format === 'dmy hm' || format === 'my') {
|
|
1202
|
+
return format;
|
|
1203
|
+
}
|
|
1204
|
+
if (format === 'DD/MM/YYYY' || format === 'YYYY-MM-DD' || format === 'dd/MM/yyyy' || format === 'dd-MM-yyyy' || format === 'dd/mm/yyyy') {
|
|
1205
|
+
return 'dmy';
|
|
1206
|
+
}
|
|
1207
|
+
if (format === 'MM-DD' || format === 'dd/MM' || format === 'dd/mm') {
|
|
1208
|
+
return 'dm';
|
|
1209
|
+
}
|
|
1210
|
+
if (format === 'M/YYYY' || format === 'YYYY-MM' || format === 'MM/yyyy') {
|
|
1211
|
+
return 'my';
|
|
1212
|
+
}
|
|
1213
|
+
if (format === 'YYYY/MM/DD hh:mm:ss' || format === 'dmy hms' || format === 'dd/mm/yyyy hh:mm:ss') {
|
|
1214
|
+
return 'dmy hms';
|
|
1215
|
+
}
|
|
1216
|
+
if (format === 'hh:mm' || format === 'HH:mm') {
|
|
1217
|
+
return 'hh:mm';
|
|
1218
|
+
}
|
|
1219
|
+
if (format === 'hh:mm A' || format === 'HH:mm A') {
|
|
1220
|
+
return 'hh:mm A';
|
|
1221
|
+
}
|
|
1222
|
+
return 'dmy hm';
|
|
1223
|
+
};
|
|
1224
|
+
const getFormatData = (type, lang) => {
|
|
1225
|
+
switch (type) {
|
|
1226
|
+
case 'dm':
|
|
1227
|
+
if (lang === 'vi') {
|
|
1228
|
+
return 'D MMM';
|
|
1229
|
+
}
|
|
1230
|
+
return 'MMM D';
|
|
1231
|
+
case 'dmy':
|
|
1232
|
+
if (lang === 'vi') {
|
|
1233
|
+
return 'D MMM, YYYY';
|
|
1234
|
+
}
|
|
1235
|
+
return 'MMM D, YYYY';
|
|
1236
|
+
case 'dmy hm':
|
|
1237
|
+
if (lang === 'vi') {
|
|
1238
|
+
return 'D MMM, YYYY HH:mm';
|
|
1239
|
+
}
|
|
1240
|
+
return 'MMM D, YYYY HH:mm';
|
|
1241
|
+
case 'my':
|
|
1242
|
+
if (lang === 'vi') {
|
|
1243
|
+
return 'MMM, YYYY';
|
|
1244
|
+
}
|
|
1245
|
+
return 'MMM YYYY ';
|
|
1246
|
+
case 'dmy hms':
|
|
1247
|
+
if (lang === 'vi') {
|
|
1248
|
+
return 'D MMM, YYYY HH:mm:ss';
|
|
1249
|
+
}
|
|
1250
|
+
return 'MMM D, YYYY HH:mm:ss';
|
|
1251
|
+
case 'hh:mm':
|
|
1252
|
+
case 'HH:mm':
|
|
1253
|
+
return 'HH:mm';
|
|
1254
|
+
case 'hh:mm A':
|
|
1255
|
+
case 'HH:mm A':
|
|
1256
|
+
return 'HH:mm A';
|
|
1257
|
+
}
|
|
1258
|
+
};
|
|
1259
|
+
|
|
1260
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1261
|
+
const UtilsHttpParamsRequestInstance = (options, instance) => {
|
|
1262
|
+
return new UtilsHttpParamsRequest(options, instance);
|
|
1263
|
+
};
|
|
1264
|
+
class UtilsHttpParamsRequest extends HttpParams {
|
|
1265
|
+
params = new HttpParams();
|
|
1266
|
+
constructor(options, instance) {
|
|
1267
|
+
super(options);
|
|
1268
|
+
if (!instance) {
|
|
1269
|
+
this.params = new HttpParams(options);
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
if (instance instanceof UtilsHttpParamsRequest) {
|
|
1273
|
+
this.params = instance.getInstance();
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
if (instance instanceof HttpParams) {
|
|
1277
|
+
this.params = instance;
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
getInstance() {
|
|
1282
|
+
return this.params;
|
|
1283
|
+
}
|
|
1284
|
+
setInstance(instance) {
|
|
1285
|
+
if (instance instanceof UtilsHttpParamsRequest) {
|
|
1286
|
+
this.params = instance.getInstance();
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
if (instance instanceof HttpParams) {
|
|
1290
|
+
this.params = instance;
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
set(param, value) {
|
|
1295
|
+
this.params = this.params.set(param, value);
|
|
1296
|
+
return this;
|
|
1297
|
+
}
|
|
1298
|
+
has(param) {
|
|
1299
|
+
return this.params.has(param);
|
|
1300
|
+
}
|
|
1301
|
+
get(param) {
|
|
1302
|
+
return this.params.get(param);
|
|
1303
|
+
}
|
|
1304
|
+
getAll(param) {
|
|
1305
|
+
return this.params.getAll(param);
|
|
1306
|
+
}
|
|
1307
|
+
keys() {
|
|
1308
|
+
return this.params.keys();
|
|
1309
|
+
}
|
|
1310
|
+
append(param, value) {
|
|
1311
|
+
this.params = this.params.append(param, value);
|
|
1312
|
+
return this;
|
|
1313
|
+
}
|
|
1314
|
+
appendAll(params) {
|
|
1315
|
+
this.params = this.params.appendAll(params);
|
|
1316
|
+
return this;
|
|
1317
|
+
}
|
|
1318
|
+
delete(param, value) {
|
|
1319
|
+
this.params = this.params.delete(param, value);
|
|
1320
|
+
return this;
|
|
1321
|
+
}
|
|
1322
|
+
toString() {
|
|
1323
|
+
return this.params.toString();
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
// Demo su dung GET_PATH_VARIABLE
|
|
1327
|
+
// interface Person {
|
|
1328
|
+
// name: string;
|
|
1329
|
+
// age: number;
|
|
1330
|
+
// location: string;
|
|
1331
|
+
// }
|
|
1332
|
+
// type c = GET_PATH_VARIABLE<Person, unknown>;
|
|
1333
|
+
// const a: c = {
|
|
1334
|
+
// "pathVariable-age": 1,
|
|
1335
|
+
// "pathVariable-location": '12',
|
|
1336
|
+
// "pathVariable-name": '124',
|
|
1337
|
+
// };
|
|
1338
|
+
|
|
1339
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1340
|
+
/**
|
|
1341
|
+
* Chuyển đổi một đối tượng hoặc giá trị thành signal
|
|
1342
|
+
* @param data Dữ liệu cần chuyển đổi thành signal
|
|
1343
|
+
* @param cloneDeepIfNotSignal Có thực hiện sao chép sâu dữ liệu trước khi chuyển đổi hay không nếu data không phải signal. Mặc định là true.
|
|
1344
|
+
* Đặt false nếu muốn giữ nguyên tham chiếu đến dữ liệu gốc.
|
|
1345
|
+
* Nếu muốn sao chép sâu đối tượng signal thì đặt cloneDeepIfNotSignal là true và acceptConvertObjectInnerWritableSignal là true.
|
|
1346
|
+
* @param isSignalPrimitiveType Có chuyển đổi các giá trị nguyên thủy (string, number, boolean) thành signal hay không. Mặc định là false.
|
|
1347
|
+
* Đặt true nếu muốn bọc các giá trị nguyên thủy trong signal.
|
|
1348
|
+
* @param acceptConvertObjectInnerWritableSignal Có tiếp tục tìm kiếm và chuyển đổi các đối tượng bên trong signal hay không. Mặc định là false.
|
|
1349
|
+
* Đặt true nếu muốn tìm và chuyển đổi các đối tượng bên trong signal hoặc chính nó thành signal mới.
|
|
1350
|
+
* @returns Dữ liệu đã được chuyển đổi theo kiểu T
|
|
1351
|
+
*/
|
|
1352
|
+
const convertObjectToSignal = (data, cloneDeepIfNotSignal = true, isSignalPrimitiveType = false, acceptConvertObjectInnerWritableSignal = false, seen = new WeakMap()) => {
|
|
1353
|
+
if ((data === null || typeof data !== 'object') && !isSignal(data)) {
|
|
1354
|
+
return (isSignalPrimitiveType ? signal(data) : data);
|
|
1355
|
+
}
|
|
1356
|
+
if (seen.has(data)) {
|
|
1357
|
+
return seen.get(data);
|
|
1358
|
+
}
|
|
1359
|
+
if (isSignal(data)) {
|
|
1360
|
+
if (!acceptConvertObjectInnerWritableSignal) {
|
|
1361
|
+
return data;
|
|
1362
|
+
}
|
|
1363
|
+
seen.set(data, convertObjectToSignal(data(), cloneDeepIfNotSignal, isSignalPrimitiveType, acceptConvertObjectInnerWritableSignal, seen));
|
|
1364
|
+
return seen.get(data);
|
|
1365
|
+
}
|
|
1366
|
+
if (isArray(data)) {
|
|
1367
|
+
seen.set(data, signal(data.map((item) => convertObjectToSignal(item, cloneDeepIfNotSignal, isSignalPrimitiveType, acceptConvertObjectInnerWritableSignal, seen))));
|
|
1368
|
+
return seen.get(data);
|
|
1369
|
+
}
|
|
1370
|
+
if (isMap(data)) {
|
|
1371
|
+
const mapCopy = new Map();
|
|
1372
|
+
Array.from(data.keys()).forEach((key) => {
|
|
1373
|
+
mapCopy.set(key, convertObjectToSignal(data.get(key), cloneDeepIfNotSignal, isSignalPrimitiveType, acceptConvertObjectInnerWritableSignal));
|
|
1374
|
+
});
|
|
1375
|
+
seen.set(data, signal(mapCopy));
|
|
1376
|
+
return seen.get(data);
|
|
1377
|
+
}
|
|
1378
|
+
// Bỏ qua các đối tượng async
|
|
1379
|
+
if (isAsyncObject(data)) {
|
|
1380
|
+
return data;
|
|
1381
|
+
}
|
|
1382
|
+
data = signal(cloneDeepIfNotSignal ? { ...data } : data);
|
|
1383
|
+
seen.set(data, data);
|
|
1384
|
+
for (const key in data()) {
|
|
1385
|
+
const value = data()[key];
|
|
1386
|
+
// Bỏ qua các đối tượng nguy hiểm và đặc biệt
|
|
1387
|
+
if (isReturnAsIsObject(value)) {
|
|
1388
|
+
continue;
|
|
1389
|
+
}
|
|
1390
|
+
if (Object.prototype.hasOwnProperty.call(data(), key)) {
|
|
1391
|
+
data()[key] = convertObjectToSignal(value, cloneDeepIfNotSignal, isSignalPrimitiveType, acceptConvertObjectInnerWritableSignal);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
return data;
|
|
1395
|
+
};
|
|
1396
|
+
const convertSignalToObject = (data, isCloneDeep = true, seen = new WeakMap()) => {
|
|
1397
|
+
let ignoreCheckSeenHasDataAfterWhile = false;
|
|
1398
|
+
while (isSignal(data) && !seen.has(data)) {
|
|
1399
|
+
const dataConvert = data();
|
|
1400
|
+
if (typeof dataConvert === 'object') {
|
|
1401
|
+
if (dataConvert === null || isReturnAsIsObject(dataConvert)) {
|
|
1402
|
+
return dataConvert;
|
|
1403
|
+
}
|
|
1404
|
+
seen.set(dataConvert, dataConvert);
|
|
1405
|
+
data = dataConvert;
|
|
1406
|
+
ignoreCheckSeenHasDataAfterWhile = true;
|
|
1407
|
+
break;
|
|
1408
|
+
}
|
|
1409
|
+
seen.set(data, dataConvert);
|
|
1410
|
+
data = dataConvert;
|
|
1411
|
+
}
|
|
1412
|
+
if (data === null || typeof data === 'function' || typeof data !== 'object' || isReturnAsIsObject(data)) {
|
|
1413
|
+
return data;
|
|
1414
|
+
}
|
|
1415
|
+
if (!ignoreCheckSeenHasDataAfterWhile && seen.has(data)) {
|
|
1416
|
+
return seen.get(data);
|
|
1417
|
+
}
|
|
1418
|
+
if (isArray(data)) {
|
|
1419
|
+
if (!isSignal(data[0])) {
|
|
1420
|
+
return data;
|
|
1421
|
+
}
|
|
1422
|
+
seen.set(data, data.map((item) => convertSignalToObject(isCloneDeep ? cloneDeep(item) : item, isCloneDeep, seen)));
|
|
1423
|
+
return seen.get(data);
|
|
1424
|
+
}
|
|
1425
|
+
if (isMap(data)) {
|
|
1426
|
+
const mapCopy = new Map();
|
|
1427
|
+
Array.from(data.keys()).forEach((key) => {
|
|
1428
|
+
mapCopy.set(key, convertSignalToObject(isCloneDeep ? cloneDeep(data.get(key)) : data.get(key), isCloneDeep));
|
|
1429
|
+
});
|
|
1430
|
+
seen.set(data, mapCopy);
|
|
1431
|
+
return seen.get(data);
|
|
1432
|
+
}
|
|
1433
|
+
if (isSet(data)) {
|
|
1434
|
+
const setCopy = new Set();
|
|
1435
|
+
Array.from(data.values()).forEach((value) => {
|
|
1436
|
+
setCopy.add(convertSignalToObject(isCloneDeep ? cloneDeep(value) : value, isCloneDeep));
|
|
1437
|
+
});
|
|
1438
|
+
seen.set(data, setCopy);
|
|
1439
|
+
return seen.get(data);
|
|
1440
|
+
}
|
|
1441
|
+
// Bỏ qua các đối tượng nguy hiểm và đặc biệt
|
|
1442
|
+
if (isReturnAsIsObject(data)) {
|
|
1443
|
+
return data;
|
|
1444
|
+
}
|
|
1445
|
+
const result = isCloneDeep ? {} : data;
|
|
1446
|
+
seen.set(data, result);
|
|
1447
|
+
for (const key in data) {
|
|
1448
|
+
const value = data[key];
|
|
1449
|
+
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
|
1450
|
+
// Bỏ qua các đối tượng nguy hiểm để tránh maximum call stack
|
|
1451
|
+
if (isReturnAsIsObject(value)) {
|
|
1452
|
+
result[key] = value;
|
|
1453
|
+
continue;
|
|
1454
|
+
}
|
|
1455
|
+
result[key] = convertSignalToObject(isCloneDeep ? cloneDeep(value) : value, isCloneDeep);
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
return result;
|
|
1459
|
+
};
|
|
1460
|
+
const unwrapSignal = (data) => {
|
|
1461
|
+
while (isSignal(data)) {
|
|
1462
|
+
data = data();
|
|
1463
|
+
}
|
|
1464
|
+
return data;
|
|
1465
|
+
};
|
|
1466
|
+
|
|
1467
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1468
|
+
/**Các hàm tương tự thư viện lodash */
|
|
1469
|
+
/**
|
|
1470
|
+
* Kiểm tra xem một giá trị có phải là null hoặc undefined hay không
|
|
1471
|
+
* @param value Giá trị cần kiểm tra
|
|
1472
|
+
* @returns true nếu giá trị là null hoặc undefined, false nếu không
|
|
1473
|
+
* @example
|
|
1474
|
+
* isNil(null); // true
|
|
1475
|
+
* isNil(undefined); // true
|
|
1476
|
+
* isNil(0); // false
|
|
1477
|
+
* isNil('hello'); // false
|
|
1478
|
+
*/
|
|
1479
|
+
const isNil = (value, options) => {
|
|
1480
|
+
if (!options?.ignoreUnWrapSignal) {
|
|
1481
|
+
value = unwrapSignal(value);
|
|
1482
|
+
}
|
|
1483
|
+
return value === null || value === undefined;
|
|
1484
|
+
};
|
|
1485
|
+
/**
|
|
1486
|
+
* Kiểm tra xem một giá trị có phải là rỗng hay không
|
|
1487
|
+
* @param value Giá trị cần kiểm tra
|
|
1488
|
+
* @returns true nếu giá trị là null, rỗng hoặc undefined, false nếu không
|
|
1489
|
+
* @example
|
|
1490
|
+
* isEmpty(null); // true
|
|
1491
|
+
* isEmpty(''); // true
|
|
1492
|
+
* isEmpty(undefined); // true
|
|
1493
|
+
* isEmpty({}); // true
|
|
1494
|
+
* isEmpty([]); // true
|
|
1495
|
+
* isEmpty([1, 2, 3]); // false
|
|
1496
|
+
* isEmpty({ a: 1 }); // false
|
|
1497
|
+
*/
|
|
1498
|
+
const isEmpty = (value, options) => {
|
|
1499
|
+
if (!options?.ignoreUnWrapSignal) {
|
|
1500
|
+
value = unwrapSignal(value);
|
|
1501
|
+
}
|
|
1502
|
+
if (options?.ignoreCheckString && value === '') {
|
|
1503
|
+
return false;
|
|
1504
|
+
}
|
|
1505
|
+
if (options?.ignoreCheckTypePrimitive) {
|
|
1506
|
+
return typeof value === 'object' && isEmptyTypeObject(value);
|
|
1507
|
+
}
|
|
1508
|
+
return isEmptyTypeObject(value);
|
|
1509
|
+
};
|
|
1510
|
+
/**
|
|
1511
|
+
* Kiểm tra xem một giá trị có phải là object rỗng hay không
|
|
1512
|
+
* @param value Giá trị cần kiểm tra
|
|
1513
|
+
* @returns true nếu giá trị là object rỗng, false nếu không
|
|
1514
|
+
* @example
|
|
1515
|
+
* isEmptyTypeObject({}); // true
|
|
1516
|
+
* isEmptyTypeObject({ a: 1 }); // false
|
|
1517
|
+
*/
|
|
1518
|
+
const isEmptyTypeObject = (value) => {
|
|
1519
|
+
try {
|
|
1520
|
+
if (isNil(value, { ignoreUnWrapSignal: true }) || value === '') {
|
|
1521
|
+
return true;
|
|
1522
|
+
}
|
|
1523
|
+
if (typeof value !== 'object') {
|
|
1524
|
+
return false;
|
|
1525
|
+
}
|
|
1526
|
+
if (Array.isArray(value)) {
|
|
1527
|
+
return value.length === 0;
|
|
1528
|
+
}
|
|
1529
|
+
return Object.keys(value).length === 0;
|
|
1530
|
+
}
|
|
1531
|
+
catch (error) {
|
|
1532
|
+
console.error(error);
|
|
1533
|
+
return false;
|
|
1534
|
+
}
|
|
1535
|
+
};
|
|
1536
|
+
/**
|
|
1537
|
+
* Kiểm tra xem một giá trị có phải là null, rỗng, undefined hoặc 0 hay không
|
|
1538
|
+
* @param value Giá trị cần kiểm tra
|
|
1539
|
+
* @param options Cấu hình tùy chọn
|
|
1540
|
+
* @param options.ignoreZero Nếu true, sẽ không kiểm tra giá trị 0
|
|
1541
|
+
* @returns true nếu giá trị là null, rỗng, undefined hoặc 0, false nếu không
|
|
1542
|
+
* @example
|
|
1543
|
+
* isTruthy(null); // false
|
|
1544
|
+
* isTruthy(''); // false
|
|
1545
|
+
* isTruthy(undefined); // false
|
|
1546
|
+
* isTruthy(0); // false
|
|
1547
|
+
* isTruthy({}); // true
|
|
1548
|
+
* isTruthy(0, { ignoreZero: true }); // true
|
|
1549
|
+
*/
|
|
1550
|
+
const isTruthy = (value, options) => {
|
|
1551
|
+
return !isFalsy(value, options);
|
|
1552
|
+
};
|
|
1553
|
+
/**
|
|
1554
|
+
* Kiểm tra xem một giá trị có phải là null, rỗng, undefined hoặc 0 hay không
|
|
1555
|
+
* @param value Giá trị cần kiểm tra
|
|
1556
|
+
* @param options Cấu hình tùy chọn
|
|
1557
|
+
* @param options.ignoreZero Nếu true, sẽ không kiểm tra giá trị 0
|
|
1558
|
+
* @returns true nếu giá trị là null, rỗng, undefined hoặc 0, false nếu không
|
|
1559
|
+
* @example
|
|
1560
|
+
* isFalsy(null); // true
|
|
1561
|
+
* isFalsy(''); // true
|
|
1562
|
+
* isFalsy(undefined); // true
|
|
1563
|
+
* isFalsy(0); // true
|
|
1564
|
+
* isFalsy({}); // false
|
|
1565
|
+
* isFalsy(0, { ignoreZero: true }); // false
|
|
1566
|
+
*/
|
|
1567
|
+
const isFalsy = (value, options) => {
|
|
1568
|
+
if (!options?.ignoreUnWrapSignal) {
|
|
1569
|
+
value = unwrapSignal(value);
|
|
1570
|
+
}
|
|
1571
|
+
if (options?.ignoreZero && value === 0) {
|
|
1572
|
+
return false;
|
|
1573
|
+
}
|
|
1574
|
+
if (options?.ignoreCheckString && value === '') {
|
|
1575
|
+
return false;
|
|
1576
|
+
}
|
|
1577
|
+
return !value;
|
|
1578
|
+
};
|
|
1579
|
+
/**
|
|
1580
|
+
* Loại bỏ các thuộc tính của đối tượng dựa trên một hàm điều kiện
|
|
1581
|
+
* @param objData Đối tượng cần xử lý
|
|
1582
|
+
* @param predicate Hàm điều kiện để kiểm tra giá trị của thuộc tính. Nếu hàm trả về true thì thuộc tính đó sẽ bị loại bỏ
|
|
1583
|
+
* @returns Đối tượng mới sau khi đã loại bỏ các thuộc tính thỏa mãn điều kiện
|
|
1584
|
+
* @example
|
|
1585
|
+
* const obj = { a: 1, b: null, c: 3, d: undefined };
|
|
1586
|
+
* omitBy(obj, isNil); // { a: 1, c: 3 }
|
|
1587
|
+
*/
|
|
1588
|
+
const omitBy = (objData, predicate) => {
|
|
1589
|
+
if (!objData || typeof objData !== 'object' || Array.isArray(objData)) {
|
|
1590
|
+
return objData;
|
|
1591
|
+
}
|
|
1592
|
+
const newObj = {};
|
|
1593
|
+
Object.keys(objData).forEach((key) => {
|
|
1594
|
+
const valueOfKey = get(objData, key);
|
|
1595
|
+
if (!predicate(valueOfKey)) {
|
|
1596
|
+
set(newObj, key, valueOfKey);
|
|
1597
|
+
}
|
|
1598
|
+
});
|
|
1599
|
+
return newObj;
|
|
1600
|
+
};
|
|
1601
|
+
/**
|
|
1602
|
+
* Lấy giá trị từ đối tượng theo đường dẫn chỉ định
|
|
1603
|
+
*
|
|
1604
|
+
* Hàm này giúp bạn truy cập vào các thuộc tính sâu bên trong một đối tượng một cách an toàn,
|
|
1605
|
+
* tránh lỗi khi thuộc tính không tồn tại.
|
|
1606
|
+
*
|
|
1607
|
+
* @param obj Đối tượng nguồn cần lấy giá trị
|
|
1608
|
+
* @param path Đường dẫn đến thuộc tính cần lấy. Có thể là:
|
|
1609
|
+
* - Chuỗi: 'user.profile.name' hoặc 'items[0].title'
|
|
1610
|
+
* - Mảng: ['user', 'profile', 'name'] hoặc ['items', '0', 'title']
|
|
1611
|
+
* - Chuỗi rỗng '': trả về chính đối tượng gốc
|
|
1612
|
+
* @param defaultValue Giá trị mặc định trả về khi không tìm thấy thuộc tính (mặc định: undefined)
|
|
1613
|
+
* @param keepLastValueIfSignal Có giữ nguyên signal cuối cùng hay không (mặc định: false - sẽ gọi signal())
|
|
1614
|
+
* @returns Giá trị tìm được hoặc giá trị mặc định
|
|
1615
|
+
*
|
|
1616
|
+
* @example
|
|
1617
|
+
* // Ví dụ cơ bản
|
|
1618
|
+
* const user = { name: 'John', age: 30 };
|
|
1619
|
+
* get(user, 'name'); // 'John'
|
|
1620
|
+
* get(user, 'email'); // undefined
|
|
1621
|
+
* get(user, 'email', 'no-email'); // 'no-email'
|
|
1622
|
+
*
|
|
1623
|
+
* @example
|
|
1624
|
+
* // Truyền path rỗng - trả về chính đối tượng gốc
|
|
1625
|
+
* const data = { name: 'Alice', age: 25 };
|
|
1626
|
+
* get(data, ''); // { name: 'Alice', age: 25 } (chính đối tượng data)
|
|
1627
|
+
* get(data, '', 'default'); // { name: 'Alice', age: 25 } (bỏ qua defaultValue)
|
|
1628
|
+
*
|
|
1629
|
+
* @example
|
|
1630
|
+
* // Truy cập thuộc tính sâu
|
|
1631
|
+
* const data = {
|
|
1632
|
+
* user: {
|
|
1633
|
+
* profile: {
|
|
1634
|
+
* name: 'Alice',
|
|
1635
|
+
* settings: { theme: 'dark' }
|
|
1636
|
+
* }
|
|
1637
|
+
* }
|
|
1638
|
+
* };
|
|
1639
|
+
* get(data, 'user.profile.name'); // 'Alice'
|
|
1640
|
+
* get(data, 'user.profile.settings.theme'); // 'dark'
|
|
1641
|
+
* get(data, 'user.profile.avatar', 'default.jpg'); // 'default.jpg'
|
|
1642
|
+
*
|
|
1643
|
+
* @example
|
|
1644
|
+
* // Truy cập mảng
|
|
1645
|
+
* const items = [
|
|
1646
|
+
* { name: 'Item 1', price: 100 },
|
|
1647
|
+
* { name: 'Item 2', price: 200 }
|
|
1648
|
+
* ];
|
|
1649
|
+
* get(items, '[0].name'); // 'Item 1'
|
|
1650
|
+
* get(items, '[1].name'); // 'Item 2'
|
|
1651
|
+
* get(items, '[2].name', 'Not found'); // 'Not found'
|
|
1652
|
+
*
|
|
1653
|
+
* @example
|
|
1654
|
+
* // Sử dụng với mảng path
|
|
1655
|
+
* const nested = { a: { b: { c: 'deep value' } } };
|
|
1656
|
+
* get(nested, ['a', 'b', 'c']); // 'deep value'
|
|
1657
|
+
* get(nested, ['a', 'b', 'd'], 'default'); // 'default'
|
|
1658
|
+
*
|
|
1659
|
+
* @example
|
|
1660
|
+
* // Trường hợp đặc biệt
|
|
1661
|
+
* get(null, 'any.path'); // undefined
|
|
1662
|
+
* get(undefined, 'any.path', 'fallback'); // 'fallback'
|
|
1663
|
+
*/
|
|
1664
|
+
const get = (obj, path, defaultValue, keepLastValueIfSignal) => {
|
|
1665
|
+
// helper cast để tránh lặp lại kiểu điều kiện ở các return
|
|
1666
|
+
const out = (v) => v;
|
|
1667
|
+
if (isNil(obj)) {
|
|
1668
|
+
return out(defaultValue);
|
|
1669
|
+
}
|
|
1670
|
+
obj = unwrapSignal(obj);
|
|
1671
|
+
if (path === '') {
|
|
1672
|
+
return out(obj);
|
|
1673
|
+
}
|
|
1674
|
+
if (obj instanceof HttpParams) {
|
|
1675
|
+
return out(obj.get(`${path}`) ?? defaultValue);
|
|
1676
|
+
}
|
|
1677
|
+
if (obj instanceof DOMRect) {
|
|
1678
|
+
return out(obj[path]);
|
|
1679
|
+
}
|
|
1680
|
+
const paths = Array.isArray(path)
|
|
1681
|
+
? path
|
|
1682
|
+
: path
|
|
1683
|
+
.replace(/\[(\d+)]/g, '.$1')
|
|
1684
|
+
.split('.')
|
|
1685
|
+
.filter((key) => key);
|
|
1686
|
+
for (const index in paths) {
|
|
1687
|
+
const key = paths[index];
|
|
1688
|
+
if (obj instanceof CSSStyleDeclaration) {
|
|
1689
|
+
obj = obj[key];
|
|
1690
|
+
continue;
|
|
1691
|
+
}
|
|
1692
|
+
if (obj instanceof HTMLElement) {
|
|
1693
|
+
obj = obj[key];
|
|
1694
|
+
continue;
|
|
1695
|
+
}
|
|
1696
|
+
if (isNil(obj) || !Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
1697
|
+
return out(defaultValue);
|
|
1698
|
+
}
|
|
1699
|
+
const val = isSignal(obj[key]) && !keepLastValueIfSignal ? obj[key]() : obj[key];
|
|
1700
|
+
obj = val;
|
|
1701
|
+
}
|
|
1702
|
+
return out(obj ?? defaultValue);
|
|
1703
|
+
};
|
|
1704
|
+
/**
|
|
1705
|
+
* Thiết lập giá trị cho một thuộc tính trong đối tượng theo đường dẫn chỉ định
|
|
1706
|
+
* @param obj Đối tượng cần thiết lập giá trị
|
|
1707
|
+
* @param path Đường dẫn đến thuộc tính, có thể là chuỗi (vd: 'a.b.c') hoặc mảng (vd: ['a', 'b', 'c'])
|
|
1708
|
+
* @param value Giá trị cần thiết lập
|
|
1709
|
+
* @returns Đối tượng sau khi đã thiết lập giá trị
|
|
1710
|
+
* @throws Error nếu tham số đầu tiên không phải là đối tượng
|
|
1711
|
+
* @example
|
|
1712
|
+
* const obj = { a: { b: 1 } };
|
|
1713
|
+
* set(obj, 'a.b', 2); // { a: { b: 2 } }
|
|
1714
|
+
*/
|
|
1715
|
+
const set = (obj, path, value, options) => {
|
|
1716
|
+
if (!obj || (typeof obj !== 'object' && !isSignal(obj)) || (isSignal(obj) && typeof obj() !== 'object')) {
|
|
1717
|
+
throw new Error('The first argument must be an object');
|
|
1718
|
+
}
|
|
1719
|
+
if (obj instanceof HttpParams) {
|
|
1720
|
+
return obj.set(`${path}`, value);
|
|
1721
|
+
}
|
|
1722
|
+
const pathArray = Array.isArray(path)
|
|
1723
|
+
? path
|
|
1724
|
+
: path
|
|
1725
|
+
.replace(/\[(\d+)]/g, '.[$1]')
|
|
1726
|
+
.split('.')
|
|
1727
|
+
.filter((key) => key);
|
|
1728
|
+
let currentObjectByKey = isSignal(obj) ? obj() : obj;
|
|
1729
|
+
let preObjectByKey = obj;
|
|
1730
|
+
pathArray.forEach((key, index) => {
|
|
1731
|
+
if (index < pathArray.length - 1) {
|
|
1732
|
+
if (!(key in currentObjectByKey) || (typeof currentObjectByKey[key] !== 'object' && !isSignal(currentObjectByKey[key]))) {
|
|
1733
|
+
const nextKey = pathArray[index + 1];
|
|
1734
|
+
key = key.replace(/\[(\d+)]/g, '$1');
|
|
1735
|
+
if (isNil(currentObjectByKey[key])) {
|
|
1736
|
+
currentObjectByKey[key] = /\[(\d+)]/g.test(nextKey) ? (options?.valueDefaultPathArrayUndefined ?? []) : (options?.valueDefaultPathObjectUndefined ?? {});
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
currentObjectByKey = key ? currentObjectByKey[key] : currentObjectByKey;
|
|
1740
|
+
preObjectByKey = currentObjectByKey;
|
|
1741
|
+
currentObjectByKey = isSignal(currentObjectByKey) ? currentObjectByKey() : currentObjectByKey;
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
if (typeof currentObjectByKey !== 'object') {
|
|
1745
|
+
return;
|
|
1746
|
+
}
|
|
1747
|
+
// Gán giá trị ở cuối đường dẫn
|
|
1748
|
+
key = key.replace(/\[(\d+)]/g, '$1');
|
|
1749
|
+
const valueOfKey = currentObjectByKey[key];
|
|
1750
|
+
const valueOfKeyIsSignal = isSignal(valueOfKey);
|
|
1751
|
+
if (valueOfKeyIsSignal && (typeof valueOfKey() !== 'object' || valueOfKey() === null)) {
|
|
1752
|
+
valueOfKey.set(isSignal(value) ? value() : value);
|
|
1753
|
+
return;
|
|
1754
|
+
}
|
|
1755
|
+
if (isSignal(preObjectByKey)) {
|
|
1756
|
+
preObjectByKey.update((data) => {
|
|
1757
|
+
const dataOfKey = data[key];
|
|
1758
|
+
if (isNil(dataOfKey) || !valueOfKeyIsSignal) {
|
|
1759
|
+
data[key] = value;
|
|
1760
|
+
}
|
|
1761
|
+
if (valueOfKeyIsSignal) {
|
|
1762
|
+
valueOfKey.set(isSignal(value) ? value() : value);
|
|
1763
|
+
}
|
|
1764
|
+
if (Array.isArray(data)) {
|
|
1765
|
+
return [...data];
|
|
1766
|
+
}
|
|
1767
|
+
return { ...data };
|
|
1768
|
+
});
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1771
|
+
if (valueOfKeyIsSignal) {
|
|
1772
|
+
valueOfKey.set(isSignal(value) ? value() : value);
|
|
1773
|
+
return;
|
|
1774
|
+
}
|
|
1775
|
+
currentObjectByKey[key] = value;
|
|
1776
|
+
});
|
|
1777
|
+
return obj;
|
|
1778
|
+
};
|
|
1779
|
+
/**
|
|
1780
|
+
* Tạo một bản sao sâu của một đối tượng hoặc giá trị bất kỳ
|
|
1781
|
+
* @param data Dữ liệu cần sao chép
|
|
1782
|
+
* @param options Tùy chọn cấu hình
|
|
1783
|
+
* @param options.ignoreSignal Nếu true, sẽ không sao chép các signal mà trả về signal gốc
|
|
1784
|
+
* @param seen WeakMap để theo dõi các tham chiếu đã được sao chép, tránh lặp vô hạn với các tham chiếu vòng
|
|
1785
|
+
* @returns Bản sao sâu của dữ liệu đầu vào
|
|
1786
|
+
* @example
|
|
1787
|
+
* const obj = {
|
|
1788
|
+
* a: 1,
|
|
1789
|
+
* b: { c: 2 },
|
|
1790
|
+
* d: [1, 2, 3]
|
|
1791
|
+
* };
|
|
1792
|
+
* const clone = cloneDeep(obj);
|
|
1793
|
+
* // clone là một bản sao hoàn toàn độc lập của obj
|
|
1794
|
+
*/
|
|
1795
|
+
const cloneDeep = (data, options = { ignoreSignal: false }, seen = new WeakMap()) => {
|
|
1796
|
+
if (data === null || (typeof data !== 'object' && !isSignal(data)) || isDangerousObject(data)) {
|
|
1797
|
+
return data;
|
|
1798
|
+
}
|
|
1799
|
+
if (seen.has(data)) {
|
|
1800
|
+
return seen.get(data);
|
|
1801
|
+
}
|
|
1802
|
+
if (data instanceof HttpParams) {
|
|
1803
|
+
return new UtilsHttpParamsRequest(undefined, data);
|
|
1804
|
+
}
|
|
1805
|
+
if (data instanceof Date) {
|
|
1806
|
+
return new Date(data.getTime());
|
|
1807
|
+
}
|
|
1808
|
+
if (isDayjsObject(data)) {
|
|
1809
|
+
return getDayjs({ date: data.valueOf() });
|
|
1810
|
+
}
|
|
1811
|
+
if (isRegExp(data)) {
|
|
1812
|
+
return new RegExp(data.source, data.flags);
|
|
1813
|
+
}
|
|
1814
|
+
if (isMap(data)) {
|
|
1815
|
+
const mapCopy = new Map();
|
|
1816
|
+
seen.set(data, mapCopy);
|
|
1817
|
+
data.forEach((val, key) => {
|
|
1818
|
+
mapCopy.set(cloneDeep(key, options, seen), cloneDeep(val, options, seen));
|
|
1819
|
+
});
|
|
1820
|
+
return mapCopy;
|
|
1821
|
+
}
|
|
1822
|
+
if (isSet(data)) {
|
|
1823
|
+
const setCopy = new Set();
|
|
1824
|
+
seen.set(data, setCopy);
|
|
1825
|
+
data.forEach((val) => {
|
|
1826
|
+
setCopy.add(cloneDeep(val, options, seen));
|
|
1827
|
+
});
|
|
1828
|
+
return setCopy;
|
|
1829
|
+
}
|
|
1830
|
+
if (isArray(data)) {
|
|
1831
|
+
seen.set(data, data.map((item) => cloneDeep(item, options, seen)));
|
|
1832
|
+
return seen.get(data);
|
|
1833
|
+
}
|
|
1834
|
+
if (isReturnAsIsObject(data)) {
|
|
1835
|
+
return data;
|
|
1836
|
+
}
|
|
1837
|
+
if (isSignal(data)) {
|
|
1838
|
+
if (options?.ignoreSignal) {
|
|
1839
|
+
return data;
|
|
1840
|
+
}
|
|
1841
|
+
seen.set(data, signal(cloneDeep(data(), options, seen)));
|
|
1842
|
+
return seen.get(data);
|
|
1843
|
+
}
|
|
1844
|
+
const result = {};
|
|
1845
|
+
seen.set(data, result);
|
|
1846
|
+
for (const key in data) {
|
|
1847
|
+
const value = data[key];
|
|
1848
|
+
if (value instanceof HttpParams) {
|
|
1849
|
+
result[key] = new UtilsHttpParamsRequest(undefined, value);
|
|
1850
|
+
continue;
|
|
1851
|
+
}
|
|
1852
|
+
if (value instanceof Date) {
|
|
1853
|
+
result[key] = new Date(value.getTime());
|
|
1854
|
+
continue;
|
|
1855
|
+
}
|
|
1856
|
+
if (dayjs.isDayjs(value)) {
|
|
1857
|
+
result[key] = getDayjs({ date: value.valueOf() });
|
|
1858
|
+
continue;
|
|
1859
|
+
}
|
|
1860
|
+
if (isRegExp(data)) {
|
|
1861
|
+
result[key] = new RegExp(value.source, value.flags);
|
|
1862
|
+
continue;
|
|
1863
|
+
}
|
|
1864
|
+
if (isReturnAsIsObject(data)) {
|
|
1865
|
+
result[key] = value;
|
|
1866
|
+
continue;
|
|
1867
|
+
}
|
|
1868
|
+
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
|
1869
|
+
result[key] = cloneDeep(value, options, seen);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
return result;
|
|
1873
|
+
};
|
|
1874
|
+
/**
|
|
1875
|
+
* Chuyển đổi một mảng các đối tượng thành một đối tượng với khóa được chỉ định
|
|
1876
|
+
* @param data Mảng các đối tượng cần chuyển đổi
|
|
1877
|
+
* @param key Tên thuộc tính được sử dụng làm khóa trong đối tượng kết quả
|
|
1878
|
+
* @returns Đối tượng với các giá trị từ mảng được đánh key theo thuộc tính đã chỉ định
|
|
1879
|
+
* @example
|
|
1880
|
+
* const data = [
|
|
1881
|
+
* { id: 1, name: 'John' },
|
|
1882
|
+
* { id: 2, name: 'Jane' }
|
|
1883
|
+
* ];
|
|
1884
|
+
* keyBy(data, 'id');
|
|
1885
|
+
* // Kết quả: {
|
|
1886
|
+
* // '1': { id: 1, name: 'John' },
|
|
1887
|
+
* // '2': { id: 2, name: 'Jane' }
|
|
1888
|
+
* // }
|
|
1889
|
+
*/
|
|
1890
|
+
const keyBy = (data, key) => {
|
|
1891
|
+
if (!data || !data.length || !key) {
|
|
1892
|
+
return {};
|
|
1893
|
+
}
|
|
1894
|
+
return data.reduce((dir, nextItem) => {
|
|
1895
|
+
const valueOfKey = get(nextItem, key);
|
|
1896
|
+
if (typeof valueOfKey !== 'string' && typeof valueOfKey !== 'number' && typeof valueOfKey !== 'boolean') {
|
|
1897
|
+
return dir;
|
|
1898
|
+
}
|
|
1899
|
+
if (!Object.keys(dir).includes(`${valueOfKey}`)) {
|
|
1900
|
+
dir[`${valueOfKey}`] = nextItem;
|
|
1901
|
+
}
|
|
1902
|
+
return dir;
|
|
1903
|
+
}, {});
|
|
1904
|
+
};
|
|
1905
|
+
/**
|
|
1906
|
+
* Nhóm các đối tượng trong một mảng thành các nhóm dựa trên một thuộc tính cụ thể
|
|
1907
|
+
* @param data Mảng các đối tượng cần nhóm
|
|
1908
|
+
* @param key Tên thuộc tính được sử dụng làm khóa nhóm
|
|
1909
|
+
* @returns Đối tượng với các giá trị từ mảng được nhóm theo thuộc tính đã chỉ định
|
|
1910
|
+
* @example
|
|
1911
|
+
* const data = [
|
|
1912
|
+
* { id: 1, name: 'John' },
|
|
1913
|
+
* { id: 2, name: 'Jane' },
|
|
1914
|
+
* { id: 1, name: 'John' }
|
|
1915
|
+
* ];
|
|
1916
|
+
* groupBy(data, 'id');
|
|
1917
|
+
* // Kết quả: {
|
|
1918
|
+
* // '1': [
|
|
1919
|
+
* // { id: 1, name: 'John' },
|
|
1920
|
+
* // { id: 1, name: 'John' }
|
|
1921
|
+
* // ],
|
|
1922
|
+
* // '2': [
|
|
1923
|
+
* // { id: 2, name: 'Jane' }
|
|
1924
|
+
* // }
|
|
1925
|
+
*/
|
|
1926
|
+
const groupBy = (data, key) => {
|
|
1927
|
+
if (!data || !data.length || !Object.keys(get(data, '[0]')).length || !key) {
|
|
1928
|
+
return {};
|
|
1929
|
+
}
|
|
1930
|
+
return data.reduce((dir, nextItem) => {
|
|
1931
|
+
const valueOfKey = get(nextItem, key);
|
|
1932
|
+
if (typeof valueOfKey !== 'string' && typeof valueOfKey !== 'number' && typeof valueOfKey !== 'boolean') {
|
|
1933
|
+
return dir;
|
|
1934
|
+
}
|
|
1935
|
+
if (!Object.keys(dir).includes(`${valueOfKey}`)) {
|
|
1936
|
+
dir[`${valueOfKey}`] = [];
|
|
1937
|
+
}
|
|
1938
|
+
dir[`${valueOfKey}`].push(nextItem);
|
|
1939
|
+
return dir;
|
|
1940
|
+
}, {});
|
|
1941
|
+
};
|
|
1942
|
+
/**
|
|
1943
|
+
* Tạo một mảng các số từ giá trị bắt đầu đến giá trị kết thúc với bước nhảy tùy chọn
|
|
1944
|
+
* @param start Giá trị bắt đầu của dãy số. Nếu chỉ có một tham số, đây sẽ là giá trị kết thúc và giá trị bắt đầu sẽ là 0
|
|
1945
|
+
* @param end Giá trị kết thúc của dãy số (tùy chọn)
|
|
1946
|
+
* @param step Bước nhảy giữa các số trong dãy (tùy chọn). Mặc định là 1 nếu end > start, -1 nếu end < start
|
|
1947
|
+
* @returns Mảng các số từ start đến end với bước nhảy step
|
|
1948
|
+
* @example
|
|
1949
|
+
* range(4); // [0, 1, 2, 3]
|
|
1950
|
+
* range(1, 5); // [1, 2, 3, 4]
|
|
1951
|
+
* range(0, 20, 5); // [0, 5, 10, 15]
|
|
1952
|
+
* range(5, 2); // [5, 4, 3]
|
|
1953
|
+
*/
|
|
1954
|
+
const range = (start, end, step) => {
|
|
1955
|
+
if (end === undefined || end === null) {
|
|
1956
|
+
end = start;
|
|
1957
|
+
start = 0;
|
|
1958
|
+
}
|
|
1959
|
+
if (!step) {
|
|
1960
|
+
step = end < 0 ? -1 : 1;
|
|
1961
|
+
}
|
|
1962
|
+
if (end < start && step > 0) {
|
|
1963
|
+
step *= -1;
|
|
1964
|
+
}
|
|
1965
|
+
const valueStartByStep = step + start;
|
|
1966
|
+
const direction = start < end ? 'asc' : 'desc';
|
|
1967
|
+
if ((direction === 'asc' && (valueStartByStep < start || valueStartByStep > end)) || (direction === 'desc' && (valueStartByStep > start || valueStartByStep < end))) {
|
|
1968
|
+
return [start];
|
|
1969
|
+
}
|
|
1970
|
+
const arr = new Array();
|
|
1971
|
+
for (let index = 0; index < Math.abs(end - start); index++) {
|
|
1972
|
+
let value = start + index * step;
|
|
1973
|
+
if (index === 0) {
|
|
1974
|
+
value = start;
|
|
1975
|
+
}
|
|
1976
|
+
if ((direction === 'asc' && (value < start || value > end)) || (direction === 'desc' && (value > start || value < end))) {
|
|
1977
|
+
return arr;
|
|
1978
|
+
}
|
|
1979
|
+
arr.push(value);
|
|
1980
|
+
}
|
|
1981
|
+
return arr;
|
|
1982
|
+
};
|
|
1983
|
+
/**
|
|
1984
|
+
* So sánh hai giá trị bất kỳ có bằng nhau hay không
|
|
1985
|
+
* @param value1 Giá trị thứ nhất cần so sánh
|
|
1986
|
+
* @param value2 Giá trị thứ hai cần so sánh
|
|
1987
|
+
* @param exactlyPosition Có so sánh chính xác vị trí các phần tử trong mảng hay không
|
|
1988
|
+
* @returns true nếu hai giá trị bằng nhau, false nếu không bằng nhau
|
|
1989
|
+
* @example
|
|
1990
|
+
* isEqual([1,2,3], [1,2,3]); // true
|
|
1991
|
+
* isEqual([1,2,3], [3,2,1]); // true khi exactlyPosition = false
|
|
1992
|
+
* isEqual([1,2,3], [3,2,1]); // false khi exactlyPosition = true
|
|
1993
|
+
* isEqual({a:1}, {a:1}); // true
|
|
1994
|
+
*/
|
|
1995
|
+
const isEqual = (value1, value2, options) => {
|
|
1996
|
+
// Handle signals
|
|
1997
|
+
if (!options?.ignoreUnWrapSignal) {
|
|
1998
|
+
value1 = unwrapSignal(value1);
|
|
1999
|
+
value2 = unwrapSignal(value2);
|
|
2000
|
+
}
|
|
2001
|
+
const { exactlyPosition = false, ignoreExactlyDataType = false } = options || {};
|
|
2002
|
+
if (value1 === value2 || (value1 === null && value2 === null) || (value1 === undefined && value2 === undefined)) {
|
|
2003
|
+
return true;
|
|
2004
|
+
}
|
|
2005
|
+
if (ignoreExactlyDataType) {
|
|
2006
|
+
return isEqual(isNil(value1) ? undefined : `${value1}`, isNil(value2) ? undefined : `${value2}`);
|
|
2007
|
+
}
|
|
2008
|
+
if (typeof value1 !== 'object' || typeof value2 !== 'object' || (Array.isArray(value1) && !Array.isArray(value2)) || (!Array.isArray(value1) && Array.isArray(value2))) {
|
|
2009
|
+
return false;
|
|
2010
|
+
}
|
|
2011
|
+
if (Array.isArray(value1) || Array.isArray(value2)) {
|
|
2012
|
+
if (value1.length !== value2.length) {
|
|
2013
|
+
return false;
|
|
2014
|
+
}
|
|
2015
|
+
if (!exactlyPosition) {
|
|
2016
|
+
return !value1.some((item) => !value2.includes(item));
|
|
2017
|
+
}
|
|
2018
|
+
return !value1.some((item, index) => !isEqual(item, value2[index], options));
|
|
2019
|
+
}
|
|
2020
|
+
if (Object.keys(value1).length !== Object.keys(value2).length) {
|
|
2021
|
+
return false;
|
|
2022
|
+
}
|
|
2023
|
+
return !Object.keys(value1).some((key) => !isEqual(value1[key], value2[key], options));
|
|
2024
|
+
};
|
|
2025
|
+
/**
|
|
2026
|
+
* Loại bỏ các phần tử trùng lặp trong mảng dựa trên một thuộc tính chỉ định
|
|
2027
|
+
* @param data Mảng dữ liệu cần xử lý
|
|
2028
|
+
* @param key Tên thuộc tính dùng để so sánh trùng lặp. Nếu không có key thì so sánh trực tiếp giá trị
|
|
2029
|
+
* @returns Mảng mới chứa các phần tử không trùng lặp
|
|
2030
|
+
* @example
|
|
2031
|
+
* const arr = [
|
|
2032
|
+
* { id: 1, name: 'A' },
|
|
2033
|
+
* { id: 2, name: 'B' },
|
|
2034
|
+
* { id: 1, name: 'C' }
|
|
2035
|
+
* ];
|
|
2036
|
+
* uniqBy(arr, 'id'); // [{ id: 1, name: 'A' }, { id: 2, name: 'B' }]
|
|
2037
|
+
*
|
|
2038
|
+
* const numbers = [1, 2, 2, 3, 3];
|
|
2039
|
+
* uniqBy(numbers); // [1, 2, 3]
|
|
2040
|
+
*
|
|
2041
|
+
* const numbersSignal = [signal(1), signal(2), signal(3), signal(2), signal(5), signal(4), signal(1), signal(6), signal(7), signal(6)];
|
|
2042
|
+
* uniqBy(numbersSignal); // [signal(1), signal(2), signal(3), signal(5), signal(4), signal(6), signal(7)]
|
|
2043
|
+
*/
|
|
2044
|
+
const uniqBy = (data, key) => {
|
|
2045
|
+
if (!key || !data?.length || typeof get(data, '[0]') !== 'object') {
|
|
2046
|
+
// Xử lý mảng chứa signal values
|
|
2047
|
+
if (data[0] && isSignal(data[0])) {
|
|
2048
|
+
const seen = new Set();
|
|
2049
|
+
return data.filter((item) => {
|
|
2050
|
+
const value = `${get(item, '')}`;
|
|
2051
|
+
if (seen.has(value)) {
|
|
2052
|
+
return false;
|
|
2053
|
+
}
|
|
2054
|
+
seen.add(value);
|
|
2055
|
+
return true;
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
// Xử lý mảng primitive values
|
|
2059
|
+
return Array.from(new Set(data));
|
|
2060
|
+
}
|
|
2061
|
+
const dataUnique = keyBy(data, key);
|
|
2062
|
+
return Object.keys(dataUnique).map((key) => dataUnique[key]);
|
|
2063
|
+
};
|
|
2064
|
+
const generateInterface = (obj, interfaceName) => {
|
|
2065
|
+
const generateType = (value) => {
|
|
2066
|
+
if (value === null) {
|
|
2067
|
+
return 'null';
|
|
2068
|
+
}
|
|
2069
|
+
const type = typeof value;
|
|
2070
|
+
if (type === 'string') {
|
|
2071
|
+
return 'string';
|
|
2072
|
+
}
|
|
2073
|
+
if (type === 'number') {
|
|
2074
|
+
return 'number';
|
|
2075
|
+
}
|
|
2076
|
+
if (type === 'boolean') {
|
|
2077
|
+
return 'boolean';
|
|
2078
|
+
}
|
|
2079
|
+
if (type === 'undefined') {
|
|
2080
|
+
return 'any';
|
|
2081
|
+
}
|
|
2082
|
+
if (value instanceof Date) {
|
|
2083
|
+
return 'Date';
|
|
2084
|
+
}
|
|
2085
|
+
if (value instanceof RegExp) {
|
|
2086
|
+
return 'RegExp';
|
|
2087
|
+
}
|
|
2088
|
+
if (Array.isArray(value)) {
|
|
2089
|
+
if (value.length === 0) {
|
|
2090
|
+
return 'Array<any>';
|
|
2091
|
+
}
|
|
2092
|
+
return `Array<${generateType(value[0])}>`;
|
|
2093
|
+
}
|
|
2094
|
+
if (type === 'object') {
|
|
2095
|
+
let interfaceStr = '{\n';
|
|
2096
|
+
for (const key in value) {
|
|
2097
|
+
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
2098
|
+
const valueType = generateType(value[key]);
|
|
2099
|
+
interfaceStr += ` ${key}: ${valueType};\n`;
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
interfaceStr += '}';
|
|
2103
|
+
return interfaceStr;
|
|
2104
|
+
}
|
|
2105
|
+
return 'any';
|
|
2106
|
+
};
|
|
2107
|
+
const interfaceStr = `interface ${interfaceName} ${generateType(obj)}`;
|
|
2108
|
+
return interfaceStr;
|
|
2109
|
+
};
|
|
2110
|
+
|
|
2111
|
+
const deviceDetector = new DeviceDetector();
|
|
2112
|
+
const parser = new DOMParser();
|
|
2113
|
+
const quill = new Quill(document.createElement('div'));
|
|
2114
|
+
const getDeviceInfo = () => {
|
|
2115
|
+
return deviceDetector.parse(window.navigator.userAgent);
|
|
2116
|
+
};
|
|
2117
|
+
const getPlatFromBrowser = () => {
|
|
2118
|
+
const info = getDeviceInfo();
|
|
2119
|
+
return `${info.client?.name} ${info.client?.version}`;
|
|
2120
|
+
};
|
|
2121
|
+
const getEventNameHandleClick = (() => {
|
|
2122
|
+
const deviceInfo = getDeviceInfo();
|
|
2123
|
+
let nameEventHandleClick = deviceInfo.device?.type === 'desktop' ? 'click' : 'touchstart';
|
|
2124
|
+
if ('onpointerdown' in window) {
|
|
2125
|
+
nameEventHandleClick = 'pointerdown';
|
|
2126
|
+
}
|
|
2127
|
+
return nameEventHandleClick;
|
|
2128
|
+
})();
|
|
2129
|
+
const getDocumentByString = (htmlStr) => {
|
|
2130
|
+
return parser.parseFromString(htmlStr, 'text/html');
|
|
2131
|
+
};
|
|
2132
|
+
const cloneIBoundingClientRect = (rect) => {
|
|
2133
|
+
return {
|
|
2134
|
+
top: rect['top'],
|
|
2135
|
+
left: rect['left'],
|
|
2136
|
+
width: rect['width'],
|
|
2137
|
+
height: rect['height'],
|
|
2138
|
+
bottom: rect['bottom'],
|
|
2139
|
+
};
|
|
2140
|
+
};
|
|
2141
|
+
const setStylesElement = (el, styles) => {
|
|
2142
|
+
Object.keys(styles).forEach((key) => set(el, `style.${key}`, styles[key]));
|
|
2143
|
+
};
|
|
2144
|
+
const getViewport = (win = window) => {
|
|
2145
|
+
const doc = win.document.documentElement, body = win.document.getElementsByTagName('body')[0], w = win.innerWidth || doc.clientWidth || body.clientWidth, h = win.innerHeight || doc.clientHeight || body.clientHeight;
|
|
2146
|
+
return { width: w, height: h };
|
|
2147
|
+
};
|
|
2148
|
+
const setCaretPosition = (element, position) => {
|
|
2149
|
+
if (!element || !element.setSelectionRange) {
|
|
2150
|
+
return;
|
|
2151
|
+
}
|
|
2152
|
+
element.focus();
|
|
2153
|
+
element.setSelectionRange(position, position);
|
|
2154
|
+
};
|
|
2155
|
+
const checkViewInScreen = (container, element, elementScroll, maxTopLeft) => {
|
|
2156
|
+
if (!container || !element) {
|
|
2157
|
+
return false;
|
|
2158
|
+
}
|
|
2159
|
+
const rectContainer = cloneIBoundingClientRect(container.getBoundingClientRect());
|
|
2160
|
+
if (elementScroll) {
|
|
2161
|
+
const rectElementScroll = elementScroll.getBoundingClientRect();
|
|
2162
|
+
rectContainer['left'] = rectElementScroll.left;
|
|
2163
|
+
rectContainer['top'] = rectElementScroll.top;
|
|
2164
|
+
}
|
|
2165
|
+
const rectElement = element.getBoundingClientRect();
|
|
2166
|
+
const { left, top } = rectElement;
|
|
2167
|
+
const topContainer = rectContainer['top'];
|
|
2168
|
+
const leftContainer = rectContainer['left'];
|
|
2169
|
+
const widthContainer = rectContainer['width'];
|
|
2170
|
+
const heightContainer = rectContainer['height'];
|
|
2171
|
+
const scrollTopContainer = container.scrollTop;
|
|
2172
|
+
const scrollLeftContainer = container.scrollLeft;
|
|
2173
|
+
const limitTop = topContainer + scrollTopContainer + heightContainer;
|
|
2174
|
+
const limitLeft = leftContainer + scrollLeftContainer + widthContainer;
|
|
2175
|
+
const maxTopItem = top + (maxTopLeft?.top || 0);
|
|
2176
|
+
const maxLeftItem = left + (maxTopLeft?.left || 0);
|
|
2177
|
+
if (topContainer <= maxTopItem && maxTopItem <= limitTop && leftContainer <= maxLeftItem && maxLeftItem <= limitLeft) {
|
|
2178
|
+
return true;
|
|
2179
|
+
}
|
|
2180
|
+
return false;
|
|
2181
|
+
};
|
|
2182
|
+
const checkMouseOverInContainer = (mousePosition, element, rect) => {
|
|
2183
|
+
if (!element && !rect) {
|
|
2184
|
+
return false;
|
|
2185
|
+
}
|
|
2186
|
+
const rectElement = rect || element?.getBoundingClientRect() || {};
|
|
2187
|
+
const { left, top, width, height } = rectElement;
|
|
2188
|
+
const limitLeft = left + width;
|
|
2189
|
+
const limitTop = top + height;
|
|
2190
|
+
const { clientX, clientY } = mousePosition;
|
|
2191
|
+
if (left <= clientX && clientX <= limitLeft && top <= clientY && clientY <= limitTop) {
|
|
2192
|
+
return true;
|
|
2193
|
+
}
|
|
2194
|
+
return false;
|
|
2195
|
+
};
|
|
2196
|
+
const getDragEventByElement = (config) => {
|
|
2197
|
+
let timer;
|
|
2198
|
+
const addClass = () => {
|
|
2199
|
+
timer = setTimeout(() => {
|
|
2200
|
+
document.body.classList.add('!select-none');
|
|
2201
|
+
document.body.classList.add('!cursor-grabbing');
|
|
2202
|
+
}, 250);
|
|
2203
|
+
};
|
|
2204
|
+
const removeClass = () => {
|
|
2205
|
+
document.body.classList.remove('!select-none');
|
|
2206
|
+
document.body.classList.remove('!cursor-grabbing');
|
|
2207
|
+
clearTimeout(timer);
|
|
2208
|
+
};
|
|
2209
|
+
fromEvent(config.elementMouseDown, 'mouseleave')
|
|
2210
|
+
.pipe(tap(() => document.body.classList.remove('cursor-pointer')), takeUntil(config.onDestroy))
|
|
2211
|
+
.subscribe();
|
|
2212
|
+
fromEvent(config.elementMouseDown, 'mouseenter')
|
|
2213
|
+
.pipe(tap(() => document.body.classList.add('cursor-pointer')), takeUntil(config.onDestroy))
|
|
2214
|
+
.subscribe();
|
|
2215
|
+
const mouseDown = fromEvent(config.elementMouseDown, 'mousedown').pipe(tap((e) => {
|
|
2216
|
+
e.stopPropagation();
|
|
2217
|
+
addClass();
|
|
2218
|
+
config.functionMouseDown?.(e);
|
|
2219
|
+
}));
|
|
2220
|
+
const mouseup = fromEvent(config.elementMouseUp || document, 'mouseup').pipe(tap((e) => {
|
|
2221
|
+
e.stopPropagation();
|
|
2222
|
+
removeClass();
|
|
2223
|
+
config.functionMouseUp?.(e);
|
|
2224
|
+
}));
|
|
2225
|
+
const mousemove = fromEvent(config.elementMouseMove || document, 'mousemove').pipe(tap((e) => {
|
|
2226
|
+
e.stopPropagation();
|
|
2227
|
+
config.functionMouseMove?.(e);
|
|
2228
|
+
}), takeUntil(mouseup));
|
|
2229
|
+
return mouseDown.pipe(mergeMap((e) => (config.isStartWithMouseDownEvent ? mousemove.pipe(startWith(e)) : mousemove)), takeUntil(config.onDestroy), finalize(removeClass));
|
|
2230
|
+
};
|
|
2231
|
+
const getHTMLFromQuill = async (data, options) => {
|
|
2232
|
+
const { replaceNewLineTo = '<br>', replaceTagBRTo, replaceTags, replaceBrToDiv } = options || {};
|
|
2233
|
+
const delta = typeof data === 'string' ? await getDeltaFromHTML(data) : data;
|
|
2234
|
+
if (options?.functionReplaceDelta) {
|
|
2235
|
+
options.functionReplaceDelta(delta);
|
|
2236
|
+
}
|
|
2237
|
+
delta.ops.forEach((op) => {
|
|
2238
|
+
if (op.insert) {
|
|
2239
|
+
if (typeof op.insert === 'string') {
|
|
2240
|
+
if (replaceNewLineTo) {
|
|
2241
|
+
op.insert = op.insert.replace(/\n/g, replaceNewLineTo);
|
|
2242
|
+
}
|
|
2243
|
+
if (replaceTagBRTo) {
|
|
2244
|
+
op.insert = op.insert.replace(/<br>/g, replaceTagBRTo);
|
|
2245
|
+
}
|
|
2246
|
+
if (replaceTags?.length) {
|
|
2247
|
+
for (const tag of replaceTags) {
|
|
2248
|
+
op.insert = op.insert.replace(new RegExp(`<${tag.tag}>`, 'g'), `<${tag.replaceTo}>`);
|
|
2249
|
+
op.insert = op.insert.replace(new RegExp(`</${tag.tag}>`, 'g'), `</${tag.replaceTo}>`);
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
});
|
|
2255
|
+
quill.setContents(delta);
|
|
2256
|
+
let htmlText = options?.getRootHtml ? quill.root.innerHTML : quill.root.firstElementChild?.innerHTML;
|
|
2257
|
+
if (replaceBrToDiv) {
|
|
2258
|
+
htmlText = convertHtmlToDivBlocks(htmlText || '');
|
|
2259
|
+
}
|
|
2260
|
+
return decodeEscapeHtml(htmlText || '');
|
|
2261
|
+
};
|
|
2262
|
+
const convertHtmlToDivBlocks = (html) => {
|
|
2263
|
+
const BREAK_TOKEN = '<<<BREAK>>>';
|
|
2264
|
+
// Bước 1: thay <br> thành token tạm
|
|
2265
|
+
const normalizedHtml = html.replace(/<br\s*\/?>/gi, BREAK_TOKEN);
|
|
2266
|
+
// Bước 2: tách theo token
|
|
2267
|
+
const parts = normalizedHtml.split(BREAK_TOKEN);
|
|
2268
|
+
const parser = new DOMParser();
|
|
2269
|
+
const divs = [];
|
|
2270
|
+
for (const raw of parts) {
|
|
2271
|
+
const trimmed = raw.trim();
|
|
2272
|
+
if (!trimmed)
|
|
2273
|
+
continue;
|
|
2274
|
+
// parse mỗi phần nhỏ như một document riêng
|
|
2275
|
+
const doc = parser.parseFromString(trimmed, 'text/html');
|
|
2276
|
+
const body = doc.body;
|
|
2277
|
+
// Lấy lại nội dung bên trong body
|
|
2278
|
+
divs.push(`<div>${body.innerHTML}</div>`);
|
|
2279
|
+
}
|
|
2280
|
+
return divs.join('');
|
|
2281
|
+
};
|
|
2282
|
+
const getDeltaFromHTML = async (html) => {
|
|
2283
|
+
quill.root.innerHTML = html;
|
|
2284
|
+
setTimeout(() => {
|
|
2285
|
+
console.log(quill.getContents());
|
|
2286
|
+
}, 1000);
|
|
2287
|
+
await lastValueFrom(timer(1000));
|
|
2288
|
+
return quill.getContents();
|
|
2289
|
+
};
|
|
2290
|
+
const processPasteData = async (e, config) => {
|
|
2291
|
+
const element = config.element;
|
|
2292
|
+
const files = e.clipboardData?.files;
|
|
2293
|
+
if (files?.length) {
|
|
2294
|
+
e.preventDefault();
|
|
2295
|
+
config.handlerPasteFile?.(files);
|
|
2296
|
+
config.callBack?.('file');
|
|
2297
|
+
return;
|
|
2298
|
+
}
|
|
2299
|
+
// Lưu selection TRƯỚC khi prevent default
|
|
2300
|
+
const selection = window.getSelection();
|
|
2301
|
+
let savedRange = null;
|
|
2302
|
+
if (selection && selection.rangeCount > 0) {
|
|
2303
|
+
const range = selection.getRangeAt(0);
|
|
2304
|
+
// Chỉ lưu nếu range nằm trong contentText element
|
|
2305
|
+
const container = range.commonAncestorContainer;
|
|
2306
|
+
const isInContentElement = element.contains(container.nodeType === Node.TEXT_NODE ? container.parentNode : container);
|
|
2307
|
+
if (isInContentElement) {
|
|
2308
|
+
savedRange = range.cloneRange();
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
// Prevent default để tự xử lý paste
|
|
2312
|
+
e.preventDefault();
|
|
2313
|
+
// Sử dụng Quill để clean HTML content
|
|
2314
|
+
const htmlContent = e.clipboardData?.getData('text/html') || '';
|
|
2315
|
+
const plainText = e.clipboardData?.getData('text/plain') || '';
|
|
2316
|
+
let contentToInsert = (plainText || '').replace(/\n/g, '<br>');
|
|
2317
|
+
if (htmlContent) {
|
|
2318
|
+
contentToInsert = await getHTMLFromQuill(htmlContent);
|
|
2319
|
+
}
|
|
2320
|
+
if (!contentToInsert) {
|
|
2321
|
+
config.callBack?.('no-content');
|
|
2322
|
+
return;
|
|
2323
|
+
}
|
|
2324
|
+
if (savedRange) {
|
|
2325
|
+
insertContentWithRange(contentToInsert, savedRange, element);
|
|
2326
|
+
config.callBack?.('range');
|
|
2327
|
+
return;
|
|
2328
|
+
}
|
|
2329
|
+
element.innerHTML += contentToInsert;
|
|
2330
|
+
config.callBack?.('content');
|
|
2331
|
+
};
|
|
2332
|
+
const insertContentWithRange = (content, savedRange, element) => {
|
|
2333
|
+
const selection = window.getSelection();
|
|
2334
|
+
if (!selection) {
|
|
2335
|
+
// Fallback: append vào cuối
|
|
2336
|
+
element.innerHTML += content;
|
|
2337
|
+
return;
|
|
2338
|
+
}
|
|
2339
|
+
// Restore selection
|
|
2340
|
+
selection.removeAllRanges();
|
|
2341
|
+
selection.addRange(savedRange);
|
|
2342
|
+
// Xóa nội dung đã select (nếu có)
|
|
2343
|
+
savedRange.deleteContents();
|
|
2344
|
+
// Tạo document fragment từ HTML content
|
|
2345
|
+
const tempDiv = document.createElement('div');
|
|
2346
|
+
tempDiv.innerHTML = content;
|
|
2347
|
+
const fragment = document.createDocumentFragment();
|
|
2348
|
+
while (tempDiv.firstChild) {
|
|
2349
|
+
fragment.appendChild(tempDiv.firstChild);
|
|
2350
|
+
}
|
|
2351
|
+
// Insert fragment tại vị trí range
|
|
2352
|
+
savedRange.insertNode(fragment);
|
|
2353
|
+
// Di chuyển cursor đến cuối nội dung vừa insert
|
|
2354
|
+
if (fragment.lastChild) {
|
|
2355
|
+
savedRange.setStartAfter(fragment.lastChild);
|
|
2356
|
+
}
|
|
2357
|
+
savedRange.collapse(true);
|
|
2358
|
+
selection.removeAllRanges();
|
|
2359
|
+
selection.addRange(savedRange);
|
|
2360
|
+
};
|
|
2361
|
+
|
|
2362
|
+
class UtilsUrlSearchParams {
|
|
2363
|
+
static instance;
|
|
2364
|
+
params;
|
|
2365
|
+
constructor(paramString) {
|
|
2366
|
+
this.params = new Array();
|
|
2367
|
+
this.params.length = 0;
|
|
2368
|
+
this.params.push(...this.buildParams(paramString));
|
|
2369
|
+
}
|
|
2370
|
+
static getInstance() {
|
|
2371
|
+
if (!this.instance) {
|
|
2372
|
+
this.instance = new UtilsUrlSearchParams('');
|
|
2373
|
+
}
|
|
2374
|
+
return this.instance;
|
|
2375
|
+
}
|
|
2376
|
+
static toStringParamObject(params) {
|
|
2377
|
+
const paramsArr = Object.keys(params).map((key) => {
|
|
2378
|
+
return {
|
|
2379
|
+
key,
|
|
2380
|
+
value: params[key],
|
|
2381
|
+
};
|
|
2382
|
+
});
|
|
2383
|
+
return UtilsUrlSearchParams.ToString(paramsArr);
|
|
2384
|
+
}
|
|
2385
|
+
static ToString(params) {
|
|
2386
|
+
let paramsStr = '';
|
|
2387
|
+
params
|
|
2388
|
+
.sort((item1, item2) => item1.key.localeCompare(item2.key))
|
|
2389
|
+
.forEach((item) => {
|
|
2390
|
+
const { key, value } = item;
|
|
2391
|
+
paramsStr = `${paramsStr}${paramsStr ? '&' : ''}${key}=${value}`;
|
|
2392
|
+
});
|
|
2393
|
+
return paramsStr;
|
|
2394
|
+
}
|
|
2395
|
+
buildParams(paramString) {
|
|
2396
|
+
const paramsBuild = new Array();
|
|
2397
|
+
if (!paramString) {
|
|
2398
|
+
return paramsBuild;
|
|
2399
|
+
}
|
|
2400
|
+
const paramsError = paramString.split('?');
|
|
2401
|
+
if (paramsError.length > 2) {
|
|
2402
|
+
paramString = `${paramsError[0]}?${paramsError[1]}&${paramsError.slice(2).join('&')}`;
|
|
2403
|
+
}
|
|
2404
|
+
const params = paramString.replace(/\S+[?]/g, '').split('&');
|
|
2405
|
+
if (params) {
|
|
2406
|
+
params.forEach((param) => {
|
|
2407
|
+
if (param.indexOf('=') < 0) {
|
|
2408
|
+
return;
|
|
2409
|
+
}
|
|
2410
|
+
let [key, value] = param.split('=');
|
|
2411
|
+
key = key.replace(/[?&]+/g, '');
|
|
2412
|
+
value = decodeURIComponent(value.replace(/\+/g, ' '));
|
|
2413
|
+
if (!paramsBuild.some((param) => param.key === key)) {
|
|
2414
|
+
paramsBuild.push({ key, value });
|
|
2415
|
+
}
|
|
2416
|
+
});
|
|
2417
|
+
}
|
|
2418
|
+
return paramsBuild;
|
|
2419
|
+
}
|
|
2420
|
+
set(key, value) {
|
|
2421
|
+
const paramByKey = this.params.find((param) => param.key === key);
|
|
2422
|
+
if (!paramByKey) {
|
|
2423
|
+
this.params.push({ key, value: decodeURIComponent(value.replace(/\+/g, ' ')) });
|
|
2424
|
+
return;
|
|
2425
|
+
}
|
|
2426
|
+
paramByKey['value'] = value;
|
|
2427
|
+
}
|
|
2428
|
+
get(key) {
|
|
2429
|
+
return this.params.find((param) => param.key === key)?.value;
|
|
2430
|
+
}
|
|
2431
|
+
has(key) {
|
|
2432
|
+
return this.params.some((param) => param.key === key);
|
|
2433
|
+
}
|
|
2434
|
+
delete(key) {
|
|
2435
|
+
const index = this.params.findIndex((param) => param.key === key);
|
|
2436
|
+
if (index >= 0) {
|
|
2437
|
+
this.params.splice(index, 1);
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
length() {
|
|
2441
|
+
return this.params.length;
|
|
2442
|
+
}
|
|
2443
|
+
toString(params) {
|
|
2444
|
+
params = params || this.params;
|
|
2445
|
+
return UtilsUrlSearchParams.ToString(params);
|
|
2446
|
+
}
|
|
2447
|
+
getParamObject() {
|
|
2448
|
+
const params = {};
|
|
2449
|
+
this.params.forEach((item) => {
|
|
2450
|
+
params[item.key] = item.value;
|
|
2451
|
+
});
|
|
2452
|
+
return params;
|
|
2453
|
+
}
|
|
2454
|
+
compareParams(param1, param2) {
|
|
2455
|
+
const params1 = this.toString(this.buildParams(param1));
|
|
2456
|
+
const params2 = this.toString(this.buildParams(param2));
|
|
2457
|
+
return params1 === params2;
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
const step = 20;
|
|
2462
|
+
const percent = 0.05;
|
|
2463
|
+
const colorStepContrastFromOrigin = (color, stepNumber) => {
|
|
2464
|
+
return colorContrastFromOrigin(color).find((item) => item.step === stepNumber);
|
|
2465
|
+
};
|
|
2466
|
+
const colorContrastFromOrigin = (color) => {
|
|
2467
|
+
const parsedColorsArray = parseColorValues(color);
|
|
2468
|
+
const colors = [];
|
|
2469
|
+
let calculatedShades = [];
|
|
2470
|
+
let calculatedTints = [];
|
|
2471
|
+
if (parsedColorsArray !== null) {
|
|
2472
|
+
for (let i = 0; i < parsedColorsArray.length; i++) {
|
|
2473
|
+
calculatedShades = calculateShades(parsedColorsArray[i]);
|
|
2474
|
+
calculatedTints = calculateTints(parsedColorsArray[i]);
|
|
2475
|
+
}
|
|
2476
|
+
for (let i = 0; i <= step; i++) {
|
|
2477
|
+
colors.push({ step: i * 5, label: `${i * 5}%`, dark: `#${calculatedShades[i]}`, light: `#${calculatedTints[i]}` });
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
return colors;
|
|
2481
|
+
};
|
|
2482
|
+
const parseColorValues = (colorValues) => {
|
|
2483
|
+
let colorValuesArray = colorValues.match(/\b[0-9A-Fa-f]{3}\b|[0-9A-Fa-f]{6}\b/g);
|
|
2484
|
+
if (colorValuesArray) {
|
|
2485
|
+
colorValuesArray = colorValuesArray.map((item) => {
|
|
2486
|
+
if (item.length === 3) {
|
|
2487
|
+
let newItem = item.toString().split('');
|
|
2488
|
+
newItem = newItem.reduce((acc, it) => {
|
|
2489
|
+
return acc + it + it;
|
|
2490
|
+
}, '');
|
|
2491
|
+
return newItem;
|
|
2492
|
+
}
|
|
2493
|
+
return item;
|
|
2494
|
+
});
|
|
2495
|
+
}
|
|
2496
|
+
return colorValuesArray;
|
|
2497
|
+
};
|
|
2498
|
+
const calculateShades = (colorValue) => {
|
|
2499
|
+
return calculate(colorValue, rgbShade).concat('000000');
|
|
2500
|
+
};
|
|
2501
|
+
const calculateTints = (colorValue) => {
|
|
2502
|
+
return calculate(colorValue, rgbTint).concat('ffffff');
|
|
2503
|
+
};
|
|
2504
|
+
const calculate = (colorValue, shadeOrTint) => {
|
|
2505
|
+
const color = hexToRGB(colorValue);
|
|
2506
|
+
const shadeValues = [];
|
|
2507
|
+
for (let i = 0; i < step; i++) {
|
|
2508
|
+
shadeValues[i] = rgbToHex(shadeOrTint(color, i));
|
|
2509
|
+
}
|
|
2510
|
+
return shadeValues;
|
|
2511
|
+
};
|
|
2512
|
+
const rgbShade = (rgb, i) => {
|
|
2513
|
+
return { red: rgb.red * (1 - percent * i), green: rgb.green * (1 - percent * i), blue: rgb.blue * (1 - percent * i) };
|
|
2514
|
+
};
|
|
2515
|
+
const rgbTint = (rgb, i) => {
|
|
2516
|
+
return { red: rgb.red + (255 - rgb.red) * i * percent, green: rgb.green + (255 - rgb.green) * i * percent, blue: rgb.blue + (255 - rgb.blue) * i * percent };
|
|
2517
|
+
};
|
|
2518
|
+
const rgbToHex = (rgb) => {
|
|
2519
|
+
return intToHex(rgb.red) + intToHex(rgb.green) + intToHex(rgb.blue);
|
|
2520
|
+
};
|
|
2521
|
+
const hexToRGB = (colorValue) => {
|
|
2522
|
+
return { red: parseInt(colorValue.substr(0, 2), 16), green: parseInt(colorValue.substr(2, 2), 16), blue: parseInt(colorValue.substr(4, 2), 16) };
|
|
2523
|
+
};
|
|
2524
|
+
const intToHex = (rgbint) => {
|
|
2525
|
+
return pad(Math.min(Math.max(Math.round(rgbint), 0), 255).toString(16), 2);
|
|
2526
|
+
};
|
|
2527
|
+
const pad = (number, length) => {
|
|
2528
|
+
let str = '' + number;
|
|
2529
|
+
while (str.length < length) {
|
|
2530
|
+
str = '0' + str;
|
|
2531
|
+
}
|
|
2532
|
+
return str;
|
|
2533
|
+
};
|
|
2534
|
+
const listColorDefine = [
|
|
2535
|
+
'#E62222',
|
|
2536
|
+
'#B81B1B',
|
|
2537
|
+
'#EB4E4E',
|
|
2538
|
+
'#F97316',
|
|
2539
|
+
'#C75C12',
|
|
2540
|
+
'#FA8F45',
|
|
2541
|
+
'#FFB700',
|
|
2542
|
+
'#CC9200',
|
|
2543
|
+
'#FFC533',
|
|
2544
|
+
'#84CC16',
|
|
2545
|
+
'#6AA312',
|
|
2546
|
+
'#9dd645',
|
|
2547
|
+
'#00BC62',
|
|
2548
|
+
'#00A757',
|
|
2549
|
+
'#33DA8A',
|
|
2550
|
+
'#06B6D4',
|
|
2551
|
+
'#1B59C4',
|
|
2552
|
+
'#4E8CF7',
|
|
2553
|
+
'#0EA5E9',
|
|
2554
|
+
'#1B59C4',
|
|
2555
|
+
'#4E8CF7',
|
|
2556
|
+
'#226FF5',
|
|
2557
|
+
'#1B59C4',
|
|
2558
|
+
'#4E8CF7',
|
|
2559
|
+
'#6366F1',
|
|
2560
|
+
'#4F52C1',
|
|
2561
|
+
'#8285F4',
|
|
2562
|
+
'#5B04B3',
|
|
2563
|
+
'#49038F',
|
|
2564
|
+
'#7C36C2',
|
|
2565
|
+
'#D946EF',
|
|
2566
|
+
'#AE38BF',
|
|
2567
|
+
'#E16BF2',
|
|
2568
|
+
'#EC4899',
|
|
2569
|
+
'#BD3A7A',
|
|
2570
|
+
'#F06DAD',
|
|
2571
|
+
'#F43F5E',
|
|
2572
|
+
'#C3324B',
|
|
2573
|
+
'#F6657E',
|
|
2574
|
+
'#757380',
|
|
2575
|
+
'#5E5C66',
|
|
2576
|
+
'#918F99',
|
|
2577
|
+
'#202020',
|
|
2578
|
+
'#1A1A1A',
|
|
2579
|
+
'#4D4D4D',
|
|
2580
|
+
];
|
|
2581
|
+
const getColorById = (str) => {
|
|
2582
|
+
let hashString = 0;
|
|
2583
|
+
if (!str) {
|
|
2584
|
+
return '';
|
|
2585
|
+
}
|
|
2586
|
+
for (let i = 0; i < str.length; i++) {
|
|
2587
|
+
const char = str.charCodeAt(i);
|
|
2588
|
+
hashString = (hashString << 5) - hashString + char;
|
|
2589
|
+
hashString = hashString & hashString;
|
|
2590
|
+
}
|
|
2591
|
+
return listColorDefine[Math.abs(hashString) % listColorDefine.length];
|
|
2592
|
+
};
|
|
2593
|
+
|
|
2594
|
+
class UtilsKeyCodeConstant {
|
|
2595
|
+
static MAC_ENTER = 3;
|
|
2596
|
+
static BACKSPACE = 8;
|
|
2597
|
+
static TAB = 9;
|
|
2598
|
+
static NUM_CENTER = 12;
|
|
2599
|
+
static ENTER = 13;
|
|
2600
|
+
static SHIFT = 16;
|
|
2601
|
+
static CONTROL = 17;
|
|
2602
|
+
static ALT = 18;
|
|
2603
|
+
static PAUSE = 19;
|
|
2604
|
+
static CAPS_LOCK = 20;
|
|
2605
|
+
static ESCAPE = 27;
|
|
2606
|
+
static SPACE = 32;
|
|
2607
|
+
static PAGE_UP = 33;
|
|
2608
|
+
static PAGE_DOWN = 34;
|
|
2609
|
+
static END = 35;
|
|
2610
|
+
static HOME = 36;
|
|
2611
|
+
static LEFT_ARROW = 37;
|
|
2612
|
+
static UP_ARROW = 38;
|
|
2613
|
+
static RIGHT_ARROW = 39;
|
|
2614
|
+
static DOWN_ARROW = 40;
|
|
2615
|
+
static PLUS_SIGN = 43;
|
|
2616
|
+
static PRINT_SCREEN = 44;
|
|
2617
|
+
static INSERT = 45;
|
|
2618
|
+
static DELETE = 46;
|
|
2619
|
+
static ZERO = 48;
|
|
2620
|
+
static ONE = 49;
|
|
2621
|
+
static TWO = 50;
|
|
2622
|
+
static THREE = 51;
|
|
2623
|
+
static FOUR = 52;
|
|
2624
|
+
static FIVE = 53;
|
|
2625
|
+
static SIX = 54;
|
|
2626
|
+
static SEVEN = 55;
|
|
2627
|
+
static EIGHT = 56;
|
|
2628
|
+
static NINE = 57;
|
|
2629
|
+
static FF_SEMICOLON = 59; // Firefox (Gecko) fires this for semicolon instead of 186
|
|
2630
|
+
static FF_EQUALS = 61; // Firefox (Gecko) fires this for equals instead of 187
|
|
2631
|
+
static QUESTION_MARK = 63;
|
|
2632
|
+
static AT_SIGN = 64;
|
|
2633
|
+
static A = 65;
|
|
2634
|
+
static B = 66;
|
|
2635
|
+
static C = 67;
|
|
2636
|
+
static D = 68;
|
|
2637
|
+
static E = 69;
|
|
2638
|
+
static F = 70;
|
|
2639
|
+
static G = 71;
|
|
2640
|
+
static H = 72;
|
|
2641
|
+
static I = 73;
|
|
2642
|
+
static J = 74;
|
|
2643
|
+
static K = 75;
|
|
2644
|
+
static L = 76;
|
|
2645
|
+
static M = 77;
|
|
2646
|
+
static N = 78;
|
|
2647
|
+
static O = 79;
|
|
2648
|
+
static P = 80;
|
|
2649
|
+
static Q = 81;
|
|
2650
|
+
static R = 82;
|
|
2651
|
+
static S = 83;
|
|
2652
|
+
static T = 84;
|
|
2653
|
+
static U = 85;
|
|
2654
|
+
static V = 86;
|
|
2655
|
+
static W = 87;
|
|
2656
|
+
static X = 88;
|
|
2657
|
+
static Y = 89;
|
|
2658
|
+
static Z = 90;
|
|
2659
|
+
static META = 91; // WIN_KEY_LEFT
|
|
2660
|
+
static MAC_WK_CMD_LEFT = 91;
|
|
2661
|
+
static MAC_WK_CMD_RIGHT = 93;
|
|
2662
|
+
static CONTEXT_MENU = 93;
|
|
2663
|
+
static NUMPAD_ZERO = 96;
|
|
2664
|
+
static NUMPAD_ONE = 97;
|
|
2665
|
+
static NUMPAD_TWO = 98;
|
|
2666
|
+
static NUMPAD_THREE = 99;
|
|
2667
|
+
static NUMPAD_FOUR = 100;
|
|
2668
|
+
static NUMPAD_FIVE = 101;
|
|
2669
|
+
static NUMPAD_SIX = 102;
|
|
2670
|
+
static NUMPAD_SEVEN = 103;
|
|
2671
|
+
static NUMPAD_EIGHT = 104;
|
|
2672
|
+
static NUMPAD_NINE = 105;
|
|
2673
|
+
static NUMPAD_MULTIPLY = 106;
|
|
2674
|
+
static NUMPAD_PLUS = 107;
|
|
2675
|
+
static NUMPAD_MINUS = 109;
|
|
2676
|
+
static NUMPAD_PERIOD = 110;
|
|
2677
|
+
static NUMPAD_DIVIDE = 111;
|
|
2678
|
+
static F1 = 112;
|
|
2679
|
+
static F2 = 113;
|
|
2680
|
+
static F3 = 114;
|
|
2681
|
+
static F4 = 115;
|
|
2682
|
+
static F5 = 116;
|
|
2683
|
+
static F6 = 117;
|
|
2684
|
+
static F7 = 118;
|
|
2685
|
+
static F8 = 119;
|
|
2686
|
+
static F9 = 120;
|
|
2687
|
+
static F10 = 121;
|
|
2688
|
+
static F11 = 122;
|
|
2689
|
+
static F12 = 123;
|
|
2690
|
+
static NUM_LOCK = 144;
|
|
2691
|
+
static SCROLL_LOCK = 145;
|
|
2692
|
+
static FIRST_MEDIA = 166;
|
|
2693
|
+
static FF_MINUS = 173;
|
|
2694
|
+
static MUTE = 173; // Firefox (Gecko) fires 181 for MUTE
|
|
2695
|
+
static VOLUME_DOWN = 174; // Firefox (Gecko) fires 182 for VOLUME_DOWN
|
|
2696
|
+
static VOLUME_UP = 175; // Firefox (Gecko) fires 183 for VOLUME_UP
|
|
2697
|
+
static FF_MUTE = 181;
|
|
2698
|
+
static FF_VOLUME_DOWN = 182;
|
|
2699
|
+
static LAST_MEDIA = 183;
|
|
2700
|
+
static FF_VOLUME_UP = 183;
|
|
2701
|
+
static SEMICOLON = 186; // Firefox (Gecko) fires 59 for SEMICOLON
|
|
2702
|
+
static EQUALS = 187; // Firefox (Gecko) fires 61 for EQUALS
|
|
2703
|
+
static COMMA = 188;
|
|
2704
|
+
static DASH = 189; // Firefox (Gecko) fires 173 for DASH/MINUS
|
|
2705
|
+
static PERIOD = 190;
|
|
2706
|
+
static SLASH = 191;
|
|
2707
|
+
static APOSTROPHE = 192;
|
|
2708
|
+
static TILDE = 192;
|
|
2709
|
+
static OPEN_SQUARE_BRACKET = 219;
|
|
2710
|
+
static BACKSLASH = 220;
|
|
2711
|
+
static CLOSE_SQUARE_BRACKET = 221;
|
|
2712
|
+
static SINGLE_QUOTE = 222;
|
|
2713
|
+
static MAC_META = 224;
|
|
2714
|
+
static BUFFERED = 229;
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2718
|
+
const getKeyCacheByArrayObject = (keyCache, argumentsValue) => {
|
|
2719
|
+
if (!keyCache || !argumentsValue) {
|
|
2720
|
+
return '';
|
|
2721
|
+
}
|
|
2722
|
+
let keyBuild = `${JSON.stringify(argumentsValue.slice(1))}-key-cache-plus`;
|
|
2723
|
+
argumentsValue.forEach((item) => {
|
|
2724
|
+
if (typeof item === 'object') {
|
|
2725
|
+
if (Array.isArray(item)) {
|
|
2726
|
+
keyBuild = `${keyBuild}${JSON.stringify(item)}`;
|
|
2727
|
+
return;
|
|
2728
|
+
}
|
|
2729
|
+
const keys = (item instanceof HttpParams ? item.keys() : Object.keys(item)).sort((str1, str2) => str1.localeCompare(str2));
|
|
2730
|
+
keys.forEach((key) => {
|
|
2731
|
+
if (key.toLocaleLowerCase() === 'pem') {
|
|
2732
|
+
return;
|
|
2733
|
+
}
|
|
2734
|
+
keyBuild = `${keyBuild}${JSON.stringify(get(item, key))}`;
|
|
2735
|
+
});
|
|
2736
|
+
return;
|
|
2737
|
+
}
|
|
2738
|
+
keyBuild = `${keyBuild}${item}`;
|
|
2739
|
+
});
|
|
2740
|
+
const keyCachePlus = md5(keyBuild);
|
|
2741
|
+
const keyCacheMD5 = md5(keyCache);
|
|
2742
|
+
return `${keyCacheMD5}-${md5(`${keyCacheMD5}-${keyCachePlus}`)}`;
|
|
2743
|
+
};
|
|
2744
|
+
|
|
2745
|
+
const formatNumber = (value) => {
|
|
2746
|
+
const lang = UtilsCache.getLang();
|
|
2747
|
+
if (lang === UtilsLanguageConstants.EN) {
|
|
2748
|
+
return `${value}`.replace(/[,]/g, '');
|
|
2749
|
+
}
|
|
2750
|
+
return `${value}`.replace(/[.]/g, '').replace(/[,]/g, '.');
|
|
2751
|
+
};
|
|
2752
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2753
|
+
const viewDataNumberByLanguage = (value, acceptNegativeValue, parseFixed = 1, ignoreFormatSeparator = false, ignoreParseFloat = false) => {
|
|
2754
|
+
const lang = UtilsCache.getLang();
|
|
2755
|
+
if (!`${value}`.trim()) {
|
|
2756
|
+
return '';
|
|
2757
|
+
}
|
|
2758
|
+
if (isNil(value) || (!acceptNegativeValue && +value <= 0)) {
|
|
2759
|
+
return 0;
|
|
2760
|
+
}
|
|
2761
|
+
if (typeof value === 'string' && !ignoreFormatSeparator) {
|
|
2762
|
+
value = formatNumber(value);
|
|
2763
|
+
}
|
|
2764
|
+
const isFloat = value.toString().includes('.');
|
|
2765
|
+
const [intStr, floatStr] = value.toString().split('.');
|
|
2766
|
+
if (!floatStr?.length) {
|
|
2767
|
+
parseFixed = 0;
|
|
2768
|
+
}
|
|
2769
|
+
const round = (value, parseFixed) => {
|
|
2770
|
+
const fixed = Math.pow(10, parseFixed);
|
|
2771
|
+
return Math.round(value * fixed) / fixed;
|
|
2772
|
+
};
|
|
2773
|
+
if (parseFixed > (floatStr?.length || 0)) {
|
|
2774
|
+
const maxParseFixed = acceptNegativeValue && +value < 0 ? 17 : 16;
|
|
2775
|
+
const fixed = maxParseFixed - (intStr?.length || 0);
|
|
2776
|
+
parseFixed = parseFixed < fixed ? parseFixed : fixed;
|
|
2777
|
+
}
|
|
2778
|
+
if (isFloat && !ignoreParseFloat) {
|
|
2779
|
+
value = parseFloat(round(+value, parseFixed).toFixed(parseFixed));
|
|
2780
|
+
}
|
|
2781
|
+
// Xử lý không format dữ liệu phần thập phân
|
|
2782
|
+
const valueString = value.toString();
|
|
2783
|
+
const index = valueString.indexOf('.');
|
|
2784
|
+
if (index !== -1 && (parseFixed > 3 || ignoreParseFloat)) {
|
|
2785
|
+
let decimalPart = valueString.substring(index + 1, valueString.length);
|
|
2786
|
+
let int = valueString.substring(0, index + 1);
|
|
2787
|
+
if (ignoreParseFloat) {
|
|
2788
|
+
decimalPart = decimalPart.substring(0, parseFixed);
|
|
2789
|
+
}
|
|
2790
|
+
if (lang === UtilsLanguageConstants.EN) {
|
|
2791
|
+
int = int.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
2792
|
+
return `${int}${decimalPart}`;
|
|
2793
|
+
}
|
|
2794
|
+
int = int.replace('.', ',').replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
|
2795
|
+
return `${int}${decimalPart}`;
|
|
2796
|
+
}
|
|
2797
|
+
if (index === -1 || parseFixed <= 3) {
|
|
2798
|
+
if (lang === UtilsLanguageConstants.EN) {
|
|
2799
|
+
return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
2800
|
+
}
|
|
2801
|
+
return value
|
|
2802
|
+
.toString()
|
|
2803
|
+
.replace('.', ',')
|
|
2804
|
+
.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
|
2805
|
+
}
|
|
2806
|
+
};
|
|
2807
|
+
|
|
2808
|
+
let functionXssFilter = async (value) => {
|
|
2809
|
+
return value;
|
|
2810
|
+
};
|
|
2811
|
+
const updateFunctionXssFilter = (functionCustom) => {
|
|
2812
|
+
functionXssFilter = functionCustom;
|
|
2813
|
+
};
|
|
2814
|
+
const xssFilter = async (data) => {
|
|
2815
|
+
return await functionXssFilter(data);
|
|
2816
|
+
};
|
|
2817
|
+
|
|
2818
|
+
const ENCODE_URI_PATTERN = /%([0-9A-F]{2})/g;
|
|
2819
|
+
const decodeURI = (value) => {
|
|
2820
|
+
return decodeURIComponent(value
|
|
2821
|
+
.split('')
|
|
2822
|
+
.map((c) => {
|
|
2823
|
+
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
|
2824
|
+
})
|
|
2825
|
+
.join(''));
|
|
2826
|
+
};
|
|
2827
|
+
const encodeURI = (value) => {
|
|
2828
|
+
return encodeURIComponent(value).replace(ENCODE_URI_PATTERN, (match, p1) => {
|
|
2829
|
+
return String.fromCharCode(parseInt(p1, 16));
|
|
2830
|
+
});
|
|
2831
|
+
};
|
|
2832
|
+
const endCodeUrl = (params, isBody) => {
|
|
2833
|
+
params = omitBy(params, (param) => param === '' || isNil(param));
|
|
2834
|
+
let res = '';
|
|
2835
|
+
for (const p in params) {
|
|
2836
|
+
res += `&${p}=${encodeURIComponent(params[p])}`;
|
|
2837
|
+
}
|
|
2838
|
+
return res === '' ? '' : `${isBody ? '' : '?'}${res.substring(1)}`;
|
|
2839
|
+
};
|
|
2840
|
+
|
|
2841
|
+
const base64Encode = (value) => {
|
|
2842
|
+
return btoa(encodeURI(value));
|
|
2843
|
+
};
|
|
2844
|
+
const base64Decode = (value) => {
|
|
2845
|
+
return decodeURI(atob(value));
|
|
2846
|
+
};
|
|
2847
|
+
const convertBase64ToBlob = (dataBase64) => {
|
|
2848
|
+
const BASE64_MARKER = ';base64,';
|
|
2849
|
+
if (dataBase64.indexOf(BASE64_MARKER) === -1) {
|
|
2850
|
+
const parts = dataBase64.split(',');
|
|
2851
|
+
const contentType = parts[0].split(':')[1];
|
|
2852
|
+
const raw = parts[1];
|
|
2853
|
+
const blob = new Blob([raw], { type: contentType });
|
|
2854
|
+
return blob;
|
|
2855
|
+
}
|
|
2856
|
+
const parts = dataBase64.split(BASE64_MARKER);
|
|
2857
|
+
const contentType = parts[0].split(':')[1];
|
|
2858
|
+
const raw = window.atob(parts[1]);
|
|
2859
|
+
const rawLength = raw.length;
|
|
2860
|
+
const uInt8Array = new Uint8Array(rawLength);
|
|
2861
|
+
for (let i = 0; i < rawLength; ++i) {
|
|
2862
|
+
uInt8Array[i] = raw.charCodeAt(i);
|
|
2863
|
+
}
|
|
2864
|
+
const blob = new Blob([uInt8Array], { type: contentType });
|
|
2865
|
+
return blob;
|
|
2866
|
+
};
|
|
2867
|
+
const convertFileToBase64_ObjectUrl = async (file) => {
|
|
2868
|
+
if (!file.type.match(/image.*/)) {
|
|
2869
|
+
return URL.createObjectURL(file);
|
|
2870
|
+
}
|
|
2871
|
+
return await convertFileToBase64(file);
|
|
2872
|
+
};
|
|
2873
|
+
const convertFileToBase64 = (file) => {
|
|
2874
|
+
return new Promise((resolve) => {
|
|
2875
|
+
const reader = new FileReader();
|
|
2876
|
+
reader.readAsDataURL(file);
|
|
2877
|
+
reader.onload = (event) => {
|
|
2878
|
+
resolve(event.target?.result ?? '');
|
|
2879
|
+
};
|
|
2880
|
+
});
|
|
2881
|
+
};
|
|
2882
|
+
|
|
2883
|
+
const downloadFileByUrlUseXmlRequest = (fileUrl, filename) => {
|
|
2884
|
+
const xmlRequest = new XMLHttpRequest();
|
|
2885
|
+
xmlRequest.open('GET', fileUrl, true);
|
|
2886
|
+
xmlRequest.responseType = 'blob';
|
|
2887
|
+
xmlRequest.onerror = () => {
|
|
2888
|
+
const url = window.URL.createObjectURL(xmlRequest.response);
|
|
2889
|
+
downloadFileByUrl(url, filename);
|
|
2890
|
+
};
|
|
2891
|
+
xmlRequest.onload = () => {
|
|
2892
|
+
const url = window.URL.createObjectURL(xmlRequest.response);
|
|
2893
|
+
downloadFileByUrl(url, filename);
|
|
2894
|
+
};
|
|
2895
|
+
xmlRequest.send();
|
|
2896
|
+
};
|
|
2897
|
+
const downloadFileByUrl = async (fileUrl, filename, onlyOpen) => {
|
|
2898
|
+
const downloadFileElement = document.createElement('a');
|
|
2899
|
+
if (!onlyOpen) {
|
|
2900
|
+
const response = await fetch(fileUrl);
|
|
2901
|
+
if (response.ok) {
|
|
2902
|
+
const blob = await response.blob();
|
|
2903
|
+
fileUrl = URL.createObjectURL(blob);
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
downloadFileElement.href = fileUrl;
|
|
2907
|
+
downloadFileElement.download = filename;
|
|
2908
|
+
downloadFileElement.target = '_blank';
|
|
2909
|
+
downloadFileElement.click();
|
|
2910
|
+
downloadFileElement.remove();
|
|
2911
|
+
};
|
|
2912
|
+
const downloadImageFromELement = (imageElement, typeFileDownload, nameFile) => {
|
|
2913
|
+
const parentElement = imageElement?.src;
|
|
2914
|
+
const blobData = convertBase64ToBlob(parentElement);
|
|
2915
|
+
const blob = new Blob([blobData], { type: typeFileDownload || 'image/png' });
|
|
2916
|
+
const url = window.URL.createObjectURL(blob);
|
|
2917
|
+
const link = document.createElement('a');
|
|
2918
|
+
link.href = url;
|
|
2919
|
+
link.download = nameFile || 'barcode';
|
|
2920
|
+
link.click();
|
|
2921
|
+
};
|
|
2922
|
+
|
|
2923
|
+
const LINK_IMAGE_ERROR_TOKEN_INJECT = new InjectionToken('LINK_IMAGE_ERROR_TOKEN_INJECT');
|
|
2924
|
+
const PROCESS_BAR_STANDARD_CONFIG_DEFAULT_TOKEN_INJECT = new InjectionToken('PROCESS_BAR_STANDARD_CONFIG_DEFAULT_TOKEN_INJECT');
|
|
2925
|
+
const PROCESS_BAR_STEPS_CONFIG_DEFAULT_TOKEN_INJECT = new InjectionToken('PROCESS_BAR_STEPS_CONFIG_DEFAULT_TOKEN_INJECT');
|
|
2926
|
+
|
|
2927
|
+
const isTypeImage = (file) => (file.type.match(/image.*/) ? true : false);
|
|
2928
|
+
const isTypeVideo = (file) => (file.type.match(/video.*/) ? true : false);
|
|
2929
|
+
const isTypeAudio = (file) => (file.type.match(/audio.*/) ? true : false);
|
|
2930
|
+
const isTypeFile = (file) => (file instanceof File || Object.prototype.toString.call(file) === '[object File]' ? true : false);
|
|
2931
|
+
const ExcelExtList = ['xls', 'xlsx', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'];
|
|
2932
|
+
const DocumentExtList = [
|
|
2933
|
+
'doc',
|
|
2934
|
+
'docx',
|
|
2935
|
+
'xls',
|
|
2936
|
+
'xlsx',
|
|
2937
|
+
'ppt',
|
|
2938
|
+
'pptx',
|
|
2939
|
+
'pdf',
|
|
2940
|
+
'json',
|
|
2941
|
+
'xml',
|
|
2942
|
+
'application/msword',
|
|
2943
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
2944
|
+
'application/vnd.ms-excel',
|
|
2945
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
2946
|
+
'application/vnd.ms-powerpoint',
|
|
2947
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
2948
|
+
'application/pdf',
|
|
2949
|
+
'application/json',
|
|
2950
|
+
'application/xml',
|
|
2951
|
+
];
|
|
2952
|
+
const isIncludeDocumentExtList = (ext, listExt = DocumentExtList) => listExt.includes(ext) || listExt.includes(`application/${ext}`);
|
|
2953
|
+
const ImageExtList = ['gif', 'jpg', 'jpeg', 'png', 'image/gif', 'image/jpeg', 'image/jpeg', 'image/png'];
|
|
2954
|
+
const isIncludeImageExtList = (ext, listExt = ImageExtList) => listExt.includes(ext);
|
|
2955
|
+
const VideoExtList = ['mp4', 'mov', 'mpg', 'avi', 'wmv', 'video/mp4', 'video/quicktime', 'video/mpeg', 'video/x-msvideo', 'video/x-ms-wmv'];
|
|
2956
|
+
const isIncludeVideoExtList = (ext, listExt = VideoExtList) => listExt.includes(ext);
|
|
2957
|
+
const AudioExtList = ['mp3', 'wav', 'ogg', 'aac', 'm4a', 'flac', 'wma', 'audio/mpeg', 'audio/wav', 'audio/ogg', 'audio/aac', 'audio/mp4', 'audio/flac', 'audio/x-ms-wma'];
|
|
2958
|
+
const isIncludeAudioExtList = (ext, listExt = AudioExtList) => listExt.includes(ext);
|
|
2959
|
+
const getFileExtension = (file) => {
|
|
2960
|
+
if (file instanceof File && file.type) {
|
|
2961
|
+
return (file.type.split('/').pop() || '').toLowerCase();
|
|
2962
|
+
}
|
|
2963
|
+
file = file;
|
|
2964
|
+
if (file.mimetype || file.file?.type) {
|
|
2965
|
+
return file.mimetype || file.file?.type;
|
|
2966
|
+
}
|
|
2967
|
+
let fileName = file.name;
|
|
2968
|
+
const url = get(file, 'url') || get(file, 'origin_url');
|
|
2969
|
+
if (!fileName && !url) {
|
|
2970
|
+
return;
|
|
2971
|
+
}
|
|
2972
|
+
if (!fileName && url) {
|
|
2973
|
+
fileName = url.split('/').pop() || '';
|
|
2974
|
+
set(file, 'name', fileName);
|
|
2975
|
+
}
|
|
2976
|
+
const dots = fileName.split('.');
|
|
2977
|
+
if (!dots) {
|
|
2978
|
+
return;
|
|
2979
|
+
}
|
|
2980
|
+
return (dots.pop() || '').toLowerCase();
|
|
2981
|
+
};
|
|
2982
|
+
const getLabelBySizeFile = (size, toFixed = 2) => {
|
|
2983
|
+
if (size < 1024 * 1024) {
|
|
2984
|
+
return ` ${(size / 1024).toFixed(toFixed)} KB`;
|
|
2985
|
+
}
|
|
2986
|
+
return ` ${(size / (1024 * 1024)).toFixed(toFixed)} MB`;
|
|
2987
|
+
};
|
|
2988
|
+
const convertBlobToFile = (blob, fileName) => {
|
|
2989
|
+
const type = blob.type.split('/')[1];
|
|
2990
|
+
const name = fileName ? `${fileName.split('.')[0]}.${type}` : `${uuid()}.${type}`;
|
|
2991
|
+
return new File([blob], name, { type: blob.type, lastModified: Date.now() });
|
|
2992
|
+
};
|
|
2993
|
+
const convertUrlToFile = (url, fileName) => {
|
|
2994
|
+
return new Promise((resolve, reject) => {
|
|
2995
|
+
const xhr = new XMLHttpRequest();
|
|
2996
|
+
xhr.onload = function () {
|
|
2997
|
+
const reader = new FileReader();
|
|
2998
|
+
reader.onloadend = () => {
|
|
2999
|
+
const file = convertBase64ToBlob(reader.result);
|
|
3000
|
+
resolve(convertBlobToFile(file, fileName || Date.now().toLocaleString()));
|
|
3001
|
+
};
|
|
3002
|
+
reader.onerror = () => resolve(undefined);
|
|
3003
|
+
reader.readAsDataURL(xhr.response);
|
|
3004
|
+
};
|
|
3005
|
+
xhr.onerror = (err) => {
|
|
3006
|
+
reject(err);
|
|
3007
|
+
};
|
|
3008
|
+
xhr.open('GET', url);
|
|
3009
|
+
xhr.responseType = 'blob';
|
|
3010
|
+
xhr.send();
|
|
3011
|
+
});
|
|
3012
|
+
};
|
|
3013
|
+
|
|
3014
|
+
const DEFAULT_MIN_TICK_COUNT = 5;
|
|
3015
|
+
const DEFAULT_MAX_TICK_COUNT = 10;
|
|
3016
|
+
const MIN_DISTANCE_TO_ZERO = 3;
|
|
3017
|
+
const NEGATIVE_THRESHOLD = -5;
|
|
3018
|
+
const FALLBACK_NEGATIVE_VALUE = -4;
|
|
3019
|
+
const MAX_POW_NUMBER = 14;
|
|
3020
|
+
const MAX_TEMPLATE_NUMBER = 10;
|
|
3021
|
+
/**
|
|
3022
|
+
* Tính toán smart axis scale cho biểu đồ
|
|
3023
|
+
*
|
|
3024
|
+
* @param maxData - Giá trị tối đa của dữ liệu
|
|
3025
|
+
* @param options - Các tùy chọn cấu hình axis scale
|
|
3026
|
+
* @returns Cấu hình axis scale bao gồm stepSize, max, min, tickAmount
|
|
3027
|
+
*
|
|
3028
|
+
* @throws {Error} INVALID_NEGATIVE_DATA - khi maxData < 0 mà acceptNegative = false
|
|
3029
|
+
* @throws {Error} MISSING_MIN_NEGATIVE - khi acceptNegative = true nhưng thiếu minNegative
|
|
3030
|
+
* @throws {Error} INVALID_RANGE - khi maxData < minNegative
|
|
3031
|
+
* @throws {Error} INVALID_MIN_NEGATIVE - khi minNegative >= 0
|
|
3032
|
+
*
|
|
3033
|
+
* @example
|
|
3034
|
+
* ```typescript
|
|
3035
|
+
* const result = getSmartAxisScale(100, { minTickCount: 5, maxTickCount: 10 });
|
|
3036
|
+
* // returns { stepSize: 20, max: 120, min: 0, tickAmount: 6 }
|
|
3037
|
+
* ```
|
|
3038
|
+
*/
|
|
3039
|
+
const getSmartAxisScale = (originalMaxData, options) => {
|
|
3040
|
+
let maxData = originalMaxData;
|
|
3041
|
+
validateInputs(maxData, options);
|
|
3042
|
+
const minTickCount = options?.minTickCount || DEFAULT_MIN_TICK_COUNT;
|
|
3043
|
+
const maxTickCount = options?.maxTickCount || DEFAULT_MAX_TICK_COUNT;
|
|
3044
|
+
let scaleDirection = 1;
|
|
3045
|
+
let rangeDistance = maxData;
|
|
3046
|
+
if (options?.acceptNegative && !isNil(options.minNegative)) {
|
|
3047
|
+
if (maxData === 0) {
|
|
3048
|
+
maxData = -1;
|
|
3049
|
+
}
|
|
3050
|
+
if (maxData <= 0) {
|
|
3051
|
+
rangeDistance = options.minNegative;
|
|
3052
|
+
}
|
|
3053
|
+
if (rangeDistance > NEGATIVE_THRESHOLD) {
|
|
3054
|
+
rangeDistance = FALLBACK_NEGATIVE_VALUE;
|
|
3055
|
+
}
|
|
3056
|
+
if (maxData > 0) {
|
|
3057
|
+
rangeDistance = maxData + Math.abs(options.minNegative);
|
|
3058
|
+
scaleDirection = -1;
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
if (Math.abs(rangeDistance) < MIN_DISTANCE_TO_ZERO) {
|
|
3062
|
+
rangeDistance = MIN_DISTANCE_TO_ZERO;
|
|
3063
|
+
}
|
|
3064
|
+
const stepCandidates = getStepCandidates(maxData, minTickCount, options?.minNegative || 0, options?.acceptStepIsTypeFloat, options?.stepCandidates, options?.acceptNegative);
|
|
3065
|
+
for (const step of stepCandidates) {
|
|
3066
|
+
let tickCount = Math.abs(Math.ceil(rangeDistance / step)) + (scaleDirection === -1 ? 2 : 1);
|
|
3067
|
+
let maxValue = maxData <= 0 ? 0 : step * tickCount * scaleDirection;
|
|
3068
|
+
let minValue = 0;
|
|
3069
|
+
if (tickCount >= minTickCount && tickCount <= maxTickCount) {
|
|
3070
|
+
if (maxData < maxValue) {
|
|
3071
|
+
if (options?.acceptNegative) {
|
|
3072
|
+
let tick = 1;
|
|
3073
|
+
while (!isNil(options.minNegative) && tick <= tickCount && minValue >= options.minNegative) {
|
|
3074
|
+
minValue = tick * step;
|
|
3075
|
+
tick++;
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
const maxValuePositive = maxValue - Math.abs(minValue);
|
|
3079
|
+
tickCount = maxValuePositive - originalMaxData > Math.abs(step) && tickCount - 1 >= minTickCount ? tickCount - 1 : tickCount;
|
|
3080
|
+
maxValue = step * tickCount * scaleDirection - minValue * scaleDirection;
|
|
3081
|
+
return {
|
|
3082
|
+
stepSize: Math.abs(step),
|
|
3083
|
+
max: maxValue,
|
|
3084
|
+
min: minValue,
|
|
3085
|
+
tickAmount: tickCount,
|
|
3086
|
+
};
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
let step = Math.ceil(rangeDistance / minTickCount) || 1;
|
|
3091
|
+
let maxValue = step * minTickCount;
|
|
3092
|
+
if (rangeDistance === maxValue) {
|
|
3093
|
+
step = step + Math.ceil(step / 10);
|
|
3094
|
+
maxValue = step * minTickCount;
|
|
3095
|
+
}
|
|
3096
|
+
return {
|
|
3097
|
+
stepSize: Math.abs(step),
|
|
3098
|
+
max: maxValue,
|
|
3099
|
+
min: 0,
|
|
3100
|
+
tickAmount: minTickCount,
|
|
3101
|
+
};
|
|
3102
|
+
};
|
|
3103
|
+
// Cache cho các giá trị lũy thừa 10 để tối ưu hiệu suất
|
|
3104
|
+
const POWER_CACHE = new Map();
|
|
3105
|
+
/**
|
|
3106
|
+
* Tạo danh sách các step candidates cho việc tính toán scale
|
|
3107
|
+
* @param maxData - Giá trị dữ liệu tối đa
|
|
3108
|
+
* @param minStep - Số bước tối thiểu
|
|
3109
|
+
* @param minNegative - Giá trị âm tối thiểu
|
|
3110
|
+
* @param acceptStepIsTypeFloat - Có chấp nhận step thập phân không
|
|
3111
|
+
* @param stepCandidatesByOptions - Step candidates do người dùng cung cấp
|
|
3112
|
+
* @param includeNegativeSteps - Có bao gồm các step âm không
|
|
3113
|
+
* @returns Danh sách các step candidates
|
|
3114
|
+
*/
|
|
3115
|
+
const getStepCandidates = (maxData, minStep, minNegative, acceptStepIsTypeFloat = false, stepCandidatesByOptions, includeNegativeSteps = false) => {
|
|
3116
|
+
// Nếu có step candidates tùy chỉnh, sử dụng chúng
|
|
3117
|
+
if (stepCandidatesByOptions && stepCandidatesByOptions.length > 0) {
|
|
3118
|
+
return stepCandidatesByOptions;
|
|
3119
|
+
}
|
|
3120
|
+
const stepCandidates = new Array();
|
|
3121
|
+
const maxValueStep = Math.abs(Math.max(maxData, Math.abs(minNegative || 0))) / minStep;
|
|
3122
|
+
// Tạo step candidates dựa trên lũy thừa của 10
|
|
3123
|
+
for (let powNumber = 0; powNumber <= MAX_POW_NUMBER; powNumber++) {
|
|
3124
|
+
// Sử dụng cache để tối ưu hiệu suất
|
|
3125
|
+
if (!POWER_CACHE.has(powNumber)) {
|
|
3126
|
+
POWER_CACHE.set(powNumber, Math.pow(10, powNumber));
|
|
3127
|
+
}
|
|
3128
|
+
const powValue = POWER_CACHE.get(powNumber);
|
|
3129
|
+
for (let templateNumber = 1; templateNumber < MAX_TEMPLATE_NUMBER; templateNumber++) {
|
|
3130
|
+
// Thêm step thập phân nếu được chấp nhận
|
|
3131
|
+
if (acceptStepIsTypeFloat) {
|
|
3132
|
+
const step = powValue * (((templateNumber - 1) * 2 + 1) / 2);
|
|
3133
|
+
stepCandidates.push(step);
|
|
3134
|
+
}
|
|
3135
|
+
const step = powValue * templateNumber;
|
|
3136
|
+
stepCandidates.push(step);
|
|
3137
|
+
// Dừng khi step đã đủ lớn
|
|
3138
|
+
if (step >= maxValueStep) {
|
|
3139
|
+
checkAndSetNegativeSteps(stepCandidates, includeNegativeSteps, minNegative);
|
|
3140
|
+
return stepCandidates;
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
checkAndSetNegativeSteps(stepCandidates, includeNegativeSteps, minNegative);
|
|
3145
|
+
return stepCandidates;
|
|
3146
|
+
};
|
|
3147
|
+
/**
|
|
3148
|
+
* Kiểm tra và thêm các step âm vào danh sách candidates nếu cần
|
|
3149
|
+
* @param stepCandidates - Danh sách step candidates hiện tại
|
|
3150
|
+
* @param acceptNegative - Có chấp nhận giá trị âm không
|
|
3151
|
+
* @param minNegative - Giá trị âm tối thiểu
|
|
3152
|
+
*/
|
|
3153
|
+
const checkAndSetNegativeSteps = (stepCandidates, acceptNegative, minNegative) => {
|
|
3154
|
+
if (acceptNegative && minNegative < 0) {
|
|
3155
|
+
// Tạo các step âm và thêm vào đầu danh sách
|
|
3156
|
+
const negativeSteps = [...stepCandidates].map((step) => -step);
|
|
3157
|
+
stepCandidates.unshift(...negativeSteps);
|
|
3158
|
+
}
|
|
3159
|
+
};
|
|
3160
|
+
/**
|
|
3161
|
+
* Kiểm tra tính hợp lệ của các tham số đầu vào
|
|
3162
|
+
* @param maxData - Giá trị dữ liệu tối đa
|
|
3163
|
+
* @param options - Các tùy chọn cấu hình
|
|
3164
|
+
* @throws {Error} Khi các tham số không hợp lệ
|
|
3165
|
+
*/
|
|
3166
|
+
const validateInputs = (maxData, options) => {
|
|
3167
|
+
// Kiểm tra maxData âm khi không chấp nhận giá trị âm
|
|
3168
|
+
if (maxData < 0 && !options?.acceptNegative) {
|
|
3169
|
+
throw new Error('maxData is less than 0 and acceptNegative is false');
|
|
3170
|
+
}
|
|
3171
|
+
if (options?.acceptNegative) {
|
|
3172
|
+
// Kiểm tra minNegative có được cung cấp không
|
|
3173
|
+
if (isNil(options.minNegative)) {
|
|
3174
|
+
throw new Error('minNegative is required when acceptNegative is true');
|
|
3175
|
+
}
|
|
3176
|
+
// Kiểm tra maxData phải >= minNegative
|
|
3177
|
+
if (maxData < options.minNegative) {
|
|
3178
|
+
throw new Error('maxData is less than minNegative');
|
|
3179
|
+
}
|
|
3180
|
+
// Kiểm tra minNegative phải là số âm
|
|
3181
|
+
if (options.minNegative >= 0) {
|
|
3182
|
+
throw new Error('minNegative must be negative');
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
};
|
|
3186
|
+
|
|
3187
|
+
const protectString = (input) => {
|
|
3188
|
+
const xorKey = 77;
|
|
3189
|
+
const encoded = input
|
|
3190
|
+
.split('')
|
|
3191
|
+
.map((char) => String.fromCharCode(char.charCodeAt(0) ^ xorKey))
|
|
3192
|
+
.reverse()
|
|
3193
|
+
.join('');
|
|
3194
|
+
return btoa(encoded);
|
|
3195
|
+
};
|
|
3196
|
+
const revealString = (encoded) => {
|
|
3197
|
+
const xorKey = 77;
|
|
3198
|
+
const base = atob(encoded);
|
|
3199
|
+
const decoded = base
|
|
3200
|
+
.split('')
|
|
3201
|
+
.reverse()
|
|
3202
|
+
.map((c) => String.fromCharCode(c.charCodeAt(0) ^ xorKey))
|
|
3203
|
+
.join('');
|
|
3204
|
+
return decoded;
|
|
3205
|
+
};
|
|
3206
|
+
const createUniqueRandomIntGenerator = (min, max) => {
|
|
3207
|
+
if (max - min + 1 < min + 10) {
|
|
3208
|
+
throw new Error(`Range too small: need at least min + 10 unique values`);
|
|
3209
|
+
}
|
|
3210
|
+
const history = [];
|
|
3211
|
+
return () => {
|
|
3212
|
+
let num;
|
|
3213
|
+
let loop = 0;
|
|
3214
|
+
do {
|
|
3215
|
+
num = Math.floor(Math.random() * (max - min + 1)) + min;
|
|
3216
|
+
loop++;
|
|
3217
|
+
if (loop > 1000) {
|
|
3218
|
+
break;
|
|
3219
|
+
}
|
|
3220
|
+
} while (history.includes(num));
|
|
3221
|
+
history.push(num);
|
|
3222
|
+
if (history.length > 10) {
|
|
3223
|
+
history.shift();
|
|
3224
|
+
}
|
|
3225
|
+
return num;
|
|
3226
|
+
};
|
|
3227
|
+
};
|
|
3228
|
+
|
|
3229
|
+
/**
|
|
3230
|
+
* Generated bundle index. Do not edit.
|
|
3231
|
+
*/
|
|
3232
|
+
|
|
3233
|
+
export { AudioExtList, CHARACTER_DATA_EMPTY, COMMUNICATE_MICRO_KEY_GET_ALL_MESSAGE, COMMUNICATE_MICRO_PREFIX_TYPE, DEFAULT_START_PAGE_0, DocumentExtList, ENCODE_URI_PATTERN, ERROR_MESSAGE_EMPTY_VALID, ERROR_MESSAGE_MAX_LENGTH, ERROR_MESSAGE_MAX_VALID, ERROR_MESSAGE_MIN_LENGTH, ERROR_MESSAGE_MIN_VALID, ERROR_MESSAGE_PATTEN_VALID, ExcelExtList, ImageExtList, LINK_IMAGE_ERROR_TOKEN_INJECT, PROCESS_BAR_STANDARD_CONFIG_DEFAULT_TOKEN_INJECT, PROCESS_BAR_STEPS_CONFIG_DEFAULT_TOKEN_INJECT, UtilsCache, UtilsCommunicateMicro, UtilsCommunicateMicroKeyGlobal, UtilsHttpParamsRequest, UtilsHttpParamsRequestInstance, UtilsKeyCodeConstant, UtilsLanguageConstants, UtilsUrlSearchParams, VideoExtList, base64Decode, base64Encode, capitalize, checkMouseOverInContainer, checkViewInScreen, cloneDeep, cloneIBoundingClientRect, colorContrastFromOrigin, colorStepContrastFromOrigin, convertBase64ToBlob, convertBlobToFile, convertFileToBase64, convertFileToBase64_ObjectUrl, convertHtmlToDivBlocks, convertObjectToSignal, convertSignalToObject, convertUrlToFile, createUniqueRandomIntGenerator, decodeEscapeHtml, decodeURI, decrypt, decrypt3rd, deleteUnicode, downloadFileByUrl, downloadFileByUrlUseXmlRequest, downloadImageFromELement, encodeURI, encrypt, encrypt3rd, endCodeUrl, escapeHtml, firstLetterToUpperCase, formatDate, formatNumber, formatTextCompare, fullNameFormat, generateInterface, get, getColorById, getDayjs, getDeltaFromHTML, getDeviceInfo, getDocumentByString, getDragEventByElement, getEventNameHandleClick, getFileExtension, getHTMLFromQuill, getKeyCacheByArrayObject, getLabelBySizeFile, getPlatFromBrowser, getSmartAxisScale, getViewport, groupBy, hasDangerousConstructorName, highlightByKeyword, insertContentWithRange, isArray, isAsyncObject, isBrowserAPIObject, isBrowserGlobalObject, isBuiltInObject, isDOMObject, isDangerousObject, isDayjsObject, isDifferenceDay, isDifferenceMonth, isDifferenceYear, isEmbedFrame, isEmpty, isEqual, isFalsy, isFile, isFrameworkObject, isIncludeAudioExtList, isIncludeDocumentExtList, isIncludeImageExtList, isIncludeVideoExtList, isMap, isNil, isRegExp, isReturnAsIsObject, isSafeToProcess, isSet, isSkippableObject, isSpecialObject, isTruthy, isTypeAudio, isTypeFile, isTypeImage, isTypeVideo, keyBy, listColorDefine, md5, omitBy, patternAccount, patternDomain, patternEmail, patternEmoji, patternEncodeUri, patternGetFieldByRuleFieldReplace, patternHostUrl, patternKey, patternMobilePhone, patternName, patternNameProfile, patternNameSpecial, patternNameUtf8, patternNumber, patternPem, patternPhone, patternPhoneNormal, patternRuleFieldReplace, patternTax, patternUrl, processPasteData, protectString, range, removeEmoji, revealString, rgbToHex, set, setCaretPosition, setDefaultTimeZone, setKeyCrypto, setKeyCrypto3rd, setStylesElement, uniqBy, unwrapSignal, updateFunctionCheckEmbedFrame, updateFunctionFormatDate, updateFunctionXssFilter, uppercaseByPosition, uuid, viewDataNumberByLanguage, xssFilter };
|
|
3234
|
+
//# sourceMappingURL=libs-ui-utils.mjs.map
|