@arex95/vue-core 1.0.6 → 1.0.7
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/dist/composables/auth/useAuth.d.ts +29 -0
- package/dist/config/axios/axiosConfig.d.ts +3 -5
- package/dist/config/axios/axiosInstance.d.ts +18 -1
- package/dist/config/global/endpointsConfig.d.ts +24 -0
- package/dist/config/global/index.d.ts +2 -0
- package/dist/config/global/tokenConfig.d.ts +15 -0
- package/dist/config/index.d.ts +1 -0
- package/dist/constants/errorsEnum.d.ts +3 -0
- package/dist/constants/index.d.ts +1 -0
- package/dist/index.mjs +2311 -2071
- package/dist/rest/RestStd.d.ts +22 -5
- package/dist/types/ErrorType.d.ts +8 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/utils/errors.d.ts +10 -0
- package/package.json +17 -12
package/dist/index.mjs
CHANGED
|
@@ -1,2460 +1,2700 @@
|
|
|
1
|
+
import { useTimeoutFn, useBreakpoints, breakpointsTailwind, useWindowSize } from '@vueuse/core';
|
|
1
2
|
import axios from 'axios';
|
|
3
|
+
import { useRouter } from 'vue-router';
|
|
2
4
|
import { useQuery } from '@tanstack/vue-query';
|
|
3
5
|
import { ref, watch, onServerPrefetch, onMounted, computed } from 'vue';
|
|
4
|
-
import { useBreakpoints, breakpointsTailwind, useWindowSize, useTimeoutFn } from '@vueuse/core';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return this.fetchComposable({
|
|
37
|
-
method: 'GET',
|
|
38
|
-
url: `${this.resource}/${id}`,
|
|
39
|
-
params,
|
|
40
|
-
}, options);
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Create a new item on the server.
|
|
44
|
-
*
|
|
45
|
-
* @param data The data for the new item to create.
|
|
46
|
-
* @param options Additional options for the fetch composable.
|
|
47
|
-
* @returns The result of the fetch composable (typically a promise).
|
|
48
|
-
*/
|
|
49
|
-
static create(data, options = {}) {
|
|
50
|
-
return this.fetchComposable({
|
|
51
|
-
method: 'POST',
|
|
52
|
-
url: this.resource,
|
|
53
|
-
data,
|
|
54
|
-
}, options);
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Update an existing item on the server.
|
|
58
|
-
*
|
|
59
|
-
* @param id The ID of the item to update.
|
|
60
|
-
* @param data The updated data for the item.
|
|
61
|
-
* @param options Additional options for the fetch composable.
|
|
62
|
-
* @returns The result of the fetch composable (typically a promise).
|
|
63
|
-
*/
|
|
64
|
-
static update(id, data, options = {}) {
|
|
65
|
-
return this.fetchComposable({
|
|
66
|
-
method: 'PUT',
|
|
67
|
-
url: `${this.resource}/${id}`,
|
|
68
|
-
data,
|
|
69
|
-
}, options);
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Partially update an existing item on the server.
|
|
73
|
-
*
|
|
74
|
-
* @param id The ID of the item to update.
|
|
75
|
-
* @param data The updated data for the item.
|
|
76
|
-
* @param options Additional options for the fetch composable.
|
|
77
|
-
* @returns The result of the fetch composable (typically a promise).
|
|
78
|
-
*/
|
|
79
|
-
static patch(id, data, options = {}) {
|
|
80
|
-
return this.fetchComposable({
|
|
81
|
-
method: 'PATCH',
|
|
82
|
-
url: `${this.resource}/${id}`,
|
|
83
|
-
data,
|
|
84
|
-
}, options);
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Delete an item from the server.
|
|
88
|
-
*
|
|
89
|
-
* @param id The ID of the item to delete.
|
|
90
|
-
* @param options Additional options for the fetch composable.
|
|
91
|
-
* @returns The result of the fetch composable (typically a promise).
|
|
92
|
-
*/
|
|
93
|
-
static delete(id, options = {}) {
|
|
94
|
-
return this.fetchComposable({
|
|
95
|
-
method: 'DELETE',
|
|
96
|
-
url: `${this.resource}/${id}`,
|
|
97
|
-
}, options);
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Custom request method for more flexibility.
|
|
101
|
-
*
|
|
102
|
-
* @param method HTTP method (GET, POST, etc.).
|
|
103
|
-
* @param params Query parameters.
|
|
104
|
-
* @param data Request body data.
|
|
105
|
-
* @param token Authorization token (optional).
|
|
106
|
-
* @param options Additional options for the fetch composable.
|
|
107
|
-
* @returns The result of the fetch composable (typically a promise).
|
|
108
|
-
*/
|
|
109
|
-
static customRequest(method, params, data, options = {}) {
|
|
110
|
-
return this.fetchComposable({
|
|
111
|
-
method: method,
|
|
112
|
-
url: this.resource,
|
|
113
|
-
params: params,
|
|
114
|
-
data: data,
|
|
115
|
-
}, options);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Custom composable function for making API requests with Axios.
|
|
121
|
-
*
|
|
122
|
-
* @template T The expected return type of the API request.
|
|
123
|
-
* @param {AxiosInstance} axios - The Axios instance for making HTTP requests.
|
|
124
|
-
* @param {AxiosRequestConfig} axiosRequest - Configuration for the Axios request.
|
|
125
|
-
* @returns {Promise<T>} A promise with the result of the API request.
|
|
126
|
-
*/
|
|
127
|
-
async function axiosFetch(axios, axiosRequest) {
|
|
128
|
-
return axios(axiosRequest)
|
|
129
|
-
.then(response => response.data)
|
|
130
|
-
.catch(error => {
|
|
131
|
-
throw error;
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* AxiosService class encapsulates the Axios configuration and logic.
|
|
137
|
-
* It manages request and response interceptors and provides methods for making HTTP requests.
|
|
138
|
-
*/
|
|
139
|
-
class AxiosService {
|
|
140
|
-
instance;
|
|
141
|
-
cancelTokenSource;
|
|
142
|
-
activeRequests = 0; // Track active requests
|
|
143
|
-
/**
|
|
144
|
-
* Initializes the AxiosService instance by creating an Axios instance with default configuration
|
|
145
|
-
* and setting up request and response interceptors.
|
|
146
|
-
*/
|
|
147
|
-
constructor(url) {
|
|
148
|
-
this.cancelTokenSource = axios.CancelToken.source();
|
|
149
|
-
this.instance = axios.create({
|
|
150
|
-
baseURL: url ?? '',
|
|
151
|
-
timeout: 300000,
|
|
152
|
-
headers: {
|
|
153
|
-
Accept: 'application/json',
|
|
154
|
-
'Content-Type': 'application/json',
|
|
155
|
-
},
|
|
156
|
-
withCredentials: false,
|
|
157
|
-
});
|
|
158
|
-
this.initializeInterceptors();
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Initializes request and response interceptors for the Axios instance.
|
|
162
|
-
*/
|
|
163
|
-
initializeInterceptors() {
|
|
164
|
-
this.instance.interceptors.request.use((config) => {
|
|
165
|
-
const token = typeof window !== 'undefined' ? localStorage.getItem('authToken') : null;
|
|
166
|
-
if (token && config.headers) {
|
|
167
|
-
config.headers.Authorization = `Bearer ${token}`;
|
|
168
|
-
}
|
|
169
|
-
config.cancelToken = this.cancelTokenSource.token;
|
|
170
|
-
this.activeRequests++;
|
|
171
|
-
return config;
|
|
172
|
-
}, (error) => {
|
|
173
|
-
// handleError(error);
|
|
174
|
-
console.error('Request error:', error.message);
|
|
175
|
-
this.activeRequests++;
|
|
176
|
-
return Promise.reject(error);
|
|
177
|
-
});
|
|
178
|
-
this.instance.interceptors.response.use((response) => {
|
|
179
|
-
this.activeRequests--;
|
|
180
|
-
return response;
|
|
181
|
-
}, (error) => {
|
|
182
|
-
if (axios.isCancel(error)) {
|
|
183
|
-
console.warn('Request canceled:', error.message);
|
|
184
|
-
}
|
|
185
|
-
else if (axios.isAxiosError(error)) {
|
|
186
|
-
// handleError(error);
|
|
187
|
-
console.error('Response error:', error.response?.status, error.message);
|
|
188
|
-
if (error.response?.status === 401) {
|
|
189
|
-
this.handleUnauthorized();
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
console.error('Unexpected error:', error);
|
|
194
|
-
}
|
|
195
|
-
this.activeRequests--;
|
|
196
|
-
return Promise.reject(error);
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* Returns the number of active requests.
|
|
201
|
-
*/
|
|
202
|
-
getActiveRequests() {
|
|
203
|
-
return this.activeRequests;
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* Handles unauthorized access errors (401).
|
|
207
|
-
*/
|
|
208
|
-
handleUnauthorized() {
|
|
209
|
-
console.warn('Unauthorized access - redirecting to login.');
|
|
210
|
-
}
|
|
211
|
-
/**
|
|
212
|
-
* Returns the Axios instance with the configured settings and interceptors.
|
|
213
|
-
* @returns {AxiosInstance} The configured Axios instance.
|
|
214
|
-
*/
|
|
215
|
-
getAxiosInstance() {
|
|
216
|
-
return this.instance;
|
|
217
|
-
}
|
|
218
|
-
/**
|
|
219
|
-
* Cancels all ongoing requests.
|
|
220
|
-
*/
|
|
221
|
-
cancelAllRequests() {
|
|
222
|
-
this.cancelTokenSource.cancel('Operation canceled by the user.');
|
|
223
|
-
this.cancelTokenSource = axios.CancelToken.source();
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* Sets a new header for the Axios instance.
|
|
227
|
-
* @param {string} key - The header key.
|
|
228
|
-
* @param {string} value - The header value.
|
|
229
|
-
*/
|
|
230
|
-
setHeader(key, value) {
|
|
231
|
-
this.instance.defaults.headers.common[key] = value;
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Removes a header from the Axios instance.
|
|
235
|
-
* @param {string} key - The header key to remove.
|
|
236
|
-
*/
|
|
237
|
-
removeHeader(key) {
|
|
238
|
-
delete this.instance.defaults.headers.common[key];
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
let axiosInstance;
|
|
243
|
-
const configureAxios = (baseURL) => {
|
|
244
|
-
axiosInstance = new AxiosService(baseURL);
|
|
245
|
-
};
|
|
246
|
-
const getAxiosInstance = () => {
|
|
247
|
-
if (!axiosInstance) {
|
|
248
|
-
throw new Error('Axios instance not configured. Call configureAxios first.');
|
|
249
|
-
}
|
|
250
|
-
return axiosInstance.getAxiosInstance();
|
|
251
|
-
};
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Creates a fetchComposable for Axios requests with an optional custom instance.
|
|
255
|
-
* @param fetchComposable - The composable to use for the request (can be any fetch function).
|
|
256
|
-
* @param axiosCustomInstance - An optional custom Axios instance to use for the request.
|
|
257
|
-
* @returns A function that handles the request using the provided composable.
|
|
258
|
-
*/
|
|
259
|
-
function createFetch(fetchComposable, axiosCustomInstance) {
|
|
260
|
-
const instance = axiosCustomInstance || getAxiosInstance();
|
|
261
|
-
return (axiosRequestConfig, options) => {
|
|
262
|
-
return fetchComposable(instance, axiosRequestConfig, options);
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Custom composable for integrating Axios requests with Vue Query.
|
|
268
|
-
*
|
|
269
|
-
* @template T - The type of data expected from the query.
|
|
270
|
-
* @param {AxiosInstance} axios - Axios instance used for making HTTP requests.
|
|
271
|
-
* @param {AxiosRequestConfig} axiosRequest - Initial Axios request configuration.
|
|
272
|
-
* @param {ExtendedQueryOptions<T>} [queryOptions] - Optional Vue Query options with server execution flag.
|
|
273
|
-
* @returns {object} Composable functions and query state for managing API requests.
|
|
274
|
-
*/
|
|
275
|
-
function useVueQuery(axios, axiosRequest, queryOptions) {
|
|
276
|
-
const key = ref([
|
|
277
|
-
queryOptions?.queryKey || `${axiosRequest.url}-${JSON.stringify(axiosRequest)}`
|
|
278
|
-
]);
|
|
279
|
-
const axiosRequestRef = ref(axiosRequest);
|
|
280
|
-
const queryOptionsRef = ref(queryOptions);
|
|
281
|
-
const isInitialized = ref(false);
|
|
282
|
-
const errorCallbacks = ref([]);
|
|
283
|
-
const resultCallbacks = ref([]);
|
|
284
|
-
/**
|
|
285
|
-
* Vue Query instance for managing API requests.
|
|
286
|
-
*/
|
|
287
|
-
const query = useQuery({
|
|
288
|
-
queryKey: key.value,
|
|
289
|
-
queryFn: async () => {
|
|
290
|
-
const response = await axios(axiosRequestRef.value);
|
|
291
|
-
return response.data;
|
|
292
|
-
},
|
|
293
|
-
...(queryOptionsRef.value?.options ?? {})
|
|
294
|
-
});
|
|
295
|
-
/**
|
|
296
|
-
* Executes the query manually with optional new key and request parameters.
|
|
297
|
-
*
|
|
298
|
-
* @param {Array<any>} [newKey] - New query key to use.
|
|
299
|
-
* @param {object} [newRequestParams] - New request parameters to use.
|
|
300
|
-
* @param {ExtendedQueryOptions<T>} [newQueryOptions] - New Vue Query options to use.
|
|
301
|
-
*/
|
|
302
|
-
function execute(newKey, newRequestParams, newQueryOptions) {
|
|
303
|
-
if (newKey) {
|
|
304
|
-
key.value = newKey;
|
|
305
|
-
}
|
|
306
|
-
if (newRequestParams) {
|
|
307
|
-
axiosRequestRef.value.data = newRequestParams;
|
|
308
|
-
}
|
|
309
|
-
if (newQueryOptions) {
|
|
310
|
-
queryOptionsRef.value = newQueryOptions;
|
|
311
|
-
}
|
|
312
|
-
query.refetch();
|
|
313
|
-
}
|
|
314
|
-
/**
|
|
315
|
-
* Registers a callback to be executed on successful request result.
|
|
316
|
-
* If data is already available, the callback will be executed immediately.
|
|
317
|
-
*
|
|
318
|
-
* @param {(data: T) => void} cb - Callback function to handle the result.
|
|
319
|
-
*/
|
|
320
|
-
function onResult(cb) {
|
|
321
|
-
resultCallbacks.value.push(cb);
|
|
322
|
-
if (query.data.value !== undefined && query.data.value !== null) {
|
|
323
|
-
cb(query.data.value);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
/**
|
|
327
|
-
* Registers a callback to be executed when the request results in an error.
|
|
328
|
-
* If an error is already present, the callback will be executed immediately.
|
|
329
|
-
*
|
|
330
|
-
* @param {(e: unknown) => void} callback - Callback function to handle errors.
|
|
331
|
-
*/
|
|
332
|
-
function onError(callback) {
|
|
333
|
-
errorCallbacks.value.push(callback);
|
|
334
|
-
if (query.error.value !== undefined) {
|
|
335
|
-
callback(query.error.value);
|
|
336
|
-
}
|
|
8
|
+
* Creates and downloads a file from Blob data.
|
|
9
|
+
* @param {Blob} blob The Blob containing the file data.
|
|
10
|
+
* @param {string} fileName The name of the file to create.
|
|
11
|
+
*/
|
|
12
|
+
function downloadFile(blob, fileName) {
|
|
13
|
+
const link = document.createElement('a');
|
|
14
|
+
const url = URL.createObjectURL(blob);
|
|
15
|
+
link.setAttribute('href', url);
|
|
16
|
+
link.setAttribute('download', fileName);
|
|
17
|
+
// Append link to the body and trigger a click
|
|
18
|
+
document.body.appendChild(link);
|
|
19
|
+
link.click();
|
|
20
|
+
// Clean up
|
|
21
|
+
document.body.removeChild(link);
|
|
22
|
+
URL.revokeObjectURL(url);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Exports data to a CSV file.
|
|
26
|
+
* @param {string[]} headers The headers for the CSV.
|
|
27
|
+
* @param {any[][]} data The data to export, as an array of arrays.
|
|
28
|
+
* @param {string} fileName The name of the file to create.
|
|
29
|
+
*/
|
|
30
|
+
function exportToCSV(headers, data, fileName) {
|
|
31
|
+
const csvRows = [];
|
|
32
|
+
// Add the headers
|
|
33
|
+
csvRows.push(headers.map(header => `"${header.replace(/"/g, '""')}"`).join(','));
|
|
34
|
+
// Add the data rows
|
|
35
|
+
for (const row of data) {
|
|
36
|
+
csvRows.push(row.map(cell => `"${String(cell).replace(/"/g, '""')}"`).join(','));
|
|
337
37
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
if (query.data.value != null) {
|
|
362
|
-
resultCallbacks.value.forEach((cb) => cb(query.data.value));
|
|
363
|
-
}
|
|
364
|
-
}
|
|
38
|
+
// Create a Blob with CSV data
|
|
39
|
+
const csvContent = csvRows.join('\n');
|
|
40
|
+
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
|
41
|
+
// Use the download utility function
|
|
42
|
+
downloadFile(blob, fileName);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Exports data to an Excel file (.xls) using HTML table.
|
|
46
|
+
* @param {string[]} headers The headers for the Excel file.
|
|
47
|
+
* @param {any[][]} data The data to export, as an array of arrays.
|
|
48
|
+
* @param {string} fileName The name of the file to create.
|
|
49
|
+
*/
|
|
50
|
+
function exportToExcel(headers, data, fileName) {
|
|
51
|
+
// Create a table element
|
|
52
|
+
const table = document.createElement('table');
|
|
53
|
+
const thead = table.createTHead();
|
|
54
|
+
const tbody = table.createTBody();
|
|
55
|
+
// Create the header row
|
|
56
|
+
const headerRow = thead.insertRow();
|
|
57
|
+
headers.forEach(header => {
|
|
58
|
+
const th = document.createElement('th');
|
|
59
|
+
th.textContent = header;
|
|
60
|
+
headerRow.appendChild(th);
|
|
365
61
|
});
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
}
|
|
374
|
-
if (query.error.value !== undefined) {
|
|
375
|
-
errorCallbacks.value.forEach((cb) => cb(query.error.value));
|
|
376
|
-
}
|
|
62
|
+
// Create the data rows
|
|
63
|
+
data.forEach(row => {
|
|
64
|
+
const tr = tbody.insertRow();
|
|
65
|
+
row.forEach(cell => {
|
|
66
|
+
const td = tr.insertCell();
|
|
67
|
+
td.textContent = String(cell);
|
|
68
|
+
});
|
|
377
69
|
});
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
};
|
|
70
|
+
// Create a Blob with the table HTML
|
|
71
|
+
const html = table.outerHTML;
|
|
72
|
+
const blob = new Blob([html], { type: 'application/vnd.ms-excel;charset=utf-8;' });
|
|
73
|
+
// Use the download utility function
|
|
74
|
+
downloadFile(blob, fileName);
|
|
384
75
|
}
|
|
385
|
-
|
|
386
|
-
let notified = false;
|
|
387
76
|
/**
|
|
388
|
-
*
|
|
389
|
-
*
|
|
390
|
-
* @
|
|
77
|
+
* Exports data to a JSON file.
|
|
78
|
+
* @param {any[]} data The data to export.
|
|
79
|
+
* @param {string} fileName The name of the file to create.
|
|
391
80
|
*/
|
|
392
|
-
function
|
|
393
|
-
const
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
const sm_S = breakpoints.smaller('sm');
|
|
398
|
-
const sm_SE = breakpoints.smallerOrEqual('sm');
|
|
399
|
-
const sm_GE = breakpoints.greaterOrEqual('sm');
|
|
400
|
-
const sm_md = breakpoints.between('sm', 'md');
|
|
401
|
-
const sm_xl = breakpoints.between('sm', 'xl');
|
|
402
|
-
const sm_2xl = breakpoints.between('sm', '2xl');
|
|
403
|
-
const md_S = breakpoints.smaller('md');
|
|
404
|
-
const md_SE = breakpoints.smallerOrEqual('md');
|
|
405
|
-
const md_GE = breakpoints.greaterOrEqual('md');
|
|
406
|
-
const md_lg = breakpoints.between('md', 'lg');
|
|
407
|
-
const md_xl = breakpoints.between('md', 'xl');
|
|
408
|
-
const md_2xl = breakpoints.between('md', '2xl');
|
|
409
|
-
const lg_S = breakpoints.smaller('lg');
|
|
410
|
-
const lg_SE = breakpoints.smallerOrEqual('lg');
|
|
411
|
-
const lg_GE = breakpoints.greaterOrEqual('lg');
|
|
412
|
-
const lg_xl = breakpoints.between('lg', 'xl');
|
|
413
|
-
const lg_2xl = breakpoints.between('lg', '2xl');
|
|
414
|
-
const xl_S = breakpoints.smaller('xl');
|
|
415
|
-
const xl_SE = breakpoints.smallerOrEqual('xl');
|
|
416
|
-
const xl_GE = breakpoints.greaterOrEqual('xl');
|
|
417
|
-
const xl_2xl = breakpoints.between('xl', '2xl');
|
|
418
|
-
const _2xl_S = breakpoints.smaller('2xl');
|
|
419
|
-
const _2xl_SE = breakpoints.smallerOrEqual('2xl');
|
|
420
|
-
const _2xl_GE = breakpoints.greaterOrEqual('2xl');
|
|
421
|
-
const reactiveBreakpoint = ref('sm');
|
|
422
|
-
const isGreaterThanBreakpoint = breakpoints.greaterOrEqual(() => reactiveBreakpoint.value);
|
|
423
|
-
// Define device ranges
|
|
424
|
-
const mobile = breakpoints.smaller('md'); // <768px
|
|
425
|
-
const tablet = breakpoints.between('md', 'lg'); // 768px - 1024px
|
|
426
|
-
const laptop = breakpoints.between('lg', 'xl'); // 1024px - 1280px
|
|
427
|
-
const desktop = breakpoints.greaterOrEqual('xl'); // >=1280px
|
|
428
|
-
// Use VueUse for window size
|
|
429
|
-
const { width: windowWidth, height: windowHeight } = useWindowSize();
|
|
430
|
-
/**
|
|
431
|
-
* Logs a styled "chip" message to the console.
|
|
432
|
-
*
|
|
433
|
-
* @param {string} label - The label to display inside the chip.
|
|
434
|
-
* @param {string} backgroundColor - The background color of the chip.
|
|
435
|
-
* @param {string} textColor - The text color of the chip.
|
|
436
|
-
*/
|
|
437
|
-
const logChip = (label, backgroundColor, textColor = 'white') => {
|
|
438
|
-
console.log(`%c ${label} `, `background-color: ${backgroundColor}; color: ${textColor}; border-radius: 16px; padding: 4px 8px;`);
|
|
439
|
-
};
|
|
440
|
-
// Singleton logic: Only notify once
|
|
441
|
-
const notifyOnce = () => {
|
|
442
|
-
if (notified)
|
|
443
|
-
return;
|
|
444
|
-
watch([mobile, tablet, laptop, desktop], ([isMobile, isTablet, isLaptop, isDesktop]) => {
|
|
445
|
-
if (isMobile) {
|
|
446
|
-
logChip('Mobile', '#ff6b6b');
|
|
447
|
-
}
|
|
448
|
-
else if (isTablet) {
|
|
449
|
-
logChip('Tablet', '#4ecdc4');
|
|
450
|
-
}
|
|
451
|
-
else if (isLaptop) {
|
|
452
|
-
logChip('Laptop', '#1a535c');
|
|
453
|
-
}
|
|
454
|
-
else if (isDesktop) {
|
|
455
|
-
logChip('Desktop', '#f7fff7', 'black');
|
|
456
|
-
}
|
|
457
|
-
notified = true; // Mark as notified
|
|
458
|
-
}, { immediate: true });
|
|
459
|
-
};
|
|
460
|
-
notifyOnce(); // Ensure the notification happens only once globally
|
|
461
|
-
return {
|
|
462
|
-
current,
|
|
463
|
-
active,
|
|
464
|
-
sm_S,
|
|
465
|
-
sm_SE,
|
|
466
|
-
sm_GE,
|
|
467
|
-
sm_md,
|
|
468
|
-
sm_xl,
|
|
469
|
-
sm_2xl,
|
|
470
|
-
md_S,
|
|
471
|
-
md_SE,
|
|
472
|
-
md_GE,
|
|
473
|
-
md_lg,
|
|
474
|
-
md_xl,
|
|
475
|
-
md_2xl,
|
|
476
|
-
lg_S,
|
|
477
|
-
lg_SE,
|
|
478
|
-
lg_GE,
|
|
479
|
-
lg_xl,
|
|
480
|
-
lg_2xl,
|
|
481
|
-
xl_S,
|
|
482
|
-
xl_SE,
|
|
483
|
-
xl_GE,
|
|
484
|
-
xl_2xl,
|
|
485
|
-
_2xl_S,
|
|
486
|
-
_2xl_SE,
|
|
487
|
-
_2xl_GE,
|
|
488
|
-
isGreaterThanBreakpoint,
|
|
489
|
-
reactiveBreakpoint,
|
|
490
|
-
breakpointsTailwind,
|
|
491
|
-
windowWidth,
|
|
492
|
-
windowHeight,
|
|
493
|
-
mobile,
|
|
494
|
-
tablet,
|
|
495
|
-
laptop,
|
|
496
|
-
desktop,
|
|
497
|
-
breakpoints,
|
|
498
|
-
};
|
|
81
|
+
function exportToJSON(data, fileName) {
|
|
82
|
+
const jsonContent = JSON.stringify(data, null, 2); // Pretty print with 2 spaces
|
|
83
|
+
const blob = new Blob([jsonContent], { type: 'application/json;charset=utf-8;' });
|
|
84
|
+
// Use the download utility function
|
|
85
|
+
downloadFile(blob, fileName);
|
|
499
86
|
}
|
|
500
|
-
|
|
501
87
|
/**
|
|
502
|
-
*
|
|
503
|
-
*
|
|
504
|
-
* @param {
|
|
505
|
-
* @param {
|
|
506
|
-
* @param {string} filterConfig.field - The field in the objects to filter.
|
|
507
|
-
* @param {string} filterConfig.type - The data type of the field ('date', 'string', 'number', 'boolean').
|
|
508
|
-
* @param {any} filterConfig.criteria - The criteria for filtering. Can be a date range, a string to search, a number range, or a boolean value.
|
|
509
|
-
*
|
|
510
|
-
* @returns {Object[]} - The filtered list of objects.
|
|
88
|
+
* Exports data to an XML file.
|
|
89
|
+
* @param {string[]} headers The headers for the XML.
|
|
90
|
+
* @param {any[][]} data The data to export, as an array of arrays.
|
|
91
|
+
* @param {string} fileName The name of the file to create.
|
|
511
92
|
*/
|
|
512
|
-
function
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
return date >= new Date(startDate) && date <= new Date(endDate);
|
|
93
|
+
function exportToXML(headers, data, fileName) {
|
|
94
|
+
let xmlContent = '<?xml version="1.0" encoding="UTF-8"?>\n<rows>\n';
|
|
95
|
+
// Add headers
|
|
96
|
+
xmlContent += ' <header>\n';
|
|
97
|
+
headers.forEach(header => {
|
|
98
|
+
xmlContent += ` <column>${header}</column>\n`;
|
|
519
99
|
});
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
100
|
+
xmlContent += ' </header>\n';
|
|
101
|
+
// Add data rows
|
|
102
|
+
data.forEach(row => {
|
|
103
|
+
xmlContent += ' <row>\n';
|
|
104
|
+
row.forEach(cell => {
|
|
105
|
+
xmlContent += ` <cell>${cell}</cell>\n`;
|
|
526
106
|
});
|
|
527
|
-
|
|
528
|
-
const filterByNumber = (objects, field, { min, max }) => objects.filter(item => {
|
|
529
|
-
const value = item[field];
|
|
530
|
-
return value >= min && value <= max;
|
|
531
|
-
});
|
|
532
|
-
const filterByBoolean = (objects, field, expectedValue) => objects.filter(item => item[field] === expectedValue);
|
|
533
|
-
// Apply the appropriate filter and return the filtered objects
|
|
534
|
-
const filteredObjects = computed(() => {
|
|
535
|
-
if (!criteria ||
|
|
536
|
-
(type === 'date' && (!criteria.startDate || !criteria.endDate)) ||
|
|
537
|
-
(type === 'number' && (criteria.min == null || criteria.max == null)) ||
|
|
538
|
-
(type === 'boolean' && typeof criteria !== 'boolean')) {
|
|
539
|
-
return items; // Return all objects if criteria is not valid
|
|
540
|
-
}
|
|
541
|
-
// Apply the filter based on the type
|
|
542
|
-
switch (type) {
|
|
543
|
-
case 'date':
|
|
544
|
-
return filterByDate(items, field, criteria);
|
|
545
|
-
case 'string':
|
|
546
|
-
return filterByString(items, field, criteria);
|
|
547
|
-
case 'number':
|
|
548
|
-
return filterByNumber(items, field, criteria);
|
|
549
|
-
case 'boolean':
|
|
550
|
-
return filterByBoolean(items, field, criteria);
|
|
551
|
-
default:
|
|
552
|
-
return items;
|
|
553
|
-
}
|
|
107
|
+
xmlContent += ' </row>\n';
|
|
554
108
|
});
|
|
555
|
-
|
|
109
|
+
xmlContent += '</rows>';
|
|
110
|
+
const blob = new Blob([xmlContent], { type: 'application/xml;charset=utf-8;' });
|
|
111
|
+
// Use the download utility function
|
|
112
|
+
downloadFile(blob, fileName);
|
|
556
113
|
}
|
|
557
|
-
|
|
558
114
|
/**
|
|
559
|
-
*
|
|
560
|
-
*
|
|
561
|
-
* @param
|
|
562
|
-
* @param
|
|
563
|
-
* @param pageSize - Reactive ref for number of items per page.
|
|
564
|
-
* @returns An object with pagination controls.
|
|
115
|
+
* Exports data to a plain text file.
|
|
116
|
+
* @param {string[]} headers The headers for the text file.
|
|
117
|
+
* @param {any[][]} data The data to export, as an array of arrays.
|
|
118
|
+
* @param {string} fileName The name of the file to create.
|
|
565
119
|
*/
|
|
566
|
-
function
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
120
|
+
function exportToText(headers, data, fileName) {
|
|
121
|
+
const textRows = [];
|
|
122
|
+
// Add headers
|
|
123
|
+
textRows.push(headers.join('\t'));
|
|
124
|
+
// Add data rows
|
|
125
|
+
data.forEach(row => {
|
|
126
|
+
textRows.push(row.join('\t'));
|
|
127
|
+
});
|
|
128
|
+
// Create a Blob with text data
|
|
129
|
+
const textContent = textRows.join('\n');
|
|
130
|
+
const blob = new Blob([textContent], { type: 'text/plain;charset=utf-8;' });
|
|
131
|
+
// Use the download utility function
|
|
132
|
+
downloadFile(blob, fileName);
|
|
578
133
|
}
|
|
579
134
|
|
|
580
135
|
/**
|
|
581
|
-
*
|
|
582
|
-
* @param {
|
|
583
|
-
* @param {
|
|
584
|
-
* @param {
|
|
585
|
-
* @
|
|
136
|
+
* Opens a new window with the specified URL and options.
|
|
137
|
+
* @param {string} url The URL to open.
|
|
138
|
+
* @param {Object} [opt] Options for the new window.
|
|
139
|
+
* @param {string} [opt.target='__blank'] The target window name.
|
|
140
|
+
* @param {boolean} [opt.noopener=true] Whether to add 'noopener' attribute.
|
|
141
|
+
* @param {boolean} [opt.noreferrer=true] Whether to add 'noreferrer' attribute.
|
|
586
142
|
*/
|
|
587
|
-
function
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
const numB = typeof bValue === 'number' ? bValue : parseFloat(bValue) || 0;
|
|
598
|
-
return criteria.order === 'asc' ? numA - numB : numB - numA;
|
|
599
|
-
}
|
|
600
|
-
else if (criteria.type === 'date') {
|
|
601
|
-
const dateA = new Date(aValue).getTime();
|
|
602
|
-
const dateB = new Date(bValue).getTime();
|
|
603
|
-
return criteria.order === 'asc' ? dateA - dateB : dateB - dateA;
|
|
604
|
-
}
|
|
605
|
-
else if (criteria.type === 'boolean') {
|
|
606
|
-
const boolA = !!aValue;
|
|
607
|
-
const boolB = !!bValue;
|
|
608
|
-
return criteria.order === 'asc' ? (boolA === boolB ? 0 : boolA ? 1 : -1) : (boolA === boolB ? 0 : boolA ? -1 : 1);
|
|
609
|
-
}
|
|
610
|
-
else {
|
|
611
|
-
// Fallback for string sorting
|
|
612
|
-
const strA = (aValue || '').toString().toLowerCase();
|
|
613
|
-
const strB = (bValue || '').toString().toLowerCase();
|
|
614
|
-
return criteria.order === 'asc' ? strA.localeCompare(strB) : strB.localeCompare(strA);
|
|
615
|
-
}
|
|
616
|
-
});
|
|
617
|
-
}).value;
|
|
143
|
+
function openWindow(url, opt) {
|
|
144
|
+
const { target = '__blank', noopener = true, noreferrer = true } = opt || {};
|
|
145
|
+
const features = [];
|
|
146
|
+
// Append noopener and noreferrer attributes if enabled
|
|
147
|
+
if (noopener)
|
|
148
|
+
features.push('noopener=yes');
|
|
149
|
+
if (noreferrer)
|
|
150
|
+
features.push('noreferrer=yes');
|
|
151
|
+
// Open the window with the specified features
|
|
152
|
+
window.open(url, target, features.join(','));
|
|
618
153
|
}
|
|
619
|
-
|
|
620
154
|
/**
|
|
621
|
-
*
|
|
622
|
-
* @
|
|
155
|
+
* Copies text to the clipboard.
|
|
156
|
+
* @param {string} text The text to copy.
|
|
157
|
+
* @returns {Promise<void>} A promise that resolves when the text has been copied.
|
|
623
158
|
*/
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
159
|
+
async function copyToClipboard(text) {
|
|
160
|
+
if (navigator.clipboard) {
|
|
161
|
+
await navigator.clipboard.writeText(text);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
// Fallback for browsers that do not support the Clipboard API
|
|
165
|
+
const textArea = document.createElement('textarea');
|
|
166
|
+
textArea.value = text;
|
|
167
|
+
document.body.appendChild(textArea);
|
|
168
|
+
textArea.select();
|
|
169
|
+
document.execCommand('copy');
|
|
170
|
+
document.body.removeChild(textArea);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
632
173
|
/**
|
|
633
|
-
*
|
|
634
|
-
*
|
|
635
|
-
* @readonly
|
|
174
|
+
* Scrolls the window to the top smoothly.
|
|
175
|
+
* @param {number} [duration=300] Duration of the scroll animation in milliseconds.
|
|
636
176
|
*/
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
177
|
+
function scrollToTop(duration = 300) {
|
|
178
|
+
const start = window.scrollY;
|
|
179
|
+
const startTime = performance.now();
|
|
180
|
+
function scroll() {
|
|
181
|
+
const elapsed = performance.now() - startTime;
|
|
182
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
183
|
+
window.scrollTo(0, start * (1 - progress));
|
|
184
|
+
if (progress < 1) {
|
|
185
|
+
requestAnimationFrame(scroll);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
requestAnimationFrame(scroll);
|
|
189
|
+
}
|
|
645
190
|
/**
|
|
646
|
-
*
|
|
647
|
-
* @
|
|
191
|
+
* Gets the value of a query parameter from the URL.
|
|
192
|
+
* @param {string} paramName The name of the query parameter.
|
|
193
|
+
* @returns {string | null} The value of the query parameter, or null if it does not exist.
|
|
648
194
|
*/
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
screenMap.set(sizeEnum.LG, screenEnum.LG);
|
|
654
|
-
screenMap.set(sizeEnum.XL, screenEnum.XL);
|
|
655
|
-
screenMap.set(sizeEnum.XXL, screenEnum.XXL);
|
|
195
|
+
function getQueryParam(paramName) {
|
|
196
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
197
|
+
return urlParams.get(paramName);
|
|
198
|
+
}
|
|
656
199
|
|
|
657
200
|
/**
|
|
658
|
-
*
|
|
659
|
-
* @readonly
|
|
201
|
+
* Disables the right-click context menu on the window.
|
|
660
202
|
*/
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
203
|
+
function disableRightClick() {
|
|
204
|
+
const handler = (event) => event.preventDefault();
|
|
205
|
+
window.addEventListener('contextmenu', handler);
|
|
206
|
+
// Store handler to allow removal
|
|
207
|
+
disableRightClick.handler = handler;
|
|
208
|
+
}
|
|
209
|
+
disableRightClick.handler = null;
|
|
210
|
+
/**
|
|
211
|
+
* Enables the right-click context menu on the window.
|
|
212
|
+
*/
|
|
213
|
+
function enableRightClick() {
|
|
214
|
+
if (disableRightClick.handler) {
|
|
215
|
+
window.removeEventListener('contextmenu', disableRightClick.handler);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Disables specific mouse buttons.
|
|
220
|
+
* @param {Array<number>} buttons Array of mouse button codes to disable (0 for left, 1 for middle, 2 for right).
|
|
221
|
+
*/
|
|
222
|
+
function disableMouseButtons(buttons) {
|
|
223
|
+
const handler = (event) => {
|
|
224
|
+
if (buttons.includes(event.button)) {
|
|
225
|
+
event.preventDefault();
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
window.addEventListener('mousedown', handler);
|
|
229
|
+
window.addEventListener('contextmenu', handler); // Also handles right-click context menu
|
|
230
|
+
// Store handler to allow removal later
|
|
231
|
+
disableMouseButtons.handlers.push(handler);
|
|
232
|
+
}
|
|
233
|
+
disableMouseButtons.handlers = [];
|
|
688
234
|
/**
|
|
689
|
-
*
|
|
690
|
-
* @readonly
|
|
235
|
+
* Enables all previously disabled mouse buttons.
|
|
691
236
|
*/
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
svg: 'image/svg+xml',
|
|
700
|
-
tiff: 'image/tiff',
|
|
701
|
-
webp: 'image/webp',
|
|
702
|
-
xicon: 'image/x-icon',
|
|
703
|
-
};
|
|
237
|
+
function enableMouseButtons() {
|
|
238
|
+
for (const handler of disableMouseButtons.handlers) {
|
|
239
|
+
window.removeEventListener('mousedown', handler);
|
|
240
|
+
window.removeEventListener('contextmenu', handler);
|
|
241
|
+
}
|
|
242
|
+
disableMouseButtons.handlers = [];
|
|
243
|
+
}
|
|
704
244
|
/**
|
|
705
|
-
*
|
|
706
|
-
* @
|
|
245
|
+
* Adds a double-click event listener to a specific element.
|
|
246
|
+
* @param {HTMLElement} element The target element.
|
|
247
|
+
* @param {(event: MouseEvent) => void} callback The callback function to execute on double click.
|
|
707
248
|
*/
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
videos: 'video/*',
|
|
712
|
-
};
|
|
713
|
-
|
|
249
|
+
function addDoubleClickListener(element, callback) {
|
|
250
|
+
element.addEventListener('dblclick', callback);
|
|
251
|
+
}
|
|
714
252
|
/**
|
|
715
|
-
*
|
|
716
|
-
* @
|
|
253
|
+
* Removes a double-click event listener from a specific element.
|
|
254
|
+
* @param {HTMLElement} element The target element.
|
|
255
|
+
* @param {(event: MouseEvent) => void} callback The callback function to remove.
|
|
717
256
|
*/
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
JSON: 'application/json;charset=UTF-8',
|
|
724
|
-
/**
|
|
725
|
-
* Content type for URL-encoded form data.
|
|
726
|
-
* @constant
|
|
727
|
-
*/
|
|
728
|
-
FORM_URLENCODED: 'application/x-www-form-urlencoded;charset=UTF-8',
|
|
729
|
-
/**
|
|
730
|
-
* Content type for multipart form data with file uploads.
|
|
731
|
-
* @constant
|
|
732
|
-
*/
|
|
733
|
-
FORM_DATA: 'multipart/form-data;charset=UTF-8',
|
|
734
|
-
};
|
|
735
|
-
|
|
257
|
+
function removeDoubleClickListener(element, callback) {
|
|
258
|
+
element.removeEventListener('dblclick', callback);
|
|
259
|
+
}
|
|
260
|
+
// Example usage:
|
|
261
|
+
// addDoubleClickListener(document.body, () => alert('Double clicked!'));
|
|
736
262
|
/**
|
|
737
|
-
*
|
|
738
|
-
* @
|
|
263
|
+
* Detects a click outside a specific element and triggers a callback.
|
|
264
|
+
* @param {HTMLElement} element The element to detect clicks outside of.
|
|
265
|
+
* @param {() => void} callback The callback function to execute when a click outside is detected.
|
|
739
266
|
*/
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
};
|
|
746
|
-
|
|
267
|
+
function clickOutside(element, callback) {
|
|
268
|
+
const handler = (event) => {
|
|
269
|
+
if (!element.contains(event.target)) {
|
|
270
|
+
callback();
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
document.addEventListener('click', handler);
|
|
274
|
+
// Store handler to allow removal later
|
|
275
|
+
clickOutside.handlers.push({ element, handler });
|
|
276
|
+
}
|
|
277
|
+
clickOutside.handlers = [];
|
|
747
278
|
/**
|
|
748
|
-
*
|
|
749
|
-
* @
|
|
279
|
+
* Removes the click outside listener for a specific element.
|
|
280
|
+
* @param {HTMLElement} element The element to stop detecting clicks outside of.
|
|
750
281
|
*/
|
|
751
|
-
|
|
282
|
+
function removeClickOutside(element) {
|
|
283
|
+
const index = clickOutside.handlers.findIndex(h => h.element === element);
|
|
284
|
+
if (index !== -1) {
|
|
285
|
+
document.removeEventListener('click', clickOutside.handlers[index].handler);
|
|
286
|
+
clickOutside.handlers.splice(index, 1);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// Example usage: Close a menu when clicking outside of it
|
|
290
|
+
// const menu = document.getElementById('menu');
|
|
291
|
+
// if (menu) {
|
|
292
|
+
// clickOutside(menu, () => menu.style.display = 'none');
|
|
293
|
+
// }
|
|
752
294
|
/**
|
|
753
|
-
*
|
|
754
|
-
* @constant {string}
|
|
295
|
+
* Disables the F12 key and certain key combinations for developer tools.
|
|
755
296
|
*/
|
|
756
|
-
|
|
297
|
+
function disableF12Key() {
|
|
298
|
+
const handler = function (event) {
|
|
299
|
+
return true;
|
|
300
|
+
};
|
|
301
|
+
document.addEventListener('keydown', handler);
|
|
302
|
+
}
|
|
757
303
|
/**
|
|
758
|
-
*
|
|
759
|
-
* @
|
|
304
|
+
* Enables or disables the tab navigation (Tab key) on the page.
|
|
305
|
+
* @param {boolean} enable Whether to enable or disable tab navigation.
|
|
760
306
|
*/
|
|
761
|
-
|
|
307
|
+
function toggleTabNavigation(enable) {
|
|
308
|
+
if (enable) {
|
|
309
|
+
document.onkeydown = null; // Removes any previously set handler
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
document.onkeydown = (event) => {
|
|
313
|
+
if (event.key === 'Tab') {
|
|
314
|
+
event.preventDefault();
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
return true;
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
}
|
|
762
321
|
/**
|
|
763
|
-
*
|
|
764
|
-
* @constant {string}
|
|
322
|
+
* Disables the copy (Ctrl + C) functionality on the page.
|
|
765
323
|
*/
|
|
766
|
-
|
|
324
|
+
function disableCopy() {
|
|
325
|
+
document.addEventListener('copy', (event) => {
|
|
326
|
+
event.preventDefault();
|
|
327
|
+
alert('Copying text is disabled on this page.');
|
|
328
|
+
});
|
|
329
|
+
}
|
|
767
330
|
/**
|
|
768
|
-
*
|
|
769
|
-
* @
|
|
331
|
+
* Adds a custom keyboard shortcut to execute a given callback function.
|
|
332
|
+
* @param {string} key The key to trigger the callback.
|
|
333
|
+
* @param {Function} callback The function to execute on the key press.
|
|
334
|
+
* @param {boolean} [ctrlKey=false] Whether Ctrl key should be pressed.
|
|
335
|
+
* @param {boolean} [shiftKey=false] Whether Shift key should be pressed.
|
|
770
336
|
*/
|
|
771
|
-
|
|
337
|
+
function addCustomKeyboardShortcut(key, callback, ctrlKey = false, shiftKey = false) {
|
|
338
|
+
document.addEventListener('keydown', (event) => {
|
|
339
|
+
if (event.key === key &&
|
|
340
|
+
event.ctrlKey === ctrlKey &&
|
|
341
|
+
event.shiftKey === shiftKey) {
|
|
342
|
+
event.preventDefault();
|
|
343
|
+
callback();
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
}
|
|
772
347
|
/**
|
|
773
|
-
*
|
|
774
|
-
* @
|
|
348
|
+
* Removes a custom keyboard shortcut by key and modifiers.
|
|
349
|
+
* @param {string} key The key to trigger the callback.
|
|
350
|
+
* @param {boolean} [ctrlKey=false] Whether Ctrl key should be pressed.
|
|
351
|
+
* @param {boolean} [shiftKey=false] Whether Shift key should be pressed.
|
|
775
352
|
*/
|
|
776
|
-
|
|
353
|
+
function removeCustomKeyboardShortcut(key, ctrlKey = false, shiftKey = false) {
|
|
354
|
+
const handler = (event) => {
|
|
355
|
+
if (event.key === key &&
|
|
356
|
+
event.ctrlKey === ctrlKey &&
|
|
357
|
+
event.shiftKey === shiftKey) {
|
|
358
|
+
event.preventDefault();
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
document.removeEventListener('keydown', handler);
|
|
362
|
+
}
|
|
777
363
|
/**
|
|
778
|
-
*
|
|
779
|
-
* @
|
|
364
|
+
* Disables specific keys or key combinations.
|
|
365
|
+
* @param {Array<string>} keys Array of key names to disable (e.g., ['F1', 'F5', 'Control+S']).
|
|
780
366
|
*/
|
|
781
|
-
|
|
367
|
+
function disableSpecificKeys(keys) {
|
|
368
|
+
const handler = (event) => {
|
|
369
|
+
const keyCombination = `${event.ctrlKey ? 'Control+' : ''}${event.shiftKey ? 'Shift+' : ''}${event.altKey ? 'Alt+' : ''}${event.key}`;
|
|
370
|
+
if (keys.includes(event.key) || keys.includes(keyCombination)) {
|
|
371
|
+
event.preventDefault();
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
document.addEventListener('keydown', handler);
|
|
375
|
+
// Storing the handler to allow removal later
|
|
376
|
+
disableSpecificKeys.handlers.push(handler);
|
|
377
|
+
}
|
|
378
|
+
disableSpecificKeys.handlers = [];
|
|
782
379
|
/**
|
|
783
|
-
*
|
|
784
|
-
* @constant {string}
|
|
380
|
+
* Enables keys that were previously disabled using disableSpecificKeys.
|
|
785
381
|
*/
|
|
786
|
-
|
|
382
|
+
function enableSpecificKeys() {
|
|
383
|
+
for (const handler of disableSpecificKeys.handlers) {
|
|
384
|
+
document.removeEventListener('keydown', handler);
|
|
385
|
+
}
|
|
386
|
+
disableSpecificKeys.handlers = [];
|
|
387
|
+
}
|
|
787
388
|
/**
|
|
788
|
-
*
|
|
789
|
-
* @
|
|
389
|
+
* Registers multiple keyboard shortcuts with their respective callback functions.
|
|
390
|
+
* @param {Array<{ key: string, ctrlKey?: boolean, shiftKey?: boolean, altKey?: boolean, callback: Function }>} shortcuts Array of shortcut objects.
|
|
790
391
|
*/
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
392
|
+
function registerKeyboardShortcuts(shortcuts) {
|
|
393
|
+
const handler = (event) => {
|
|
394
|
+
for (const shortcut of shortcuts) {
|
|
395
|
+
if (event.key === shortcut.key &&
|
|
396
|
+
(shortcut.ctrlKey ? event.ctrlKey : true) &&
|
|
397
|
+
(shortcut.shiftKey ? event.shiftKey : true) &&
|
|
398
|
+
(shortcut.altKey ? event.altKey : true)) {
|
|
399
|
+
event.preventDefault();
|
|
400
|
+
shortcut.callback();
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
document.addEventListener('keydown', handler);
|
|
405
|
+
// Storing the handler to allow removal later
|
|
406
|
+
registerKeyboardShortcuts.handlers.push(handler);
|
|
407
|
+
}
|
|
408
|
+
registerKeyboardShortcuts.handlers = [];
|
|
798
409
|
/**
|
|
799
|
-
*
|
|
800
|
-
|
|
801
|
-
|
|
410
|
+
* Unregisters all keyboard shortcuts that were registered with registerKeyboardShortcuts.
|
|
411
|
+
*/
|
|
412
|
+
function unregisterKeyboardShortcuts() {
|
|
413
|
+
for (const handler of registerKeyboardShortcuts.handlers) {
|
|
414
|
+
document.removeEventListener('keydown', handler);
|
|
415
|
+
}
|
|
416
|
+
registerKeyboardShortcuts.handlers = [];
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Adds a listener for a specific key to trigger a custom event.
|
|
420
|
+
* @param {string} key The key to listen for (e.g., 'Enter', 'Escape').
|
|
421
|
+
* @param {Function} callback The function to execute when the key is pressed.
|
|
802
422
|
*/
|
|
803
|
-
function
|
|
804
|
-
const
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
//
|
|
812
|
-
|
|
813
|
-
URL.revokeObjectURL(url);
|
|
423
|
+
function addKeyListener(key, callback) {
|
|
424
|
+
const handler = (event) => {
|
|
425
|
+
if (event.key === key) {
|
|
426
|
+
event.preventDefault();
|
|
427
|
+
callback();
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
document.addEventListener('keydown', handler);
|
|
431
|
+
// Storing the handler to allow removal later
|
|
432
|
+
addKeyListener.handlers.push(handler);
|
|
814
433
|
}
|
|
434
|
+
addKeyListener.handlers = [];
|
|
815
435
|
/**
|
|
816
|
-
*
|
|
817
|
-
* @param {string[]} headers The headers for the CSV.
|
|
818
|
-
* @param {any[][]} data The data to export, as an array of arrays.
|
|
819
|
-
* @param {string} fileName The name of the file to create.
|
|
436
|
+
* Removes all custom key listeners added by addKeyListener.
|
|
820
437
|
*/
|
|
821
|
-
function
|
|
822
|
-
const
|
|
823
|
-
|
|
824
|
-
csvRows.push(headers.map(header => `"${header.replace(/"/g, '""')}"`).join(','));
|
|
825
|
-
// Add the data rows
|
|
826
|
-
for (const row of data) {
|
|
827
|
-
csvRows.push(row.map(cell => `"${String(cell).replace(/"/g, '""')}"`).join(','));
|
|
438
|
+
function removeKeyListeners() {
|
|
439
|
+
for (const handler of addKeyListener.handlers) {
|
|
440
|
+
document.removeEventListener('keydown', handler);
|
|
828
441
|
}
|
|
829
|
-
|
|
830
|
-
const csvContent = csvRows.join('\n');
|
|
831
|
-
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
|
832
|
-
// Use the download utility function
|
|
833
|
-
downloadFile(blob, fileName);
|
|
442
|
+
addKeyListener.handlers = [];
|
|
834
443
|
}
|
|
835
444
|
/**
|
|
836
|
-
*
|
|
837
|
-
* @param {string
|
|
838
|
-
* @param {
|
|
839
|
-
* @param {string} fileName The name of the file to create.
|
|
445
|
+
* Detects if a specific key is held down.
|
|
446
|
+
* @param {string} key The key to detect (e.g., 'Shift', 'Control', 'Alt', 'a').
|
|
447
|
+
* @param {Function} onHold Callback function to execute while the key is held down.
|
|
840
448
|
*/
|
|
841
|
-
function
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
th.textContent = header;
|
|
851
|
-
headerRow.appendChild(th);
|
|
852
|
-
});
|
|
853
|
-
// Create the data rows
|
|
854
|
-
data.forEach(row => {
|
|
855
|
-
const tr = tbody.insertRow();
|
|
856
|
-
row.forEach(cell => {
|
|
857
|
-
const td = tr.insertCell();
|
|
858
|
-
td.textContent = String(cell);
|
|
859
|
-
});
|
|
860
|
-
});
|
|
861
|
-
// Create a Blob with the table HTML
|
|
862
|
-
const html = table.outerHTML;
|
|
863
|
-
const blob = new Blob([html], { type: 'application/vnd.ms-excel;charset=utf-8;' });
|
|
864
|
-
// Use the download utility function
|
|
865
|
-
downloadFile(blob, fileName);
|
|
449
|
+
function detectKeyHold(key, onHold) {
|
|
450
|
+
const keyDownHandler = (event) => {
|
|
451
|
+
if (event.key === key) {
|
|
452
|
+
onHold();
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
document.addEventListener('keydown', keyDownHandler);
|
|
456
|
+
// Storing the handler to allow removal later
|
|
457
|
+
detectKeyHold.handlers.push(keyDownHandler);
|
|
866
458
|
}
|
|
459
|
+
detectKeyHold.handlers = [];
|
|
867
460
|
/**
|
|
868
|
-
*
|
|
869
|
-
* @param {any[]} data The data to export.
|
|
870
|
-
* @param {string} fileName The name of the file to create.
|
|
461
|
+
* Stops detecting if a specific key is held down.
|
|
871
462
|
*/
|
|
872
|
-
function
|
|
873
|
-
const
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
463
|
+
function stopDetectingKeyHold() {
|
|
464
|
+
for (const handler of detectKeyHold.handlers) {
|
|
465
|
+
document.removeEventListener('keydown', handler);
|
|
466
|
+
}
|
|
467
|
+
detectKeyHold.handlers = [];
|
|
877
468
|
}
|
|
878
469
|
/**
|
|
879
|
-
*
|
|
880
|
-
* @
|
|
881
|
-
* @param {any[][]} data The data to export, as an array of arrays.
|
|
882
|
-
* @param {string} fileName The name of the file to create.
|
|
470
|
+
* Tracks currently pressed keys and provides a map of active keys.
|
|
471
|
+
* @returns {Set<string>} A set of currently pressed keys.
|
|
883
472
|
*/
|
|
884
|
-
function
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
xmlContent += '</rows>';
|
|
901
|
-
const blob = new Blob([xmlContent], { type: 'application/xml;charset=utf-8;' });
|
|
902
|
-
// Use the download utility function
|
|
903
|
-
downloadFile(blob, fileName);
|
|
473
|
+
function createKeyMap() {
|
|
474
|
+
const pressedKeys = new Set();
|
|
475
|
+
const keyDownHandler = (event) => {
|
|
476
|
+
pressedKeys.add(event.key);
|
|
477
|
+
};
|
|
478
|
+
const keyUpHandler = (event) => {
|
|
479
|
+
pressedKeys.delete(event.key);
|
|
480
|
+
};
|
|
481
|
+
document.addEventListener('keydown', keyDownHandler);
|
|
482
|
+
document.addEventListener('keyup', keyUpHandler);
|
|
483
|
+
// Function to remove listeners
|
|
484
|
+
createKeyMap.clearListeners = () => {
|
|
485
|
+
document.removeEventListener('keydown', keyDownHandler);
|
|
486
|
+
document.removeEventListener('keyup', keyUpHandler);
|
|
487
|
+
};
|
|
488
|
+
return pressedKeys;
|
|
904
489
|
}
|
|
905
490
|
/**
|
|
906
|
-
*
|
|
907
|
-
* @param {string[]} headers The headers for the text file.
|
|
908
|
-
* @param {any[][]} data The data to export, as an array of arrays.
|
|
909
|
-
* @param {string} fileName The name of the file to create.
|
|
491
|
+
* Clears listeners for the key map tracking.
|
|
910
492
|
*/
|
|
911
|
-
|
|
912
|
-
const textRows = [];
|
|
913
|
-
// Add headers
|
|
914
|
-
textRows.push(headers.join('\t'));
|
|
915
|
-
// Add data rows
|
|
916
|
-
data.forEach(row => {
|
|
917
|
-
textRows.push(row.join('\t'));
|
|
918
|
-
});
|
|
919
|
-
// Create a Blob with text data
|
|
920
|
-
const textContent = textRows.join('\n');
|
|
921
|
-
const blob = new Blob([textContent], { type: 'text/plain;charset=utf-8;' });
|
|
922
|
-
// Use the download utility function
|
|
923
|
-
downloadFile(blob, fileName);
|
|
924
|
-
}
|
|
925
|
-
|
|
493
|
+
createKeyMap.clearListeners = () => { };
|
|
926
494
|
/**
|
|
927
|
-
*
|
|
928
|
-
* @param {string}
|
|
929
|
-
* @param {
|
|
930
|
-
* @param {string} [opt.target='__blank'] The target window name.
|
|
931
|
-
* @param {boolean} [opt.noopener=true] Whether to add 'noopener' attribute.
|
|
932
|
-
* @param {boolean} [opt.noreferrer=true] Whether to add 'noreferrer' attribute.
|
|
495
|
+
* Sets up custom keyboard shortcuts with flexible order.
|
|
496
|
+
* @param {Array<string>} keys The combination of keys for the shortcut.
|
|
497
|
+
* @param {Function} callback The callback function to execute when the combination is detected.
|
|
933
498
|
*/
|
|
934
|
-
function
|
|
935
|
-
const
|
|
936
|
-
const
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
499
|
+
function customShortcut(keys, callback) {
|
|
500
|
+
const pressedKeys = new Set();
|
|
501
|
+
const keyDownHandler = (event) => {
|
|
502
|
+
pressedKeys.add(event.key);
|
|
503
|
+
if (keys.every(key => pressedKeys.has(key))) {
|
|
504
|
+
callback();
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
const keyUpHandler = (event) => {
|
|
508
|
+
pressedKeys.delete(event.key);
|
|
509
|
+
};
|
|
510
|
+
document.addEventListener('keydown', keyDownHandler);
|
|
511
|
+
document.addEventListener('keyup', keyUpHandler);
|
|
512
|
+
// Store the handlers for later removal
|
|
513
|
+
customShortcut.handlers.push({ keyDownHandler, keyUpHandler });
|
|
944
514
|
}
|
|
515
|
+
customShortcut.handlers = [];
|
|
945
516
|
/**
|
|
946
|
-
*
|
|
947
|
-
* @param {string} text The text to copy.
|
|
948
|
-
* @returns {Promise<void>} A promise that resolves when the text has been copied.
|
|
517
|
+
* Removes all custom keyboard shortcuts added by customShortcut.
|
|
949
518
|
*/
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
else {
|
|
955
|
-
// Fallback for browsers that do not support the Clipboard API
|
|
956
|
-
const textArea = document.createElement('textarea');
|
|
957
|
-
textArea.value = text;
|
|
958
|
-
document.body.appendChild(textArea);
|
|
959
|
-
textArea.select();
|
|
960
|
-
document.execCommand('copy');
|
|
961
|
-
document.body.removeChild(textArea);
|
|
519
|
+
function removeCustomShortcuts() {
|
|
520
|
+
for (const { keyDownHandler, keyUpHandler } of customShortcut.handlers) {
|
|
521
|
+
document.removeEventListener('keydown', keyDownHandler);
|
|
522
|
+
document.removeEventListener('keyup', keyUpHandler);
|
|
962
523
|
}
|
|
524
|
+
customShortcut.handlers = [];
|
|
963
525
|
}
|
|
964
526
|
/**
|
|
965
|
-
*
|
|
966
|
-
* @param {
|
|
527
|
+
* Simulates a key press event.
|
|
528
|
+
* @param {string} key The key to simulate (e.g., 'Enter', 'a').
|
|
529
|
+
* @param {boolean} ctrlKey If true, include Ctrl key in the event.
|
|
530
|
+
* @param {boolean} shiftKey If true, include Shift key in the event.
|
|
531
|
+
* @param {boolean} altKey If true, include Alt key in the event.
|
|
967
532
|
*/
|
|
968
|
-
function
|
|
969
|
-
const
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
533
|
+
function simulateKeyPress(key, ctrlKey = false, shiftKey = false, altKey = false) {
|
|
534
|
+
const event = new KeyboardEvent('keydown', {
|
|
535
|
+
key,
|
|
536
|
+
ctrlKey,
|
|
537
|
+
shiftKey,
|
|
538
|
+
altKey,
|
|
539
|
+
bubbles: true,
|
|
540
|
+
cancelable: true
|
|
541
|
+
});
|
|
542
|
+
document.dispatchEvent(event);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Parses a date string into a Date object.
|
|
547
|
+
* @param {string} dateString The date string in 'YYYY-MM-DD' format.
|
|
548
|
+
* @returns {Date | null} The parsed Date object or null if the format is invalid.
|
|
549
|
+
*/
|
|
550
|
+
function parseDate(dateString) {
|
|
551
|
+
const parts = dateString.split('-');
|
|
552
|
+
if (parts.length === 3) {
|
|
553
|
+
const year = parseInt(parts[0], 10);
|
|
554
|
+
const month = parseInt(parts[1], 10) - 1; // Months are zero-based
|
|
555
|
+
const day = parseInt(parts[2], 10);
|
|
556
|
+
const date = new Date(year, month, day);
|
|
557
|
+
if (date.getFullYear() === year && date.getMonth() === month && date.getDate() === day) {
|
|
558
|
+
return date;
|
|
977
559
|
}
|
|
978
560
|
}
|
|
979
|
-
|
|
561
|
+
return null;
|
|
980
562
|
}
|
|
981
563
|
/**
|
|
982
|
-
*
|
|
983
|
-
* @param {
|
|
984
|
-
* @
|
|
564
|
+
* Formats a Date object into a string.
|
|
565
|
+
* @param {Date} date The date to format.
|
|
566
|
+
* @param {string} format The format string (e.g., 'YYYY-MM-DD').
|
|
567
|
+
* @returns {string} The formatted date string.
|
|
985
568
|
*/
|
|
986
|
-
function
|
|
987
|
-
const
|
|
988
|
-
|
|
569
|
+
function formatDate(date, format) {
|
|
570
|
+
const map = {
|
|
571
|
+
'YYYY': date.getFullYear(),
|
|
572
|
+
'MM': String(date.getMonth() + 1).padStart(2, '0'),
|
|
573
|
+
'DD': String(date.getDate()).padStart(2, '0'),
|
|
574
|
+
'HH': String(date.getHours()).padStart(2, '0'),
|
|
575
|
+
'mm': String(date.getMinutes()).padStart(2, '0'),
|
|
576
|
+
'ss': String(date.getSeconds()).padStart(2, '0')
|
|
577
|
+
};
|
|
578
|
+
return format.replace(/YYYY|MM|DD|HH|mm|ss/g, (matched) => (map[matched] || matched).toString());
|
|
989
579
|
}
|
|
990
|
-
|
|
991
580
|
/**
|
|
992
|
-
*
|
|
581
|
+
* Calculates the number of days between two dates.
|
|
582
|
+
* @param {Date} startDate The start date.
|
|
583
|
+
* @param {Date} endDate The end date.
|
|
584
|
+
* @returns {number} The number of days between the two dates.
|
|
993
585
|
*/
|
|
994
|
-
function
|
|
995
|
-
const
|
|
996
|
-
|
|
997
|
-
// Store handler to allow removal
|
|
998
|
-
disableRightClick.handler = handler;
|
|
586
|
+
function daysBetween(startDate, endDate) {
|
|
587
|
+
const millisecondsPerDay = 86400000; // Number of milliseconds in one day
|
|
588
|
+
return Math.round((endDate.getTime() - startDate.getTime()) / millisecondsPerDay);
|
|
999
589
|
}
|
|
1000
|
-
disableRightClick.handler = null;
|
|
1001
590
|
/**
|
|
1002
|
-
*
|
|
591
|
+
* Adds a specified number of days to a date.
|
|
592
|
+
* @param {Date} date The date to modify.
|
|
593
|
+
* @param {number} days The number of days to add.
|
|
594
|
+
* @returns {Date} The new date with days added.
|
|
1003
595
|
*/
|
|
1004
|
-
function
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
596
|
+
function addDays(date, days) {
|
|
597
|
+
const result = new Date(date);
|
|
598
|
+
result.setDate(result.getDate() + days);
|
|
599
|
+
return result;
|
|
1008
600
|
}
|
|
1009
601
|
/**
|
|
1010
|
-
*
|
|
1011
|
-
* @param {
|
|
602
|
+
* Subtracts a specified number of days from a date.
|
|
603
|
+
* @param {Date} date The date to modify.
|
|
604
|
+
* @param {number} days The number of days to subtract.
|
|
605
|
+
* @returns {Date} The new date with days subtracted.
|
|
1012
606
|
*/
|
|
1013
|
-
function
|
|
1014
|
-
|
|
1015
|
-
if (buttons.includes(event.button)) {
|
|
1016
|
-
event.preventDefault();
|
|
1017
|
-
}
|
|
1018
|
-
};
|
|
1019
|
-
window.addEventListener('mousedown', handler);
|
|
1020
|
-
window.addEventListener('contextmenu', handler); // Also handles right-click context menu
|
|
1021
|
-
// Store handler to allow removal later
|
|
1022
|
-
disableMouseButtons.handlers.push(handler);
|
|
607
|
+
function subtractDays(date, days) {
|
|
608
|
+
return addDays(date, -days);
|
|
1023
609
|
}
|
|
1024
|
-
disableMouseButtons.handlers = [];
|
|
1025
610
|
/**
|
|
1026
|
-
*
|
|
611
|
+
* Determines if a year is a leap year.
|
|
612
|
+
* @param {number} year The year to check.
|
|
613
|
+
* @returns {boolean} True if the year is a leap year, false otherwise.
|
|
1027
614
|
*/
|
|
1028
|
-
function
|
|
1029
|
-
|
|
1030
|
-
window.removeEventListener('mousedown', handler);
|
|
1031
|
-
window.removeEventListener('contextmenu', handler);
|
|
1032
|
-
}
|
|
1033
|
-
disableMouseButtons.handlers = [];
|
|
615
|
+
function isLeapYear(year) {
|
|
616
|
+
return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
|
|
1034
617
|
}
|
|
1035
618
|
/**
|
|
1036
|
-
*
|
|
1037
|
-
* @param {
|
|
1038
|
-
* @
|
|
619
|
+
* Gets the first day of the month for a given date.
|
|
620
|
+
* @param {Date} date The date to use.
|
|
621
|
+
* @returns {Date} The first day of the month.
|
|
1039
622
|
*/
|
|
1040
|
-
function
|
|
1041
|
-
|
|
623
|
+
function getStartOfMonth(date) {
|
|
624
|
+
return new Date(date.getFullYear(), date.getMonth(), 1);
|
|
1042
625
|
}
|
|
1043
626
|
/**
|
|
1044
|
-
*
|
|
1045
|
-
* @param {
|
|
1046
|
-
* @
|
|
627
|
+
* Gets the last day of the month for a given date.
|
|
628
|
+
* @param {Date} date The date to use.
|
|
629
|
+
* @returns {Date} The last day of the month.
|
|
1047
630
|
*/
|
|
1048
|
-
function
|
|
1049
|
-
|
|
631
|
+
function getEndOfMonth(date) {
|
|
632
|
+
return new Date(date.getFullYear(), date.getMonth() + 1, 0);
|
|
1050
633
|
}
|
|
1051
|
-
// Example usage:
|
|
1052
|
-
// addDoubleClickListener(document.body, () => alert('Double clicked!'));
|
|
1053
634
|
/**
|
|
1054
|
-
*
|
|
1055
|
-
* @param {
|
|
1056
|
-
* @
|
|
635
|
+
* Calculates age from a given birth date.
|
|
636
|
+
* @param {Date} birthDate The birth date.
|
|
637
|
+
* @returns {number} The calculated age.
|
|
1057
638
|
*/
|
|
1058
|
-
function
|
|
1059
|
-
const
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
clickOutside.handlers.push({ element, handler });
|
|
639
|
+
function calculateAge(birthDate) {
|
|
640
|
+
const today = new Date();
|
|
641
|
+
let age = today.getFullYear() - birthDate.getFullYear();
|
|
642
|
+
const m = today.getMonth() - birthDate.getMonth();
|
|
643
|
+
if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
|
|
644
|
+
age--;
|
|
645
|
+
}
|
|
646
|
+
return age;
|
|
1067
647
|
}
|
|
1068
|
-
clickOutside.handlers = [];
|
|
1069
648
|
/**
|
|
1070
|
-
*
|
|
1071
|
-
* @param {
|
|
649
|
+
* Calculates the number of days until the next birthday.
|
|
650
|
+
* @param {Date} birthDate The birth date.
|
|
651
|
+
* @returns {number} The number of days until the next birthday.
|
|
1072
652
|
*/
|
|
1073
|
-
function
|
|
1074
|
-
const
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
653
|
+
function daysToNextBirthday(birthDate) {
|
|
654
|
+
const today = new Date();
|
|
655
|
+
const currentYear = today.getFullYear();
|
|
656
|
+
const nextBirthday = new Date(birthDate);
|
|
657
|
+
nextBirthday.setFullYear(currentYear);
|
|
658
|
+
if (nextBirthday < today) {
|
|
659
|
+
nextBirthday.setFullYear(currentYear + 1);
|
|
1078
660
|
}
|
|
661
|
+
return daysBetween(today, nextBirthday);
|
|
1079
662
|
}
|
|
1080
|
-
// Example usage: Close a menu when clicking outside of it
|
|
1081
|
-
// const menu = document.getElementById('menu');
|
|
1082
|
-
// if (menu) {
|
|
1083
|
-
// clickOutside(menu, () => menu.style.display = 'none');
|
|
1084
|
-
// }
|
|
1085
663
|
/**
|
|
1086
|
-
*
|
|
664
|
+
* Calculates the age at a specific date.
|
|
665
|
+
* @param {Date} birthDate The birth date.
|
|
666
|
+
* @param {Date} atDate The date to calculate the age at.
|
|
667
|
+
* @returns {number} The calculated age.
|
|
1087
668
|
*/
|
|
1088
|
-
function
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
669
|
+
function ageAtDate(birthDate, atDate) {
|
|
670
|
+
let age = atDate.getFullYear() - birthDate.getFullYear();
|
|
671
|
+
const m = atDate.getMonth() - birthDate.getMonth();
|
|
672
|
+
if (m < 0 || (m === 0 && atDate.getDate() < birthDate.getDate())) {
|
|
673
|
+
age--;
|
|
674
|
+
}
|
|
675
|
+
return age;
|
|
1093
676
|
}
|
|
677
|
+
|
|
1094
678
|
/**
|
|
1095
|
-
*
|
|
1096
|
-
*
|
|
679
|
+
* Creates a debounced asynchronous validator function.
|
|
680
|
+
*
|
|
681
|
+
* @param validator - The async validator function to debounce.
|
|
682
|
+
* @param delay - The debounce delay in milliseconds.
|
|
683
|
+
* @returns A debounced version of the validator function.
|
|
1097
684
|
*/
|
|
1098
|
-
function
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
685
|
+
function debounceAsyncValidator(validator, delay) {
|
|
686
|
+
let currentPromiseReject = null;
|
|
687
|
+
/**
|
|
688
|
+
* Creates a debounce delay.
|
|
689
|
+
*
|
|
690
|
+
* @returns A promise that resolves after the debounce delay.
|
|
691
|
+
*/
|
|
692
|
+
function debounce() {
|
|
693
|
+
return new Promise((resolve, reject) => {
|
|
694
|
+
const { start, stop } = useTimeoutFn(() => {
|
|
695
|
+
currentPromiseReject = null;
|
|
696
|
+
resolve();
|
|
697
|
+
}, delay);
|
|
698
|
+
if (currentPromiseReject) {
|
|
699
|
+
currentPromiseReject(new Error('replaced'));
|
|
1107
700
|
}
|
|
1108
|
-
|
|
1109
|
-
|
|
701
|
+
currentPromiseReject = reject;
|
|
702
|
+
start();
|
|
703
|
+
});
|
|
1110
704
|
}
|
|
705
|
+
return function (value) {
|
|
706
|
+
if (currentPromiseReject) {
|
|
707
|
+
currentPromiseReject(new Error('replaced'));
|
|
708
|
+
currentPromiseReject = null;
|
|
709
|
+
}
|
|
710
|
+
return validator.call(this, value, debounce);
|
|
711
|
+
};
|
|
1111
712
|
}
|
|
1112
713
|
/**
|
|
1113
|
-
*
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
event.preventDefault();
|
|
1118
|
-
alert('Copying text is disabled on this page.');
|
|
1119
|
-
});
|
|
1120
|
-
}
|
|
1121
|
-
/**
|
|
1122
|
-
* Adds a custom keyboard shortcut to execute a given callback function.
|
|
1123
|
-
* @param {string} key The key to trigger the callback.
|
|
1124
|
-
* @param {Function} callback The function to execute on the key press.
|
|
1125
|
-
* @param {boolean} [ctrlKey=false] Whether Ctrl key should be pressed.
|
|
1126
|
-
* @param {boolean} [shiftKey=false] Whether Shift key should be pressed.
|
|
714
|
+
* Creates a debounced version of an asynchronous function.
|
|
715
|
+
* @param {Function} func The asynchronous function to debounce.
|
|
716
|
+
* @param {number} wait The number of milliseconds to wait before invoking the function.
|
|
717
|
+
* @returns {Function} The debounced function.
|
|
1127
718
|
*/
|
|
1128
|
-
function
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
callback();
|
|
719
|
+
function debounceAsync(func, wait) {
|
|
720
|
+
let timeoutReject = null;
|
|
721
|
+
return function (...args) {
|
|
722
|
+
if (timeoutReject) {
|
|
723
|
+
timeoutReject(new Error('replaced'));
|
|
724
|
+
timeoutReject = null;
|
|
1135
725
|
}
|
|
1136
|
-
|
|
726
|
+
return new Promise((resolve, reject) => {
|
|
727
|
+
const { start } = useTimeoutFn(() => {
|
|
728
|
+
func.apply(this, args).then(resolve).catch(reject);
|
|
729
|
+
}, wait);
|
|
730
|
+
timeoutReject = reject;
|
|
731
|
+
start();
|
|
732
|
+
});
|
|
733
|
+
};
|
|
1137
734
|
}
|
|
1138
735
|
/**
|
|
1139
|
-
*
|
|
1140
|
-
* @param {
|
|
1141
|
-
* @param {
|
|
1142
|
-
* @
|
|
736
|
+
* Creates a debounced asynchronous function that executes immediately on the first call.
|
|
737
|
+
* @param {Function} func The asynchronous function to debounce.
|
|
738
|
+
* @param {number} wait The number of milliseconds to wait before invoking the function.
|
|
739
|
+
* @returns {Function} The debounced function with immediate execution on the first call.
|
|
1143
740
|
*/
|
|
1144
|
-
function
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
741
|
+
function debounceAsyncWithImmediate(func, wait) {
|
|
742
|
+
let timeoutReject = null;
|
|
743
|
+
let callNow = true;
|
|
744
|
+
return function (...args) {
|
|
745
|
+
const context = this;
|
|
746
|
+
if (callNow) {
|
|
747
|
+
callNow = false;
|
|
748
|
+
return func.apply(context, args).finally(() => {
|
|
749
|
+
const { start } = useTimeoutFn(() => {
|
|
750
|
+
callNow = true;
|
|
751
|
+
}, wait);
|
|
752
|
+
start();
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
else {
|
|
756
|
+
if (timeoutReject) {
|
|
757
|
+
timeoutReject(new Error('replaced'));
|
|
758
|
+
timeoutReject = null;
|
|
759
|
+
}
|
|
760
|
+
return new Promise((resolve, reject) => {
|
|
761
|
+
const { start } = useTimeoutFn(() => {
|
|
762
|
+
func.apply(context, args).then(resolve).catch(reject);
|
|
763
|
+
}, wait);
|
|
764
|
+
timeoutReject = reject;
|
|
765
|
+
start();
|
|
766
|
+
});
|
|
1150
767
|
}
|
|
1151
768
|
};
|
|
1152
|
-
document.removeEventListener('keydown', handler);
|
|
1153
769
|
}
|
|
1154
770
|
/**
|
|
1155
|
-
*
|
|
1156
|
-
* @param {
|
|
771
|
+
* Creates a debounced version of a function that executes on the leading edge.
|
|
772
|
+
* @param {Function} func The function to debounce.
|
|
773
|
+
* @param {number} wait The number of milliseconds to wait before invoking the function.
|
|
774
|
+
* @returns {Function} The debounced function.
|
|
1157
775
|
*/
|
|
1158
|
-
function
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
776
|
+
function debounceLeading(func, wait) {
|
|
777
|
+
let timeoutReject = null;
|
|
778
|
+
return function (...args) {
|
|
779
|
+
const context = this;
|
|
780
|
+
if (!timeoutReject) {
|
|
781
|
+
func.apply(context, args);
|
|
782
|
+
}
|
|
783
|
+
if (timeoutReject) {
|
|
784
|
+
timeoutReject(new Error('replaced'));
|
|
785
|
+
timeoutReject = null;
|
|
1163
786
|
}
|
|
787
|
+
const { start } = useTimeoutFn(() => {
|
|
788
|
+
timeoutReject = null;
|
|
789
|
+
}, wait);
|
|
790
|
+
timeoutReject = () => { };
|
|
791
|
+
start();
|
|
1164
792
|
};
|
|
1165
|
-
document.addEventListener('keydown', handler);
|
|
1166
|
-
// Storing the handler to allow removal later
|
|
1167
|
-
disableSpecificKeys.handlers.push(handler);
|
|
1168
793
|
}
|
|
1169
|
-
disableSpecificKeys.handlers = [];
|
|
1170
794
|
/**
|
|
1171
|
-
*
|
|
795
|
+
* Creates a debounced version of a function that executes on the trailing edge.
|
|
796
|
+
* @param {Function} func The function to debounce.
|
|
797
|
+
* @param {number} wait The number of milliseconds to wait before invoking the function.
|
|
798
|
+
* @returns {Function} The debounced function.
|
|
1172
799
|
*/
|
|
1173
|
-
function
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
800
|
+
function debounceTrailing(func, wait) {
|
|
801
|
+
let timeoutReject = null;
|
|
802
|
+
return function (...args) {
|
|
803
|
+
const context = this;
|
|
804
|
+
if (timeoutReject) {
|
|
805
|
+
timeoutReject(new Error('replaced'));
|
|
806
|
+
timeoutReject = null;
|
|
807
|
+
}
|
|
808
|
+
const { start } = useTimeoutFn(() => {
|
|
809
|
+
func.apply(context, args);
|
|
810
|
+
}, wait);
|
|
811
|
+
timeoutReject = () => { };
|
|
812
|
+
start();
|
|
813
|
+
};
|
|
1178
814
|
}
|
|
1179
815
|
/**
|
|
1180
|
-
*
|
|
1181
|
-
* @param {
|
|
816
|
+
* Creates a debounced version of a function that executes on both leading and trailing edges.
|
|
817
|
+
* @param {Function} func The function to debounce.
|
|
818
|
+
* @param {number} wait The number of milliseconds to wait before invoking the function.
|
|
819
|
+
* @returns {Function} The debounced function.
|
|
1182
820
|
*/
|
|
1183
|
-
function
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
821
|
+
function debounceLeadingTrailing(func, wait) {
|
|
822
|
+
let timeoutReject = null;
|
|
823
|
+
let lastCall = 0;
|
|
824
|
+
return function (...args) {
|
|
825
|
+
const context = this;
|
|
826
|
+
const now = Date.now();
|
|
827
|
+
if (timeoutReject) {
|
|
828
|
+
timeoutReject(new Error('replaced'));
|
|
829
|
+
timeoutReject = null;
|
|
830
|
+
}
|
|
831
|
+
if (now - lastCall >= wait) {
|
|
832
|
+
func.apply(context, args);
|
|
833
|
+
lastCall = now;
|
|
834
|
+
}
|
|
835
|
+
else {
|
|
836
|
+
const { start } = useTimeoutFn(() => {
|
|
837
|
+
func.apply(context, args);
|
|
838
|
+
}, wait - (now - lastCall));
|
|
839
|
+
timeoutReject = () => { };
|
|
840
|
+
start();
|
|
1193
841
|
}
|
|
1194
842
|
};
|
|
1195
|
-
document.addEventListener('keydown', handler);
|
|
1196
|
-
// Storing the handler to allow removal later
|
|
1197
|
-
registerKeyboardShortcuts.handlers.push(handler);
|
|
1198
843
|
}
|
|
1199
|
-
registerKeyboardShortcuts.handlers = [];
|
|
1200
844
|
/**
|
|
1201
|
-
*
|
|
845
|
+
* Creates a debounced version of a function.
|
|
846
|
+
* @param {Function} func The function to debounce.
|
|
847
|
+
* @param {number} wait The number of milliseconds to wait before invoking the function.
|
|
848
|
+
* @returns {Function} The debounced function.
|
|
1202
849
|
*/
|
|
1203
|
-
function
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
850
|
+
function debounce(func, wait) {
|
|
851
|
+
let timeoutReject = null;
|
|
852
|
+
return function (...args) {
|
|
853
|
+
if (timeoutReject) {
|
|
854
|
+
timeoutReject(new Error('replaced'));
|
|
855
|
+
timeoutReject = null;
|
|
856
|
+
}
|
|
857
|
+
const { start } = useTimeoutFn(() => {
|
|
858
|
+
func.apply(this, args);
|
|
859
|
+
}, wait);
|
|
860
|
+
timeoutReject = () => { };
|
|
861
|
+
start();
|
|
862
|
+
};
|
|
1208
863
|
}
|
|
1209
864
|
/**
|
|
1210
|
-
*
|
|
1211
|
-
* @param {
|
|
1212
|
-
* @param {
|
|
865
|
+
* Creates a throttled version of a function.
|
|
866
|
+
* @param {Function} func The function to throttle.
|
|
867
|
+
* @param {number} limit The number of milliseconds to wait between function calls.
|
|
868
|
+
* @returns {Function} The throttled function.
|
|
1213
869
|
*/
|
|
1214
|
-
function
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
870
|
+
function throttle(func, limit) {
|
|
871
|
+
let lastCall = 0;
|
|
872
|
+
return function (...args) {
|
|
873
|
+
const now = Date.now();
|
|
874
|
+
if (now - lastCall >= limit) {
|
|
875
|
+
lastCall = now;
|
|
876
|
+
func.apply(this, args);
|
|
1219
877
|
}
|
|
1220
878
|
};
|
|
1221
|
-
document.addEventListener('keydown', handler);
|
|
1222
|
-
// Storing the handler to allow removal later
|
|
1223
|
-
addKeyListener.handlers.push(handler);
|
|
1224
879
|
}
|
|
1225
|
-
|
|
880
|
+
|
|
1226
881
|
/**
|
|
1227
|
-
*
|
|
882
|
+
* Converts a FormData object to a plain JavaScript object.
|
|
883
|
+
* @param {FormData} formData The FormData object to convert.
|
|
884
|
+
* @returns {Record<string, any>} The plain JavaScript object.
|
|
1228
885
|
*/
|
|
1229
|
-
function
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
886
|
+
function formDataToObject(formData) {
|
|
887
|
+
const obj = {};
|
|
888
|
+
formData.forEach((value, key) => {
|
|
889
|
+
// Handle multiple values for the same key
|
|
890
|
+
if (obj[key]) {
|
|
891
|
+
if (!Array.isArray(obj[key])) {
|
|
892
|
+
obj[key] = [obj[key]];
|
|
893
|
+
}
|
|
894
|
+
obj[key].push(value);
|
|
895
|
+
}
|
|
896
|
+
else {
|
|
897
|
+
obj[key] = value;
|
|
898
|
+
}
|
|
899
|
+
});
|
|
900
|
+
return obj;
|
|
1234
901
|
}
|
|
1235
902
|
/**
|
|
1236
|
-
*
|
|
1237
|
-
* @param {
|
|
1238
|
-
* @
|
|
903
|
+
* Reads a file as text.
|
|
904
|
+
* @param {File} file The file to read.
|
|
905
|
+
* @returns {Promise<string>} A promise that resolves with the file content.
|
|
1239
906
|
*/
|
|
1240
|
-
function
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
// Storing the handler to allow removal later
|
|
1248
|
-
detectKeyHold.handlers.push(keyDownHandler);
|
|
907
|
+
function readFileAsText(file) {
|
|
908
|
+
return new Promise((resolve, reject) => {
|
|
909
|
+
const reader = new FileReader();
|
|
910
|
+
reader.onload = () => resolve(reader.result);
|
|
911
|
+
reader.onerror = reject;
|
|
912
|
+
reader.readAsText(file);
|
|
913
|
+
});
|
|
1249
914
|
}
|
|
1250
|
-
detectKeyHold.handlers = [];
|
|
1251
915
|
/**
|
|
1252
|
-
*
|
|
916
|
+
* Reads a file as a Data URL.
|
|
917
|
+
* @param {File} file The file to read.
|
|
918
|
+
* @returns {Promise<string>} A promise that resolves with the Data URL.
|
|
1253
919
|
*/
|
|
1254
|
-
function
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
920
|
+
function readFileAsDataURL(file) {
|
|
921
|
+
return new Promise((resolve, reject) => {
|
|
922
|
+
const reader = new FileReader();
|
|
923
|
+
reader.onload = () => resolve(reader.result);
|
|
924
|
+
reader.onerror = reject;
|
|
925
|
+
reader.readAsDataURL(file);
|
|
926
|
+
});
|
|
1259
927
|
}
|
|
1260
928
|
/**
|
|
1261
|
-
*
|
|
1262
|
-
* @
|
|
929
|
+
* Creates a Blob from a string.
|
|
930
|
+
* @param {string} content The string content for the Blob.
|
|
931
|
+
* @param {string} [type='text/plain'] The MIME type of the Blob.
|
|
932
|
+
* @returns {Blob} The Blob object.
|
|
933
|
+
*/
|
|
934
|
+
function stringToBlob(content, type = 'text/plain') {
|
|
935
|
+
return new Blob([content], { type });
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* Creates a Blob from an ArrayBuffer.
|
|
939
|
+
* @param {ArrayBuffer} buffer The ArrayBuffer to convert.
|
|
940
|
+
* @param {string} [type='application/octet-stream'] The MIME type of the Blob.
|
|
941
|
+
* @returns {Blob} The Blob object.
|
|
1263
942
|
*/
|
|
1264
|
-
function
|
|
1265
|
-
|
|
1266
|
-
const keyDownHandler = (event) => {
|
|
1267
|
-
pressedKeys.add(event.key);
|
|
1268
|
-
};
|
|
1269
|
-
const keyUpHandler = (event) => {
|
|
1270
|
-
pressedKeys.delete(event.key);
|
|
1271
|
-
};
|
|
1272
|
-
document.addEventListener('keydown', keyDownHandler);
|
|
1273
|
-
document.addEventListener('keyup', keyUpHandler);
|
|
1274
|
-
// Function to remove listeners
|
|
1275
|
-
createKeyMap.clearListeners = () => {
|
|
1276
|
-
document.removeEventListener('keydown', keyDownHandler);
|
|
1277
|
-
document.removeEventListener('keyup', keyUpHandler);
|
|
1278
|
-
};
|
|
1279
|
-
return pressedKeys;
|
|
943
|
+
function bufferToBlob(buffer, type = 'application/octet-stream') {
|
|
944
|
+
return new Blob([buffer], { type });
|
|
1280
945
|
}
|
|
1281
946
|
/**
|
|
1282
|
-
*
|
|
947
|
+
* Creates and downloads a file from Blob data.
|
|
948
|
+
* @param {Blob} blob The Blob containing the file data.
|
|
949
|
+
* @param {string} fileName The name of the file to create.
|
|
1283
950
|
*/
|
|
1284
|
-
|
|
951
|
+
function downloadBlob(blob, fileName) {
|
|
952
|
+
const link = document.createElement('a');
|
|
953
|
+
const url = URL.createObjectURL(blob);
|
|
954
|
+
link.setAttribute('href', url);
|
|
955
|
+
link.setAttribute('download', fileName);
|
|
956
|
+
// Append link to the body and trigger a click
|
|
957
|
+
document.body.appendChild(link);
|
|
958
|
+
link.click();
|
|
959
|
+
// Clean up
|
|
960
|
+
document.body.removeChild(link);
|
|
961
|
+
URL.revokeObjectURL(url);
|
|
962
|
+
}
|
|
1285
963
|
/**
|
|
1286
|
-
*
|
|
1287
|
-
* @param {
|
|
1288
|
-
* @param {
|
|
964
|
+
* Creates a FormData object containing a Blob.
|
|
965
|
+
* @param {Blob} blob The Blob to include in the FormData.
|
|
966
|
+
* @param {string} name The name of the form field.
|
|
967
|
+
* @param {string} [fileName='file'] The file name for the Blob.
|
|
968
|
+
* @returns {FormData} The FormData object.
|
|
1289
969
|
*/
|
|
1290
|
-
function
|
|
1291
|
-
const
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
if (keys.every(key => pressedKeys.has(key))) {
|
|
1295
|
-
callback();
|
|
1296
|
-
}
|
|
1297
|
-
};
|
|
1298
|
-
const keyUpHandler = (event) => {
|
|
1299
|
-
pressedKeys.delete(event.key);
|
|
1300
|
-
};
|
|
1301
|
-
document.addEventListener('keydown', keyDownHandler);
|
|
1302
|
-
document.addEventListener('keyup', keyUpHandler);
|
|
1303
|
-
// Store the handlers for later removal
|
|
1304
|
-
customShortcut.handlers.push({ keyDownHandler, keyUpHandler });
|
|
970
|
+
function blobToFormData(blob, name, fileName = 'file') {
|
|
971
|
+
const formData = new FormData();
|
|
972
|
+
formData.append(name, blob, fileName);
|
|
973
|
+
return formData;
|
|
1305
974
|
}
|
|
1306
|
-
|
|
975
|
+
|
|
1307
976
|
/**
|
|
1308
|
-
*
|
|
977
|
+
* Converts a Proxy object to a plain object.
|
|
978
|
+
* @param {ProxyConstructor} proxy The Proxy object to convert.
|
|
979
|
+
* @returns {Object} The plain object.
|
|
1309
980
|
*/
|
|
1310
|
-
function
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
981
|
+
function proxyToPlainObject(proxy) {
|
|
982
|
+
if (!proxy)
|
|
983
|
+
return {};
|
|
984
|
+
const plainObject = {};
|
|
985
|
+
for (const property of Object.keys(proxy)) {
|
|
986
|
+
plainObject[property] = proxy[property];
|
|
1314
987
|
}
|
|
1315
|
-
|
|
988
|
+
return plainObject;
|
|
1316
989
|
}
|
|
1317
990
|
/**
|
|
1318
|
-
*
|
|
1319
|
-
* @param {
|
|
1320
|
-
* @param {
|
|
1321
|
-
* @
|
|
1322
|
-
* @param {boolean} altKey If true, include Alt key in the event.
|
|
991
|
+
* Compares two objects to check if they have the same keys.
|
|
992
|
+
* @param {Object} object1 The first object to compare.
|
|
993
|
+
* @param {Object} object2 The second object to compare.
|
|
994
|
+
* @returns {boolean} True if the objects have the same keys, otherwise false.
|
|
1323
995
|
*/
|
|
1324
|
-
function
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
ctrlKey,
|
|
1328
|
-
shiftKey,
|
|
1329
|
-
altKey,
|
|
1330
|
-
bubbles: true,
|
|
1331
|
-
cancelable: true
|
|
996
|
+
function compareObject(object1, object2) {
|
|
997
|
+
return Object.keys(object1).every(function (element) {
|
|
998
|
+
return Object.keys(object2).includes(element);
|
|
1332
999
|
});
|
|
1333
|
-
document.dispatchEvent(event);
|
|
1334
1000
|
}
|
|
1335
|
-
|
|
1336
1001
|
/**
|
|
1337
|
-
*
|
|
1338
|
-
* @param {
|
|
1339
|
-
* @
|
|
1002
|
+
* Deeply compares two objects to check if they are equal.
|
|
1003
|
+
* @param {Object} object1 The first object to compare.
|
|
1004
|
+
* @param {Object} object2 The second object to compare.
|
|
1005
|
+
* @returns {boolean} True if the objects are deeply equal, otherwise false.
|
|
1340
1006
|
*/
|
|
1341
|
-
function
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1007
|
+
function deepEqual(object1, object2) {
|
|
1008
|
+
if (object1 === object2)
|
|
1009
|
+
return true;
|
|
1010
|
+
if (typeof object1 !== 'object' || typeof object2 !== 'object' || object1 === null || object2 === null) {
|
|
1011
|
+
return false;
|
|
1012
|
+
}
|
|
1013
|
+
const keys1 = Object.keys(object1);
|
|
1014
|
+
const keys2 = Object.keys(object2);
|
|
1015
|
+
if (keys1.length !== keys2.length) {
|
|
1016
|
+
return false;
|
|
1017
|
+
}
|
|
1018
|
+
for (const key of keys1) {
|
|
1019
|
+
if (!keys2.includes(key) || !deepEqual(object1[key], object2[key])) {
|
|
1020
|
+
return false;
|
|
1350
1021
|
}
|
|
1351
1022
|
}
|
|
1352
|
-
return
|
|
1023
|
+
return true;
|
|
1353
1024
|
}
|
|
1354
1025
|
/**
|
|
1355
|
-
*
|
|
1356
|
-
* @param {
|
|
1357
|
-
* @
|
|
1358
|
-
* @returns {string} The formatted date string.
|
|
1026
|
+
* Deeply clones an object.
|
|
1027
|
+
* @param {Object} obj The object to clone.
|
|
1028
|
+
* @returns {Object} The cloned object.
|
|
1359
1029
|
*/
|
|
1360
|
-
function
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1030
|
+
function deepClone(obj) {
|
|
1031
|
+
if (obj === null || typeof obj !== 'object') {
|
|
1032
|
+
return obj;
|
|
1033
|
+
}
|
|
1034
|
+
if (obj instanceof Date) {
|
|
1035
|
+
return new Date(obj.getTime());
|
|
1036
|
+
}
|
|
1037
|
+
if (obj instanceof Array) {
|
|
1038
|
+
return obj.map(item => deepClone(item));
|
|
1039
|
+
}
|
|
1040
|
+
if (obj instanceof Object) {
|
|
1041
|
+
const copy = {};
|
|
1042
|
+
for (const key in obj) {
|
|
1043
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
1044
|
+
copy[key] = deepClone(obj[key]);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
return copy;
|
|
1048
|
+
}
|
|
1049
|
+
throw new Error('Unable to clone object! Its type is not supported.');
|
|
1370
1050
|
}
|
|
1371
1051
|
/**
|
|
1372
|
-
*
|
|
1373
|
-
* @param {
|
|
1374
|
-
* @
|
|
1375
|
-
* @returns {number} The number of days between the two dates.
|
|
1052
|
+
* Converts an object to a query string.
|
|
1053
|
+
* @param {Object} obj The object to convert.
|
|
1054
|
+
* @returns {string} The query string.
|
|
1376
1055
|
*/
|
|
1377
|
-
function
|
|
1378
|
-
|
|
1379
|
-
|
|
1056
|
+
function objectToQueryString(obj) {
|
|
1057
|
+
return Object.keys(obj)
|
|
1058
|
+
.map(key => encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]))
|
|
1059
|
+
.join('&');
|
|
1060
|
+
}
|
|
1061
|
+
// Example usage:
|
|
1062
|
+
// objectToQueryString({ name: 'John Doe', age: 30 }); // 'name=John%20Doe&age=30'
|
|
1063
|
+
/**
|
|
1064
|
+
* Gets the differences between two objects.
|
|
1065
|
+
* @param {Object} object1 The first object.
|
|
1066
|
+
* @param {Object} object2 The second object.
|
|
1067
|
+
* @returns {Object} An object containing the differences.
|
|
1068
|
+
*/
|
|
1069
|
+
function getObjectDifferences(object1, object2) {
|
|
1070
|
+
const differences = {};
|
|
1071
|
+
const keys = new Set([...Object.keys(object1), ...Object.keys(object2)]);
|
|
1072
|
+
for (const key of keys) {
|
|
1073
|
+
if (object1[key] !== object2[key]) {
|
|
1074
|
+
differences[key] = { object1: object1[key], object2: object2[key] };
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
return differences;
|
|
1380
1078
|
}
|
|
1381
1079
|
/**
|
|
1382
|
-
*
|
|
1383
|
-
* @param {
|
|
1384
|
-
* @param {
|
|
1385
|
-
* @returns {
|
|
1080
|
+
* Filters an object by a list of keys.
|
|
1081
|
+
* @param {Object} obj The object to filter.
|
|
1082
|
+
* @param {Array<string>} keys The keys to keep.
|
|
1083
|
+
* @returns {Object} The filtered object.
|
|
1386
1084
|
*/
|
|
1387
|
-
function
|
|
1388
|
-
const
|
|
1389
|
-
|
|
1390
|
-
|
|
1085
|
+
function filterObjectByKeys(obj, keys) {
|
|
1086
|
+
const filteredObject = {};
|
|
1087
|
+
for (const key of keys) {
|
|
1088
|
+
if (key in obj) {
|
|
1089
|
+
filteredObject[key] = obj[key];
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
return filteredObject;
|
|
1391
1093
|
}
|
|
1094
|
+
// Example usage:
|
|
1095
|
+
// filterObjectByKeys({ name: 'John', age: 30, job: 'Developer' }, ['name', 'job']); // { name: 'John', job: 'Developer' }
|
|
1392
1096
|
/**
|
|
1393
|
-
*
|
|
1394
|
-
* @param {
|
|
1395
|
-
* @param {
|
|
1396
|
-
* @returns {
|
|
1097
|
+
* Deeply merges two objects.
|
|
1098
|
+
* @param {Object} target The target object to merge into.
|
|
1099
|
+
* @param {Object} source The source object to merge from.
|
|
1100
|
+
* @returns {Object} The merged object.
|
|
1397
1101
|
*/
|
|
1398
|
-
function
|
|
1399
|
-
|
|
1102
|
+
function deepMerge(target, source) {
|
|
1103
|
+
if (target === null || typeof target !== 'object' || typeof source !== 'object') {
|
|
1104
|
+
return target;
|
|
1105
|
+
}
|
|
1106
|
+
for (const key in source) {
|
|
1107
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
1108
|
+
if (source[key] && typeof source[key] === 'object') {
|
|
1109
|
+
if (target && !target[key]) {
|
|
1110
|
+
Object.assign(target, { [key]: {} });
|
|
1111
|
+
}
|
|
1112
|
+
deepMerge(target[key], source[key]);
|
|
1113
|
+
}
|
|
1114
|
+
else {
|
|
1115
|
+
Object.assign(target, { [key]: source[key] });
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
return target;
|
|
1400
1120
|
}
|
|
1401
1121
|
/**
|
|
1402
|
-
*
|
|
1403
|
-
* @param {
|
|
1404
|
-
* @returns {boolean} True if the
|
|
1122
|
+
* Checks if an object is empty.
|
|
1123
|
+
* @param {Object} obj The object to check.
|
|
1124
|
+
* @returns {boolean} True if the object is empty, otherwise false.
|
|
1405
1125
|
*/
|
|
1406
|
-
function
|
|
1407
|
-
return (
|
|
1126
|
+
function isEmptyObject(obj) {
|
|
1127
|
+
return Object.keys(obj).length === 0;
|
|
1408
1128
|
}
|
|
1409
1129
|
/**
|
|
1410
|
-
*
|
|
1411
|
-
* @param {
|
|
1412
|
-
* @
|
|
1130
|
+
* Safely accesses nested properties in an object.
|
|
1131
|
+
* @param {Object} obj The object to access.
|
|
1132
|
+
* @param {Array<string>} keys The array of keys representing the path.
|
|
1133
|
+
* @returns {any} The value at the nested path, or undefined if not found.
|
|
1413
1134
|
*/
|
|
1414
|
-
function
|
|
1415
|
-
return
|
|
1135
|
+
function safeGet(obj, keys) {
|
|
1136
|
+
return keys.reduce((acc, key) => (acc && acc[key] !== undefined) ? acc[key] : undefined, obj);
|
|
1416
1137
|
}
|
|
1138
|
+
// Example usage:
|
|
1139
|
+
// safeGet({ a: { b: { c: 10 } } }, ['a', 'b', 'c']); // 10
|
|
1140
|
+
// safeGet({ a: { b: { c: 10 } } }, ['a', 'x', 'c']); // undefined
|
|
1417
1141
|
/**
|
|
1418
|
-
*
|
|
1419
|
-
* @param {
|
|
1420
|
-
* @returns {
|
|
1142
|
+
* Removes empty properties (null, undefined, or empty string) from an object.
|
|
1143
|
+
* @param {Object} obj The object to clean.
|
|
1144
|
+
* @returns {Object} A new object without empty properties.
|
|
1421
1145
|
*/
|
|
1422
|
-
function
|
|
1423
|
-
return
|
|
1146
|
+
function removeEmptyProperties(obj) {
|
|
1147
|
+
return Object.keys(obj)
|
|
1148
|
+
.filter(key => obj[key] !== null && obj[key] !== undefined && obj[key] !== '')
|
|
1149
|
+
.reduce((acc, key) => {
|
|
1150
|
+
acc[key] = obj[key];
|
|
1151
|
+
return acc;
|
|
1152
|
+
}, {});
|
|
1424
1153
|
}
|
|
1154
|
+
// Example usage:
|
|
1155
|
+
// removeEmptyProperties({ a: null, b: 2, c: undefined, d: '', e: 'hello' }); // { b: 2, e: 'hello' }
|
|
1425
1156
|
/**
|
|
1426
|
-
*
|
|
1427
|
-
* @param {
|
|
1428
|
-
* @returns {
|
|
1157
|
+
* Retrieves all keys of an object as an array.
|
|
1158
|
+
* @param {Object} obj The object to retrieve keys from.
|
|
1159
|
+
* @returns {Array<string>} The array of keys.
|
|
1429
1160
|
*/
|
|
1430
|
-
function
|
|
1431
|
-
|
|
1432
|
-
let age = today.getFullYear() - birthDate.getFullYear();
|
|
1433
|
-
const m = today.getMonth() - birthDate.getMonth();
|
|
1434
|
-
if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
|
|
1435
|
-
age--;
|
|
1436
|
-
}
|
|
1437
|
-
return age;
|
|
1161
|
+
function getObjectKeys(obj) {
|
|
1162
|
+
return Object.keys(obj);
|
|
1438
1163
|
}
|
|
1164
|
+
// Example usage:
|
|
1165
|
+
// getObjectKeys({ name: 'John', age: 30 }); // ['name', 'age']
|
|
1439
1166
|
/**
|
|
1440
|
-
*
|
|
1441
|
-
* @param {
|
|
1442
|
-
* @returns {
|
|
1167
|
+
* Checks if an object has nested properties.
|
|
1168
|
+
* @param {Object} obj The object to check.
|
|
1169
|
+
* @returns {boolean} True if there are nested properties, false otherwise.
|
|
1443
1170
|
*/
|
|
1444
|
-
function
|
|
1445
|
-
|
|
1446
|
-
const currentYear = today.getFullYear();
|
|
1447
|
-
const nextBirthday = new Date(birthDate);
|
|
1448
|
-
nextBirthday.setFullYear(currentYear);
|
|
1449
|
-
if (nextBirthday < today) {
|
|
1450
|
-
nextBirthday.setFullYear(currentYear + 1);
|
|
1451
|
-
}
|
|
1452
|
-
return daysBetween(today, nextBirthday);
|
|
1171
|
+
function hasNestedProperties(obj) {
|
|
1172
|
+
return Object.values(obj).some(value => typeof value === 'object' && value !== null);
|
|
1453
1173
|
}
|
|
1174
|
+
// Example usage:
|
|
1175
|
+
// hasNestedProperties({ a: 1, b: { c: 2 } }); // true
|
|
1176
|
+
// hasNestedProperties({ a: 1, b: 2 }); // false
|
|
1454
1177
|
/**
|
|
1455
|
-
*
|
|
1456
|
-
* @param {
|
|
1457
|
-
* @param {
|
|
1458
|
-
* @
|
|
1178
|
+
* Converts an object to FormData, handling nested objects.
|
|
1179
|
+
* @param {Object} obj The object to convert.
|
|
1180
|
+
* @param {FormData} [formData] The FormData object to append to.
|
|
1181
|
+
* @param {string} [parentKey] The parent key for nested objects.
|
|
1182
|
+
* @returns {FormData} The FormData object.
|
|
1459
1183
|
*/
|
|
1460
|
-
function
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1184
|
+
function objectToFormDataEnhanced(obj, formData = new FormData(), parentKey = '') {
|
|
1185
|
+
Object.entries(obj).forEach(([key, value]) => {
|
|
1186
|
+
const finalKey = parentKey ? `${parentKey}[${key}]` : key;
|
|
1187
|
+
if (value && typeof value === 'object' && !(value instanceof File)) {
|
|
1188
|
+
objectToFormDataEnhanced(value, formData, finalKey);
|
|
1189
|
+
}
|
|
1190
|
+
else {
|
|
1191
|
+
formData.append(finalKey, value);
|
|
1192
|
+
}
|
|
1193
|
+
});
|
|
1194
|
+
return formData;
|
|
1467
1195
|
}
|
|
1468
|
-
|
|
1196
|
+
// Example usage:
|
|
1197
|
+
// objectToFormDataEnhanced({ user: { name: 'John', age: 30 } });
|
|
1469
1198
|
/**
|
|
1470
|
-
*
|
|
1199
|
+
* Converts a JavaScript object into FormData.
|
|
1471
1200
|
*
|
|
1472
|
-
* @param
|
|
1473
|
-
* @param
|
|
1474
|
-
* @
|
|
1201
|
+
* @param obj - The object to be converted.
|
|
1202
|
+
* @param form - An optional FormData instance to use.
|
|
1203
|
+
* @param namespace - An optional namespace to use for nested objects.
|
|
1204
|
+
* @returns The FormData instance with the object's key-value pairs.
|
|
1475
1205
|
*/
|
|
1476
|
-
function
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
currentPromiseReject = null;
|
|
1487
|
-
resolve();
|
|
1488
|
-
}, delay);
|
|
1489
|
-
if (currentPromiseReject) {
|
|
1490
|
-
currentPromiseReject(new Error('replaced'));
|
|
1206
|
+
const objectToFormData = function (obj, form, namespace) {
|
|
1207
|
+
const fd = form || new FormData();
|
|
1208
|
+
let formKey;
|
|
1209
|
+
for (const property in obj) {
|
|
1210
|
+
if (obj[property] === undefined) {
|
|
1211
|
+
continue;
|
|
1212
|
+
}
|
|
1213
|
+
if (Object.prototype.hasOwnProperty.call(obj, property)) {
|
|
1214
|
+
if (namespace) {
|
|
1215
|
+
formKey = `${namespace}[${property}]`;
|
|
1491
1216
|
}
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1217
|
+
else {
|
|
1218
|
+
formKey = property;
|
|
1219
|
+
}
|
|
1220
|
+
if (typeof obj[property] === 'object' && !(obj[property] instanceof File)) {
|
|
1221
|
+
// Recursively handle nested objects
|
|
1222
|
+
objectToFormData(obj[property], fd, formKey);
|
|
1223
|
+
}
|
|
1224
|
+
else {
|
|
1225
|
+
// Convert boolean values to 1/0
|
|
1226
|
+
const value = obj[property] === true || obj[property] === false ? Number(obj[property]) : obj[property];
|
|
1227
|
+
fd.append(formKey, value);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1495
1230
|
}
|
|
1496
|
-
return
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1231
|
+
return fd;
|
|
1232
|
+
};
|
|
1233
|
+
/**
|
|
1234
|
+
* Flattens a nested object, bringing all properties to the top level.
|
|
1235
|
+
* @param {Object} obj The object to flatten.
|
|
1236
|
+
* @param {string} [parentKey] The parent key for nested properties.
|
|
1237
|
+
* @param {Object} [result] The resulting flattened object.
|
|
1238
|
+
* @returns {Object} The flattened object.
|
|
1239
|
+
*/
|
|
1240
|
+
function flattenObject(obj, parentKey = '', result = {}) {
|
|
1241
|
+
for (const key in obj) {
|
|
1242
|
+
if (obj.hasOwnProperty(key)) {
|
|
1243
|
+
const propName = parentKey ? `${parentKey}.${key}` : key;
|
|
1244
|
+
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
|
1245
|
+
flattenObject(obj[key], propName, result);
|
|
1246
|
+
}
|
|
1247
|
+
else {
|
|
1248
|
+
result[propName] = obj[key];
|
|
1249
|
+
}
|
|
1500
1250
|
}
|
|
1501
|
-
|
|
1502
|
-
|
|
1251
|
+
}
|
|
1252
|
+
return result;
|
|
1503
1253
|
}
|
|
1254
|
+
// Example usage:
|
|
1255
|
+
// flattenObject({ a: 1, b: { c: 2, d: { e: 3 } } }); // { 'a': 1, 'b.c': 2, 'b.d.e': 3 }
|
|
1256
|
+
|
|
1504
1257
|
/**
|
|
1505
|
-
*
|
|
1506
|
-
* @param {
|
|
1507
|
-
* @
|
|
1508
|
-
* @returns {Function} The debounced function.
|
|
1258
|
+
* Capitalizes the first character of a string.
|
|
1259
|
+
* @param {string} s The string to capitalize.
|
|
1260
|
+
* @returns {string} The capitalized string.
|
|
1509
1261
|
*/
|
|
1510
|
-
function
|
|
1511
|
-
|
|
1512
|
-
return function (...args) {
|
|
1513
|
-
if (timeoutReject) {
|
|
1514
|
-
timeoutReject(new Error('replaced'));
|
|
1515
|
-
timeoutReject = null;
|
|
1516
|
-
}
|
|
1517
|
-
return new Promise((resolve, reject) => {
|
|
1518
|
-
const { start } = useTimeoutFn(() => {
|
|
1519
|
-
func.apply(this, args).then(resolve).catch(reject);
|
|
1520
|
-
}, wait);
|
|
1521
|
-
timeoutReject = reject;
|
|
1522
|
-
start();
|
|
1523
|
-
});
|
|
1524
|
-
};
|
|
1262
|
+
function upperFirst(s) {
|
|
1263
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
1525
1264
|
}
|
|
1265
|
+
// Example usage:
|
|
1266
|
+
// console.log(upperFirst('hello')); // 'Hello'
|
|
1526
1267
|
/**
|
|
1527
|
-
*
|
|
1528
|
-
* @param {
|
|
1529
|
-
* @
|
|
1530
|
-
* @returns {Function} The debounced function with immediate execution on the first call.
|
|
1268
|
+
* Lowercases the first character of a string.
|
|
1269
|
+
* @param {string} s The string to lowercase.
|
|
1270
|
+
* @returns {string} The lowercased string.
|
|
1531
1271
|
*/
|
|
1532
|
-
function
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
}
|
|
1551
|
-
return new Promise((resolve, reject) => {
|
|
1552
|
-
const { start } = useTimeoutFn(() => {
|
|
1553
|
-
func.apply(context, args).then(resolve).catch(reject);
|
|
1554
|
-
}, wait);
|
|
1555
|
-
timeoutReject = reject;
|
|
1556
|
-
start();
|
|
1557
|
-
});
|
|
1558
|
-
}
|
|
1559
|
-
};
|
|
1272
|
+
function lowerFirst(s) {
|
|
1273
|
+
return s.charAt(0).toLowerCase() + s.slice(1);
|
|
1274
|
+
}
|
|
1275
|
+
// Example usage:
|
|
1276
|
+
// console.log(lowerFirst('Hello')); // 'hello'
|
|
1277
|
+
/**
|
|
1278
|
+
* Removes accents and special characters from a string and converts it to a URL-friendly format.
|
|
1279
|
+
* @param {string} input The string to process.
|
|
1280
|
+
* @returns {string} The processed string.
|
|
1281
|
+
*/
|
|
1282
|
+
function removeAccent(input) {
|
|
1283
|
+
return input
|
|
1284
|
+
.toLowerCase()
|
|
1285
|
+
.trim()
|
|
1286
|
+
.replace(/[\s_-]+/g, "-")
|
|
1287
|
+
.replace(/^-+|-+$/g, "")
|
|
1288
|
+
.normalize("NFD")
|
|
1289
|
+
.replace(/[\u0300-\u036f]/g, "");
|
|
1560
1290
|
}
|
|
1291
|
+
// Example usage:
|
|
1292
|
+
// console.log(removeAccent('Café du Nord')); // 'cafe-du-nord'
|
|
1561
1293
|
/**
|
|
1562
|
-
*
|
|
1563
|
-
* @param {
|
|
1564
|
-
* @
|
|
1565
|
-
* @returns {Function} The debounced function.
|
|
1294
|
+
* Reverses a string.
|
|
1295
|
+
* @param {string} str The string to reverse.
|
|
1296
|
+
* @returns {string} The reversed string.
|
|
1566
1297
|
*/
|
|
1567
|
-
function
|
|
1568
|
-
|
|
1569
|
-
return function (...args) {
|
|
1570
|
-
const context = this;
|
|
1571
|
-
if (!timeoutReject) {
|
|
1572
|
-
func.apply(context, args);
|
|
1573
|
-
}
|
|
1574
|
-
if (timeoutReject) {
|
|
1575
|
-
timeoutReject(new Error('replaced'));
|
|
1576
|
-
timeoutReject = null;
|
|
1577
|
-
}
|
|
1578
|
-
const { start } = useTimeoutFn(() => {
|
|
1579
|
-
timeoutReject = null;
|
|
1580
|
-
}, wait);
|
|
1581
|
-
timeoutReject = () => { };
|
|
1582
|
-
start();
|
|
1583
|
-
};
|
|
1298
|
+
function reverseString(str) {
|
|
1299
|
+
return str.split('').reverse().join('');
|
|
1584
1300
|
}
|
|
1301
|
+
// Example usage:
|
|
1302
|
+
// console.log(reverseString('hello')); // 'olleh'
|
|
1585
1303
|
/**
|
|
1586
|
-
*
|
|
1587
|
-
* @param {
|
|
1588
|
-
* @
|
|
1589
|
-
* @returns {Function} The debounced function.
|
|
1304
|
+
* Counts the number of words in a string.
|
|
1305
|
+
* @param {string} str The string to analyze.
|
|
1306
|
+
* @returns {number} The word count.
|
|
1590
1307
|
*/
|
|
1591
|
-
function
|
|
1592
|
-
|
|
1593
|
-
return function (...args) {
|
|
1594
|
-
const context = this;
|
|
1595
|
-
if (timeoutReject) {
|
|
1596
|
-
timeoutReject(new Error('replaced'));
|
|
1597
|
-
timeoutReject = null;
|
|
1598
|
-
}
|
|
1599
|
-
const { start } = useTimeoutFn(() => {
|
|
1600
|
-
func.apply(context, args);
|
|
1601
|
-
}, wait);
|
|
1602
|
-
timeoutReject = () => { };
|
|
1603
|
-
start();
|
|
1604
|
-
};
|
|
1308
|
+
function countWords(str) {
|
|
1309
|
+
return str.trim().split(/\s+/).length;
|
|
1605
1310
|
}
|
|
1311
|
+
// Example usage:
|
|
1312
|
+
// console.log(countWords('Hello world!')); // 2
|
|
1606
1313
|
/**
|
|
1607
|
-
*
|
|
1608
|
-
* @param {
|
|
1609
|
-
* @param {number}
|
|
1610
|
-
* @returns {
|
|
1314
|
+
* Truncates a string to the specified length and adds ellipsis if necessary.
|
|
1315
|
+
* @param {string} str The string to truncate.
|
|
1316
|
+
* @param {number} maxLength The maximum length of the string.
|
|
1317
|
+
* @returns {string} The truncated string.
|
|
1611
1318
|
*/
|
|
1612
|
-
function
|
|
1613
|
-
|
|
1614
|
-
let lastCall = 0;
|
|
1615
|
-
return function (...args) {
|
|
1616
|
-
const context = this;
|
|
1617
|
-
const now = Date.now();
|
|
1618
|
-
if (timeoutReject) {
|
|
1619
|
-
timeoutReject(new Error('replaced'));
|
|
1620
|
-
timeoutReject = null;
|
|
1621
|
-
}
|
|
1622
|
-
if (now - lastCall >= wait) {
|
|
1623
|
-
func.apply(context, args);
|
|
1624
|
-
lastCall = now;
|
|
1625
|
-
}
|
|
1626
|
-
else {
|
|
1627
|
-
const { start } = useTimeoutFn(() => {
|
|
1628
|
-
func.apply(context, args);
|
|
1629
|
-
}, wait - (now - lastCall));
|
|
1630
|
-
timeoutReject = () => { };
|
|
1631
|
-
start();
|
|
1632
|
-
}
|
|
1633
|
-
};
|
|
1319
|
+
function truncateString(str, maxLength) {
|
|
1320
|
+
return str.length > maxLength ? str.slice(0, maxLength) + '...' : str;
|
|
1634
1321
|
}
|
|
1322
|
+
// Example usage:
|
|
1323
|
+
// console.log(truncateString('This is a long string', 10)); // 'This is a...'
|
|
1635
1324
|
/**
|
|
1636
|
-
*
|
|
1637
|
-
* @param {
|
|
1638
|
-
* @
|
|
1639
|
-
* @returns {Function} The debounced function.
|
|
1325
|
+
* Converts a string to camel case.
|
|
1326
|
+
* @param {string} str The string to convert.
|
|
1327
|
+
* @returns {string} The camel cased string.
|
|
1640
1328
|
*/
|
|
1641
|
-
function
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1329
|
+
function toCamelCase(str) {
|
|
1330
|
+
return str
|
|
1331
|
+
.toLowerCase()
|
|
1332
|
+
.replace(/[^a-zA-Z0-9]+(.)/g, (match, chr) => chr.toUpperCase());
|
|
1333
|
+
}
|
|
1334
|
+
// Example usage:
|
|
1335
|
+
// console.log(toCamelCase('hello world example')); // 'helloWorldExample'
|
|
1336
|
+
/**
|
|
1337
|
+
* Converts a string to kebab case.
|
|
1338
|
+
* @param {string} str The string to convert.
|
|
1339
|
+
* @returns {string} The kebab cased string.
|
|
1340
|
+
*/
|
|
1341
|
+
function toKebabCase(str) {
|
|
1342
|
+
return str
|
|
1343
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
1344
|
+
.replace(/[\s_]+/g, '-')
|
|
1345
|
+
.toLowerCase();
|
|
1654
1346
|
}
|
|
1347
|
+
// Example usage:
|
|
1348
|
+
// console.log(toKebabCase('Hello World Example')); // 'hello-world-example'
|
|
1655
1349
|
/**
|
|
1656
|
-
*
|
|
1657
|
-
* @param {
|
|
1658
|
-
* @param {
|
|
1659
|
-
* @
|
|
1350
|
+
* Replaces all instances of a substring within a string.
|
|
1351
|
+
* @param {string} str The original string.
|
|
1352
|
+
* @param {string} find The substring to find.
|
|
1353
|
+
* @param {string} replace The substring to replace with.
|
|
1354
|
+
* @returns {string} The modified string.
|
|
1660
1355
|
*/
|
|
1661
|
-
function
|
|
1662
|
-
|
|
1663
|
-
return function (...args) {
|
|
1664
|
-
const now = Date.now();
|
|
1665
|
-
if (now - lastCall >= limit) {
|
|
1666
|
-
lastCall = now;
|
|
1667
|
-
func.apply(this, args);
|
|
1668
|
-
}
|
|
1669
|
-
};
|
|
1356
|
+
function replaceAll(str, find, replace) {
|
|
1357
|
+
return str.split(find).join(replace);
|
|
1670
1358
|
}
|
|
1671
|
-
|
|
1359
|
+
// Example usage:
|
|
1360
|
+
// console.log(replaceAll('hello world', 'o', 'a')); // 'hella warld'
|
|
1672
1361
|
/**
|
|
1673
|
-
*
|
|
1674
|
-
* @param {
|
|
1675
|
-
* @returns {
|
|
1362
|
+
* Generates a random string of a specific length.
|
|
1363
|
+
* @param {number} length The length of the string to generate.
|
|
1364
|
+
* @returns {string} The random string.
|
|
1676
1365
|
*/
|
|
1677
|
-
function
|
|
1678
|
-
const
|
|
1679
|
-
|
|
1680
|
-
// Handle multiple values for the same key
|
|
1681
|
-
if (obj[key]) {
|
|
1682
|
-
if (!Array.isArray(obj[key])) {
|
|
1683
|
-
obj[key] = [obj[key]];
|
|
1684
|
-
}
|
|
1685
|
-
obj[key].push(value);
|
|
1686
|
-
}
|
|
1687
|
-
else {
|
|
1688
|
-
obj[key] = value;
|
|
1689
|
-
}
|
|
1690
|
-
});
|
|
1691
|
-
return obj;
|
|
1366
|
+
function generateRandomString(length) {
|
|
1367
|
+
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
1368
|
+
return Array.from({ length }, () => characters.charAt(Math.floor(Math.random() * characters.length))).join('');
|
|
1692
1369
|
}
|
|
1370
|
+
// Example usage:
|
|
1371
|
+
// console.log(generateRandomString(10)); // 'A1b2C3d4E5'
|
|
1372
|
+
|
|
1693
1373
|
/**
|
|
1694
|
-
*
|
|
1695
|
-
* @param {
|
|
1696
|
-
* @returns {
|
|
1374
|
+
* Validates if the key pressed is a valid letter or special character.
|
|
1375
|
+
* @param {KeyboardEvent} e The keyboard event.
|
|
1376
|
+
* @returns {boolean} True if the key is valid, otherwise false.
|
|
1697
1377
|
*/
|
|
1698
|
-
function
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1378
|
+
function validateLetters(e) {
|
|
1379
|
+
const key = e.keyCode;
|
|
1380
|
+
const validKeys = [
|
|
1381
|
+
...Array.from({ length: 26 }, (_, i) => i + 65), // Letters A-Z
|
|
1382
|
+
...Array.from({ length: 26 }, (_, i) => i + 97), // Letters a-z
|
|
1383
|
+
45, // Hyphen
|
|
1384
|
+
32, // Space
|
|
1385
|
+
241, // ñ
|
|
1386
|
+
209, // Ñ
|
|
1387
|
+
225, // á
|
|
1388
|
+
233, // é
|
|
1389
|
+
237, // í
|
|
1390
|
+
243, // ó
|
|
1391
|
+
250, // ú
|
|
1392
|
+
193, // Á
|
|
1393
|
+
201, // É
|
|
1394
|
+
205, // Í
|
|
1395
|
+
211, // Ó
|
|
1396
|
+
218 // Ú
|
|
1397
|
+
];
|
|
1398
|
+
if (validKeys.includes(key)) {
|
|
1399
|
+
return true;
|
|
1400
|
+
}
|
|
1401
|
+
e.preventDefault();
|
|
1402
|
+
return false;
|
|
1705
1403
|
}
|
|
1404
|
+
// Example usage:
|
|
1405
|
+
// document.addEventListener('keydown', validateLetters);
|
|
1706
1406
|
/**
|
|
1707
|
-
*
|
|
1708
|
-
* @param {
|
|
1709
|
-
* @returns {
|
|
1407
|
+
* Validates if the key pressed is a valid alphanumeric character.
|
|
1408
|
+
* @param {KeyboardEvent} e The keyboard event.
|
|
1409
|
+
* @returns {boolean} True if the key is valid, otherwise false.
|
|
1710
1410
|
*/
|
|
1711
|
-
function
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1411
|
+
function validateAlphanumeric(e) {
|
|
1412
|
+
const key = e.keyCode;
|
|
1413
|
+
const validKeys = [
|
|
1414
|
+
...Array.from({ length: 10 }, (_, i) => i + 48), // Numbers 0-9
|
|
1415
|
+
...Array.from({ length: 26 }, (_, i) => i + 65), // Letters A-Z
|
|
1416
|
+
...Array.from({ length: 26 }, (_, i) => i + 97), // Letters a-z
|
|
1417
|
+
45, // Hyphen
|
|
1418
|
+
95, // Underscore
|
|
1419
|
+
32 // Space
|
|
1420
|
+
];
|
|
1421
|
+
if (validKeys.includes(key)) {
|
|
1422
|
+
return true;
|
|
1423
|
+
}
|
|
1424
|
+
e.preventDefault();
|
|
1425
|
+
return false;
|
|
1718
1426
|
}
|
|
1427
|
+
// Example usage:
|
|
1428
|
+
// document.addEventListener('keydown', validateAlphanumeric);
|
|
1719
1429
|
/**
|
|
1720
|
-
*
|
|
1721
|
-
* @param {
|
|
1722
|
-
* @
|
|
1723
|
-
* @returns {Blob} The Blob object.
|
|
1430
|
+
* Validates if the key pressed is a number.
|
|
1431
|
+
* @param {KeyboardEvent} e The keyboard event.
|
|
1432
|
+
* @returns {boolean} True if the key is a number, otherwise false.
|
|
1724
1433
|
*/
|
|
1725
|
-
function
|
|
1726
|
-
|
|
1434
|
+
function validateNumbers(e) {
|
|
1435
|
+
const key = e.keyCode;
|
|
1436
|
+
if ((key >= 48 && key <= 57) || key === 46 || key === 8 || key === 37 || key === 39) {
|
|
1437
|
+
return true;
|
|
1438
|
+
}
|
|
1439
|
+
e.preventDefault();
|
|
1440
|
+
return false;
|
|
1727
1441
|
}
|
|
1442
|
+
// Example usage:
|
|
1443
|
+
// document.addEventListener('keydown', validateNumbers);
|
|
1728
1444
|
/**
|
|
1729
|
-
*
|
|
1730
|
-
* @param {
|
|
1731
|
-
* @
|
|
1732
|
-
* @returns {Blob} The Blob object.
|
|
1445
|
+
* Validates if a phone number is valid.
|
|
1446
|
+
* @param {string} phoneNumber The phone number to validate.
|
|
1447
|
+
* @returns {boolean} True if the phone number is valid, otherwise false.
|
|
1733
1448
|
*/
|
|
1734
|
-
function
|
|
1735
|
-
|
|
1449
|
+
function isValidPhoneNumber(phoneNumber) {
|
|
1450
|
+
const phonePattern = /^[0-9]{10}$/; // Example pattern for 10-digit phone numbers
|
|
1451
|
+
return phonePattern.test(phoneNumber);
|
|
1736
1452
|
}
|
|
1453
|
+
// Example usage:
|
|
1454
|
+
// console.log(isValidPhoneNumber('1234567890')); // true
|
|
1455
|
+
// console.log(isValidPhoneNumber('123-456-7890')); // false
|
|
1737
1456
|
/**
|
|
1738
|
-
*
|
|
1739
|
-
* @param {
|
|
1740
|
-
* @
|
|
1457
|
+
* Validates if a string is a valid email address.
|
|
1458
|
+
* @param {string} email The email address to validate.
|
|
1459
|
+
* @returns {boolean} True if the email address is valid, otherwise false.
|
|
1741
1460
|
*/
|
|
1742
|
-
function
|
|
1743
|
-
const
|
|
1744
|
-
|
|
1745
|
-
link.setAttribute('href', url);
|
|
1746
|
-
link.setAttribute('download', fileName);
|
|
1747
|
-
// Append link to the body and trigger a click
|
|
1748
|
-
document.body.appendChild(link);
|
|
1749
|
-
link.click();
|
|
1750
|
-
// Clean up
|
|
1751
|
-
document.body.removeChild(link);
|
|
1752
|
-
URL.revokeObjectURL(url);
|
|
1461
|
+
function isValidEmail(email) {
|
|
1462
|
+
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
1463
|
+
return emailPattern.test(email);
|
|
1753
1464
|
}
|
|
1465
|
+
// Example usage:
|
|
1466
|
+
// console.log(isValidEmail('example@domain.com')); // true
|
|
1467
|
+
// console.log(isValidEmail('invalid-email')); // false
|
|
1754
1468
|
/**
|
|
1755
|
-
*
|
|
1756
|
-
* @param {
|
|
1757
|
-
* @
|
|
1758
|
-
* @param {string} [fileName='file'] The file name for the Blob.
|
|
1759
|
-
* @returns {FormData} The FormData object.
|
|
1469
|
+
* Validates if a string is a valid URL.
|
|
1470
|
+
* @param {string} url The URL to validate.
|
|
1471
|
+
* @returns {boolean} True if the URL is valid, otherwise false.
|
|
1760
1472
|
*/
|
|
1761
|
-
function
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1473
|
+
function isValidURL(url) {
|
|
1474
|
+
try {
|
|
1475
|
+
new URL(url);
|
|
1476
|
+
return true;
|
|
1477
|
+
}
|
|
1478
|
+
catch {
|
|
1479
|
+
return false;
|
|
1480
|
+
}
|
|
1765
1481
|
}
|
|
1766
|
-
|
|
1482
|
+
// Example usage:
|
|
1483
|
+
// console.log(isValidURL('https://www.example.com')); // true
|
|
1484
|
+
// console.log(isValidURL('invalid-url')); // false
|
|
1767
1485
|
/**
|
|
1768
|
-
*
|
|
1769
|
-
* @param {
|
|
1770
|
-
* @returns {
|
|
1486
|
+
* Validates if a string is a valid date in YYYY-MM-DD format.
|
|
1487
|
+
* @param {string} date The date string to validate.
|
|
1488
|
+
* @returns {boolean} True if the date is valid, otherwise false.
|
|
1771
1489
|
*/
|
|
1772
|
-
function
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
return plainObject;
|
|
1490
|
+
function isValidDate(date) {
|
|
1491
|
+
const datePattern = /^\d{4}-\d{2}-\d{2}$/;
|
|
1492
|
+
if (!datePattern.test(date))
|
|
1493
|
+
return false;
|
|
1494
|
+
const [year, month, day] = date.split('-').map(Number);
|
|
1495
|
+
const dateObj = new Date(year, month - 1, day);
|
|
1496
|
+
return dateObj.getFullYear() === year && dateObj.getMonth() === month - 1 && dateObj.getDate() === day;
|
|
1780
1497
|
}
|
|
1498
|
+
// Example usage:
|
|
1499
|
+
// console.log(isValidDate('2024-08-31')); // true
|
|
1500
|
+
// console.log(isValidDate('2024-02-30')); // false
|
|
1781
1501
|
/**
|
|
1782
|
-
*
|
|
1783
|
-
* @param {
|
|
1784
|
-
* @
|
|
1785
|
-
* @returns {boolean} True if the objects have the same keys, otherwise false.
|
|
1502
|
+
* Validates if a password meets certain strength criteria.
|
|
1503
|
+
* @param {string} password The password to validate.
|
|
1504
|
+
* @returns {boolean} True if the password is strong, otherwise false.
|
|
1786
1505
|
*/
|
|
1787
|
-
function
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1506
|
+
function isStrongPassword(password) {
|
|
1507
|
+
const minLength = 8;
|
|
1508
|
+
const hasUpperCase = /[A-Z]/.test(password);
|
|
1509
|
+
const hasLowerCase = /[a-z]/.test(password);
|
|
1510
|
+
const hasNumbers = /[0-9]/.test(password);
|
|
1511
|
+
const hasSpecialChars = /[!@#$%^&*(),.?":{}|<>]/.test(password);
|
|
1512
|
+
return password.length >= minLength && hasUpperCase && hasLowerCase && hasNumbers && hasSpecialChars;
|
|
1791
1513
|
}
|
|
1514
|
+
// Example usage:
|
|
1515
|
+
// console.log(isStrongPassword('Strong1@password')); // true
|
|
1516
|
+
// console.log(isStrongPassword('weakpass')); // false
|
|
1792
1517
|
/**
|
|
1793
|
-
*
|
|
1794
|
-
* @param {
|
|
1795
|
-
* @
|
|
1796
|
-
* @returns {boolean} True if the objects are deeply equal, otherwise false.
|
|
1518
|
+
* Validates a credit card number using the Luhn algorithm.
|
|
1519
|
+
* @param {string} cardNumber The credit card number to validate.
|
|
1520
|
+
* @returns {boolean} True if the credit card number is valid, otherwise false.
|
|
1797
1521
|
*/
|
|
1798
|
-
function
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
}
|
|
1809
|
-
for (const key of keys1) {
|
|
1810
|
-
if (!keys2.includes(key) || !deepEqual(object1[key], object2[key])) {
|
|
1811
|
-
return false;
|
|
1522
|
+
function isValidCreditCard(cardNumber) {
|
|
1523
|
+
const sanitized = cardNumber.replace(/\D/g, '');
|
|
1524
|
+
let sum = 0;
|
|
1525
|
+
let shouldDouble = false;
|
|
1526
|
+
for (let i = sanitized.length - 1; i >= 0; i--) {
|
|
1527
|
+
let digit = parseInt(sanitized.charAt(i), 10);
|
|
1528
|
+
if (shouldDouble) {
|
|
1529
|
+
digit *= 2;
|
|
1530
|
+
if (digit > 9)
|
|
1531
|
+
digit -= 9;
|
|
1812
1532
|
}
|
|
1533
|
+
sum += digit;
|
|
1534
|
+
shouldDouble = !shouldDouble;
|
|
1813
1535
|
}
|
|
1814
|
-
return
|
|
1536
|
+
return sum % 10 === 0;
|
|
1815
1537
|
}
|
|
1538
|
+
// Example usage:
|
|
1539
|
+
// console.log(isValidCreditCard('4111111111111111')); // true
|
|
1540
|
+
// console.log(isValidCreditCard('1234567812345670')); // false
|
|
1816
1541
|
/**
|
|
1817
|
-
*
|
|
1818
|
-
* @param {
|
|
1819
|
-
* @returns {
|
|
1542
|
+
* Validates if a string is a valid hex color code.
|
|
1543
|
+
* @param {string} color The color code to validate.
|
|
1544
|
+
* @returns {boolean} True if the color code is valid, otherwise false.
|
|
1820
1545
|
*/
|
|
1821
|
-
function
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
}
|
|
1825
|
-
if (obj instanceof Date) {
|
|
1826
|
-
return new Date(obj.getTime());
|
|
1827
|
-
}
|
|
1828
|
-
if (obj instanceof Array) {
|
|
1829
|
-
return obj.map(item => deepClone(item));
|
|
1830
|
-
}
|
|
1831
|
-
if (obj instanceof Object) {
|
|
1832
|
-
const copy = {};
|
|
1833
|
-
for (const key in obj) {
|
|
1834
|
-
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
1835
|
-
copy[key] = deepClone(obj[key]);
|
|
1836
|
-
}
|
|
1837
|
-
}
|
|
1838
|
-
return copy;
|
|
1839
|
-
}
|
|
1840
|
-
throw new Error('Unable to clone object! Its type is not supported.');
|
|
1546
|
+
function isValidHexColor(color) {
|
|
1547
|
+
const hexPattern = /^#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})$/;
|
|
1548
|
+
return hexPattern.test(color);
|
|
1841
1549
|
}
|
|
1550
|
+
// Example usage:
|
|
1551
|
+
// console.log(isValidHexColor('#FFFFFF')); // true
|
|
1552
|
+
// console.log(isValidHexColor('#FFF')); // true
|
|
1553
|
+
// console.log(isValidHexColor('#12345G')); // false
|
|
1842
1554
|
/**
|
|
1843
|
-
*
|
|
1844
|
-
* @param {
|
|
1845
|
-
* @returns {
|
|
1555
|
+
* Validates if a string is a valid time in HH:MM format.
|
|
1556
|
+
* @param {string} time The time string to validate.
|
|
1557
|
+
* @returns {boolean} True if the time is valid, otherwise false.
|
|
1846
1558
|
*/
|
|
1847
|
-
function
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
.join('&');
|
|
1559
|
+
function isValidTime(time) {
|
|
1560
|
+
const timePattern = /^([01]\d|2[0-3]):([0-5]\d)$/;
|
|
1561
|
+
return timePattern.test(time);
|
|
1851
1562
|
}
|
|
1852
1563
|
// Example usage:
|
|
1853
|
-
//
|
|
1564
|
+
// console.log(isValidTime('14:30')); // true
|
|
1565
|
+
// console.log(isValidTime('25:00')); // false
|
|
1854
1566
|
/**
|
|
1855
|
-
*
|
|
1856
|
-
* @param {
|
|
1857
|
-
* @
|
|
1858
|
-
* @returns {Object} An object containing the differences.
|
|
1567
|
+
* Validates if a string is a valid IPv4 address.
|
|
1568
|
+
* @param {string} ip The IP address to validate.
|
|
1569
|
+
* @returns {boolean} True if the IP address is valid, otherwise false.
|
|
1859
1570
|
*/
|
|
1860
|
-
function
|
|
1861
|
-
const
|
|
1862
|
-
|
|
1863
|
-
for (const key of keys) {
|
|
1864
|
-
if (object1[key] !== object2[key]) {
|
|
1865
|
-
differences[key] = { object1: object1[key], object2: object2[key] };
|
|
1866
|
-
}
|
|
1867
|
-
}
|
|
1868
|
-
return differences;
|
|
1571
|
+
function isValidIP(ip) {
|
|
1572
|
+
const ipPattern = /^(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[0-1]?[0-9][0-9]?)$/;
|
|
1573
|
+
return ipPattern.test(ip);
|
|
1869
1574
|
}
|
|
1575
|
+
// Example usage:
|
|
1576
|
+
// console.log(isValidIP('192.168.1.1')); // true
|
|
1577
|
+
// console.log(isValidIP('999.999.999.999')); // false
|
|
1870
1578
|
/**
|
|
1871
|
-
*
|
|
1872
|
-
* @param {
|
|
1873
|
-
* @
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
for (const key of keys) {
|
|
1879
|
-
if (key in obj) {
|
|
1880
|
-
filteredObject[key] = obj[key];
|
|
1881
|
-
}
|
|
1882
|
-
}
|
|
1883
|
-
return filteredObject;
|
|
1579
|
+
* Validates if a string is a valid U.S. Social Security Number (SSN).
|
|
1580
|
+
* @param {string} ssn The SSN to validate.
|
|
1581
|
+
* @returns {boolean} True if the SSN is valid, otherwise false.
|
|
1582
|
+
*/
|
|
1583
|
+
function isValidSSN(ssn) {
|
|
1584
|
+
const ssnPattern = /^\d{3}-\d{2}-\d{4}$/;
|
|
1585
|
+
return ssnPattern.test(ssn);
|
|
1884
1586
|
}
|
|
1885
1587
|
// Example usage:
|
|
1886
|
-
//
|
|
1588
|
+
// console.log(isValidSSN('123-45-6789')); // true
|
|
1589
|
+
// console.log(isValidSSN('123-45-678')); // false
|
|
1887
1590
|
/**
|
|
1888
|
-
*
|
|
1889
|
-
* @param {
|
|
1890
|
-
* @
|
|
1891
|
-
* @returns {Object} The merged object.
|
|
1591
|
+
* Validates if a string is a valid U.S. ZIP code.
|
|
1592
|
+
* @param {string} zip The ZIP code to validate.
|
|
1593
|
+
* @returns {boolean} True if the ZIP code is valid, otherwise false.
|
|
1892
1594
|
*/
|
|
1893
|
-
function
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
}
|
|
1897
|
-
for (const key in source) {
|
|
1898
|
-
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
1899
|
-
if (source[key] && typeof source[key] === 'object') {
|
|
1900
|
-
if (target && !target[key]) {
|
|
1901
|
-
Object.assign(target, { [key]: {} });
|
|
1902
|
-
}
|
|
1903
|
-
deepMerge(target[key], source[key]);
|
|
1904
|
-
}
|
|
1905
|
-
else {
|
|
1906
|
-
Object.assign(target, { [key]: source[key] });
|
|
1907
|
-
}
|
|
1908
|
-
}
|
|
1909
|
-
}
|
|
1910
|
-
return target;
|
|
1595
|
+
function isValidZIP(zip) {
|
|
1596
|
+
const zipPattern = /^\d{5}(-\d{4})?$/;
|
|
1597
|
+
return zipPattern.test(zip);
|
|
1911
1598
|
}
|
|
1599
|
+
// Example usage:
|
|
1600
|
+
// console.log(isValidZIP('12345')); // true
|
|
1601
|
+
// console.log(isValidZIP('12345-6789')); // true
|
|
1602
|
+
// console.log(isValidZIP('1234')); // false
|
|
1912
1603
|
/**
|
|
1913
|
-
*
|
|
1914
|
-
* @param {
|
|
1915
|
-
* @returns {boolean} True if the
|
|
1604
|
+
* Validates if a string is a valid credit card expiry date in MM/YY format.
|
|
1605
|
+
* @param {string} expiryDate The expiry date to validate.
|
|
1606
|
+
* @returns {boolean} True if the expiry date is valid, otherwise false.
|
|
1916
1607
|
*/
|
|
1917
|
-
function
|
|
1918
|
-
|
|
1608
|
+
function isValidExpiryDate(expiryDate) {
|
|
1609
|
+
const expiryPattern = /^(0[1-9]|1[0-2])\/\d{2}$/;
|
|
1610
|
+
if (!expiryPattern.test(expiryDate))
|
|
1611
|
+
return false;
|
|
1612
|
+
const [month, year] = expiryDate.split('/').map(Number);
|
|
1613
|
+
const currentYear = new Date().getFullYear() % 100;
|
|
1614
|
+
const currentMonth = new Date().getMonth() + 1;
|
|
1615
|
+
return (year > currentYear) || (year === currentYear && month >= currentMonth);
|
|
1919
1616
|
}
|
|
1617
|
+
// Example usage:
|
|
1618
|
+
// console.log(isValidExpiryDate('08/24')); // true
|
|
1619
|
+
// console.log(isValidExpiryDate('12/22')); // false
|
|
1920
1620
|
/**
|
|
1921
|
-
*
|
|
1922
|
-
* @param {
|
|
1923
|
-
* @
|
|
1924
|
-
* @returns {any} The value at the nested path, or undefined if not found.
|
|
1621
|
+
* Validates if a string is a valid 8-character hexadecimal color code (including alpha).
|
|
1622
|
+
* @param {string} color The color code to validate.
|
|
1623
|
+
* @returns {boolean} True if the color code is valid, otherwise false.
|
|
1925
1624
|
*/
|
|
1926
|
-
function
|
|
1927
|
-
|
|
1625
|
+
function isValidHexColorAlpha(color) {
|
|
1626
|
+
const hexPattern = /^#([0-9A-Fa-f]{8})$/;
|
|
1627
|
+
return hexPattern.test(color);
|
|
1928
1628
|
}
|
|
1929
1629
|
// Example usage:
|
|
1930
|
-
//
|
|
1931
|
-
//
|
|
1630
|
+
// console.log(isValidHexColorAlpha('#RRGGBBAA')); // true
|
|
1631
|
+
// console.log(isValidHexColorAlpha('#FFFFFF')); // false
|
|
1932
1632
|
/**
|
|
1933
|
-
*
|
|
1934
|
-
* @param {
|
|
1935
|
-
* @returns {
|
|
1633
|
+
* Validates if a username meets specific criteria.
|
|
1634
|
+
* @param {string} username The username to validate.
|
|
1635
|
+
* @returns {boolean} True if the username is valid, otherwise false.
|
|
1936
1636
|
*/
|
|
1937
|
-
function
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
.reduce((acc, key) => {
|
|
1941
|
-
acc[key] = obj[key];
|
|
1942
|
-
return acc;
|
|
1943
|
-
}, {});
|
|
1637
|
+
function isValidUsername(username) {
|
|
1638
|
+
const usernamePattern = /^[a-zA-Z0-9_]{3,16}$/; // 3 to 16 characters, letters, numbers, and underscores only
|
|
1639
|
+
return usernamePattern.test(username);
|
|
1944
1640
|
}
|
|
1945
1641
|
// Example usage:
|
|
1946
|
-
//
|
|
1642
|
+
// console.log(isValidUsername('user_name123')); // true
|
|
1643
|
+
// console.log(isValidUsername('us')); // false
|
|
1947
1644
|
/**
|
|
1948
|
-
*
|
|
1949
|
-
* @param {
|
|
1950
|
-
* @returns {
|
|
1645
|
+
* Validates if a string represents a valid age between 0 and 120.
|
|
1646
|
+
* @param {string} age The age to validate.
|
|
1647
|
+
* @returns {boolean} True if the age is valid, otherwise false.
|
|
1951
1648
|
*/
|
|
1952
|
-
function
|
|
1953
|
-
|
|
1649
|
+
function isValidAge(age) {
|
|
1650
|
+
const ageNumber = parseInt(age, 10);
|
|
1651
|
+
return !isNaN(ageNumber) && ageNumber >= 0 && ageNumber <= 120;
|
|
1954
1652
|
}
|
|
1955
1653
|
// Example usage:
|
|
1956
|
-
//
|
|
1654
|
+
// console.log(isValidAge('25')); // true
|
|
1655
|
+
// console.log(isValidAge('121')); // false
|
|
1957
1656
|
/**
|
|
1958
|
-
*
|
|
1959
|
-
* @param {
|
|
1960
|
-
* @returns {boolean} True if
|
|
1657
|
+
* Validates if a string is a valid hexadecimal number.
|
|
1658
|
+
* @param {string} hex The hexadecimal number to validate.
|
|
1659
|
+
* @returns {boolean} True if the number is valid, otherwise false.
|
|
1961
1660
|
*/
|
|
1962
|
-
function
|
|
1963
|
-
|
|
1661
|
+
function isValidHexNumber(hex) {
|
|
1662
|
+
const hexPattern = /^[0-9A-Fa-f]+$/;
|
|
1663
|
+
return hexPattern.test(hex);
|
|
1964
1664
|
}
|
|
1965
1665
|
// Example usage:
|
|
1966
|
-
//
|
|
1967
|
-
//
|
|
1666
|
+
// console.log(isValidHexNumber('1A3F')); // true
|
|
1667
|
+
// console.log(isValidHexNumber('GHIJ')); // false
|
|
1668
|
+
|
|
1968
1669
|
/**
|
|
1969
|
-
*
|
|
1970
|
-
* @
|
|
1971
|
-
* @param {FormData} [formData] The FormData object to append to.
|
|
1972
|
-
* @param {string} [parentKey] The parent key for nested objects.
|
|
1973
|
-
* @returns {FormData} The FormData object.
|
|
1670
|
+
* Object defining available screen sizes as constants.
|
|
1671
|
+
* @readonly
|
|
1974
1672
|
*/
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
}
|
|
1984
|
-
});
|
|
1985
|
-
return formData;
|
|
1986
|
-
}
|
|
1987
|
-
// Example usage:
|
|
1988
|
-
// objectToFormDataEnhanced({ user: { name: 'John', age: 30 } });
|
|
1673
|
+
const sizeEnum = {
|
|
1674
|
+
XS: 'XS',
|
|
1675
|
+
SM: 'SM',
|
|
1676
|
+
MD: 'MD',
|
|
1677
|
+
LG: 'LG',
|
|
1678
|
+
XL: 'XL',
|
|
1679
|
+
XXL: 'XXL',
|
|
1680
|
+
};
|
|
1989
1681
|
/**
|
|
1990
|
-
*
|
|
1991
|
-
*
|
|
1992
|
-
* @
|
|
1993
|
-
* @param form - An optional FormData instance to use.
|
|
1994
|
-
* @param namespace - An optional namespace to use for nested objects.
|
|
1995
|
-
* @returns The FormData instance with the object's key-value pairs.
|
|
1682
|
+
* Object defining breakpoints for design based on screen width.
|
|
1683
|
+
* Values are in pixels.
|
|
1684
|
+
* @readonly
|
|
1996
1685
|
*/
|
|
1997
|
-
const
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
1686
|
+
const screenEnum = {
|
|
1687
|
+
XS: 480,
|
|
1688
|
+
SM: 576,
|
|
1689
|
+
MD: 768,
|
|
1690
|
+
LG: 992,
|
|
1691
|
+
XL: 1200,
|
|
1692
|
+
XXL: 1600,
|
|
1693
|
+
};
|
|
1694
|
+
/**
|
|
1695
|
+
* Map that associates each screen size defined in `sizeEnum` with its corresponding pixel value from `screenEnum`.
|
|
1696
|
+
* @type {Map<typeof sizeEnum[keyof typeof sizeEnum], number>}
|
|
1697
|
+
*/
|
|
1698
|
+
const screenMap = new Map();
|
|
1699
|
+
screenMap.set(sizeEnum.XS, screenEnum.XS);
|
|
1700
|
+
screenMap.set(sizeEnum.SM, screenEnum.SM);
|
|
1701
|
+
screenMap.set(sizeEnum.MD, screenEnum.MD);
|
|
1702
|
+
screenMap.set(sizeEnum.LG, screenEnum.LG);
|
|
1703
|
+
screenMap.set(sizeEnum.XL, screenEnum.XL);
|
|
1704
|
+
screenMap.set(sizeEnum.XXL, screenEnum.XXL);
|
|
1705
|
+
|
|
1706
|
+
/**
|
|
1707
|
+
* Object defining various exception codes.
|
|
1708
|
+
* @readonly
|
|
1709
|
+
*/
|
|
1710
|
+
const ExceptionEnum = {
|
|
1711
|
+
/**
|
|
1712
|
+
* HTTP status code for page access forbidden.
|
|
1713
|
+
*/
|
|
1714
|
+
PAGE_NOT_ACCESS: 403,
|
|
1715
|
+
/**
|
|
1716
|
+
* HTTP status code for page not found.
|
|
1717
|
+
*/
|
|
1718
|
+
PAGE_NOT_FOUND: 404,
|
|
1719
|
+
/**
|
|
1720
|
+
* HTTP status code for general server error.
|
|
1721
|
+
*/
|
|
1722
|
+
ERROR: 500,
|
|
1723
|
+
/**
|
|
1724
|
+
* HTTP status code for bad request.
|
|
1725
|
+
*/
|
|
1726
|
+
BAD_REQUEST: 406,
|
|
1727
|
+
/**
|
|
1728
|
+
* Custom error code for network errors.
|
|
1729
|
+
*/
|
|
1730
|
+
NET_WORK_ERROR: 10000,
|
|
1731
|
+
/**
|
|
1732
|
+
* Custom code indicating no data on the page, not actually an exception page.
|
|
1733
|
+
*/
|
|
1734
|
+
PAGE_NOT_DATA: 10100,
|
|
1735
|
+
};
|
|
1736
|
+
|
|
1737
|
+
/**
|
|
1738
|
+
* Object defining various image MIME types.
|
|
1739
|
+
* @readonly
|
|
1740
|
+
*/
|
|
1741
|
+
const ImageTypes = {
|
|
1742
|
+
apng: 'image/apng',
|
|
1743
|
+
bmp: 'image/bmp',
|
|
1744
|
+
gif: 'image/gif',
|
|
1745
|
+
jpeg: 'image/jpeg',
|
|
1746
|
+
pjpeg: 'image/pjpeg',
|
|
1747
|
+
png: 'image/png',
|
|
1748
|
+
svg: 'image/svg+xml',
|
|
1749
|
+
tiff: 'image/tiff',
|
|
1750
|
+
webp: 'image/webp',
|
|
1751
|
+
xicon: 'image/x-icon',
|
|
1752
|
+
};
|
|
1753
|
+
/**
|
|
1754
|
+
* Object defining various file meta types.
|
|
1755
|
+
* @readonly
|
|
1756
|
+
*/
|
|
1757
|
+
const FileMetaTypes = {
|
|
1758
|
+
images: 'image/*',
|
|
1759
|
+
audios: 'audio/*',
|
|
1760
|
+
videos: 'video/*',
|
|
2023
1761
|
};
|
|
1762
|
+
|
|
2024
1763
|
/**
|
|
2025
|
-
*
|
|
2026
|
-
* @
|
|
2027
|
-
* @param {string} [parentKey] The parent key for nested properties.
|
|
2028
|
-
* @param {Object} [result] The resulting flattened object.
|
|
2029
|
-
* @returns {Object} The flattened object.
|
|
1764
|
+
* Object representing different content types for HTTP headers.
|
|
1765
|
+
* @readonly
|
|
2030
1766
|
*/
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
1767
|
+
const ContentTypeEnum = {
|
|
1768
|
+
/**
|
|
1769
|
+
* Content type for JSON.
|
|
1770
|
+
* @constant
|
|
1771
|
+
*/
|
|
1772
|
+
JSON: 'application/json;charset=UTF-8',
|
|
1773
|
+
/**
|
|
1774
|
+
* Content type for URL-encoded form data.
|
|
1775
|
+
* @constant
|
|
1776
|
+
*/
|
|
1777
|
+
FORM_URLENCODED: 'application/x-www-form-urlencoded;charset=UTF-8',
|
|
1778
|
+
/**
|
|
1779
|
+
* Content type for multipart form data with file uploads.
|
|
1780
|
+
* @constant
|
|
1781
|
+
*/
|
|
1782
|
+
FORM_DATA: 'multipart/form-data;charset=UTF-8',
|
|
1783
|
+
};
|
|
2047
1784
|
|
|
2048
1785
|
/**
|
|
2049
|
-
*
|
|
2050
|
-
* @
|
|
2051
|
-
* @returns {string} The capitalized string.
|
|
1786
|
+
* Object defining various key codes for keyboard events.
|
|
1787
|
+
* @readonly
|
|
2052
1788
|
*/
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
1789
|
+
const KeyCodeEnum = {
|
|
1790
|
+
UP: 38,
|
|
1791
|
+
DOWN: 40,
|
|
1792
|
+
ENTER: 13,
|
|
1793
|
+
ESC: 27,
|
|
1794
|
+
};
|
|
1795
|
+
|
|
2058
1796
|
/**
|
|
2059
|
-
*
|
|
2060
|
-
* @
|
|
2061
|
-
* @returns {string} The lowercased string.
|
|
1797
|
+
* Key used for storing the authentication token.
|
|
1798
|
+
* @constant {string}
|
|
2062
1799
|
*/
|
|
2063
|
-
|
|
2064
|
-
return s.charAt(0).toLowerCase() + s.slice(1);
|
|
2065
|
-
}
|
|
2066
|
-
// Example usage:
|
|
2067
|
-
// console.log(lowerFirst('Hello')); // 'hello'
|
|
1800
|
+
const TOKEN_KEY = 'TOKEN__';
|
|
2068
1801
|
/**
|
|
2069
|
-
*
|
|
2070
|
-
* @
|
|
2071
|
-
* @returns {string} The processed string.
|
|
1802
|
+
* Key used for storing the locale information.
|
|
1803
|
+
* @constant {string}
|
|
2072
1804
|
*/
|
|
2073
|
-
|
|
2074
|
-
return input
|
|
2075
|
-
.toLowerCase()
|
|
2076
|
-
.trim()
|
|
2077
|
-
.replace(/[\s_-]+/g, "-")
|
|
2078
|
-
.replace(/^-+|-+$/g, "")
|
|
2079
|
-
.normalize("NFD")
|
|
2080
|
-
.replace(/[\u0300-\u036f]/g, "");
|
|
2081
|
-
}
|
|
2082
|
-
// Example usage:
|
|
2083
|
-
// console.log(removeAccent('Café du Nord')); // 'cafe-du-nord'
|
|
1805
|
+
const LOCALE_KEY = 'LOCALE__';
|
|
2084
1806
|
/**
|
|
2085
|
-
*
|
|
2086
|
-
* @
|
|
2087
|
-
* @returns {string} The reversed string.
|
|
1807
|
+
* Key used for storing user information.
|
|
1808
|
+
* @constant {string}
|
|
2088
1809
|
*/
|
|
2089
|
-
|
|
2090
|
-
return str.split('').reverse().join('');
|
|
2091
|
-
}
|
|
2092
|
-
// Example usage:
|
|
2093
|
-
// console.log(reverseString('hello')); // 'olleh'
|
|
1810
|
+
const USER_INFO_KEY = 'USER__INFO__';
|
|
2094
1811
|
/**
|
|
2095
|
-
*
|
|
2096
|
-
* @
|
|
2097
|
-
* @returns {number} The word count.
|
|
1812
|
+
* Key used for storing role information.
|
|
1813
|
+
* @constant {string}
|
|
2098
1814
|
*/
|
|
2099
|
-
|
|
2100
|
-
return str.trim().split(/\s+/).length;
|
|
2101
|
-
}
|
|
2102
|
-
// Example usage:
|
|
2103
|
-
// console.log(countWords('Hello world!')); // 2
|
|
1815
|
+
const ROLES_KEY = 'ROLES__KEY__';
|
|
2104
1816
|
/**
|
|
2105
|
-
*
|
|
2106
|
-
* @
|
|
2107
|
-
* @param {number} maxLength The maximum length of the string.
|
|
2108
|
-
* @returns {string} The truncated string.
|
|
1817
|
+
* Key used for storing project configuration.
|
|
1818
|
+
* @constant {string}
|
|
2109
1819
|
*/
|
|
2110
|
-
|
|
2111
|
-
return str.length > maxLength ? str.slice(0, maxLength) + '...' : str;
|
|
2112
|
-
}
|
|
2113
|
-
// Example usage:
|
|
2114
|
-
// console.log(truncateString('This is a long string', 10)); // 'This is a...'
|
|
1820
|
+
const PROJ_CFG_KEY = 'PROJ__CFG__KEY__';
|
|
2115
1821
|
/**
|
|
2116
|
-
*
|
|
2117
|
-
* @
|
|
2118
|
-
* @returns {string} The camel cased string.
|
|
1822
|
+
* Key used for storing lock information.
|
|
1823
|
+
* @constant {string}
|
|
2119
1824
|
*/
|
|
2120
|
-
|
|
2121
|
-
return str
|
|
2122
|
-
.toLowerCase()
|
|
2123
|
-
.replace(/[^a-zA-Z0-9]+(.)/g, (match, chr) => chr.toUpperCase());
|
|
2124
|
-
}
|
|
2125
|
-
// Example usage:
|
|
2126
|
-
// console.log(toCamelCase('hello world example')); // 'helloWorldExample'
|
|
1825
|
+
const LOCK_INFO_KEY = 'LOCK__INFO__KEY__';
|
|
2127
1826
|
/**
|
|
2128
|
-
*
|
|
2129
|
-
* @
|
|
2130
|
-
* @returns {string} The kebab cased string.
|
|
1827
|
+
* Key used for base global local cache.
|
|
1828
|
+
* @constant {string}
|
|
2131
1829
|
*/
|
|
2132
|
-
|
|
2133
|
-
return str
|
|
2134
|
-
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
2135
|
-
.replace(/[\s_]+/g, '-')
|
|
2136
|
-
.toLowerCase();
|
|
2137
|
-
}
|
|
2138
|
-
// Example usage:
|
|
2139
|
-
// console.log(toKebabCase('Hello World Example')); // 'hello-world-example'
|
|
1830
|
+
const APP_LOCAL_CACHE_KEY = 'COMMON__LOCAL__KEY';
|
|
2140
1831
|
/**
|
|
2141
|
-
*
|
|
2142
|
-
* @
|
|
2143
|
-
* @param {string} find The substring to find.
|
|
2144
|
-
* @param {string} replace The substring to replace with.
|
|
2145
|
-
* @returns {string} The modified string.
|
|
1832
|
+
* Key used for base global session cache.
|
|
1833
|
+
* @constant {string}
|
|
2146
1834
|
*/
|
|
2147
|
-
|
|
2148
|
-
return str.split(find).join(replace);
|
|
2149
|
-
}
|
|
2150
|
-
// Example usage:
|
|
2151
|
-
// console.log(replaceAll('hello world', 'o', 'a')); // 'hella warld'
|
|
1835
|
+
const APP_SESSION_CACHE_KEY = 'COMMON__SESSION__KEY';
|
|
2152
1836
|
/**
|
|
2153
|
-
*
|
|
2154
|
-
* @
|
|
2155
|
-
* @returns {string} The random string.
|
|
1837
|
+
* Object defining types of cache storage.
|
|
1838
|
+
* @readonly
|
|
2156
1839
|
*/
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
1840
|
+
const CacheTypeEnum = {
|
|
1841
|
+
/** Represents session storage */
|
|
1842
|
+
SESSION: 0,
|
|
1843
|
+
/** Represents local storage */
|
|
1844
|
+
LOCAL: 1,
|
|
1845
|
+
};
|
|
1846
|
+
|
|
1847
|
+
const ERROR_MESSAGES = {
|
|
1848
|
+
warning: 'Algo no está del todo bien. Verifica y vuelve a intentar.',
|
|
1849
|
+
error: 'Ocurrió un error inesperado.',
|
|
1850
|
+
critical: 'Error crítico. Contacta al soporte técnico.',
|
|
1851
|
+
validation: 'Hay errores en el formulario. Revisa los campos.',
|
|
1852
|
+
component: 'Error en la carga del componente. Intenta recargar.',
|
|
1853
|
+
network: 'Problema de conexión. Verifica tu internet.',
|
|
1854
|
+
authentication: 'No tienes permisos para realizar esta acción.',
|
|
1855
|
+
runtime: 'Error de ejecución. Intenta nuevamente.',
|
|
1856
|
+
type: 'Tipo de error no identificado.',
|
|
1857
|
+
reference: 'Referencia no válida.',
|
|
1858
|
+
syntax: 'Error de sintaxis en el código.',
|
|
1859
|
+
range: 'Error en el rango de valores.',
|
|
1860
|
+
eval: 'Error en la evaluación.',
|
|
1861
|
+
uri: 'URI no válida.',
|
|
1862
|
+
};
|
|
1863
|
+
const ERROR_STYLES = {
|
|
1864
|
+
warning: 'color: orange; font-weight: bold;',
|
|
1865
|
+
error: 'color: red; font-weight: bold;',
|
|
1866
|
+
critical: 'color: white; background-color: red; font-weight: bold; padding: 2px;',
|
|
1867
|
+
validation: 'color: blue; font-weight: bold;',
|
|
1868
|
+
component: 'color: purple; font-weight: bold;',
|
|
1869
|
+
network: 'color: cyan; font-weight: bold;',
|
|
1870
|
+
authentication: 'color: brown; font-weight: bold;',
|
|
1871
|
+
runtime: 'color: darkred; font-weight: bold;',
|
|
1872
|
+
type: 'color: gray; font-weight: bold;',
|
|
1873
|
+
reference: 'color: darkorange; font-weight: bold;',
|
|
1874
|
+
syntax: 'color: darkblue; font-weight: bold;',
|
|
1875
|
+
range: 'color: darkgreen; font-weight: bold;',
|
|
1876
|
+
eval: 'color: darkviolet; font-weight: bold;',
|
|
1877
|
+
uri: 'color: darkcyan; font-weight: bold;',
|
|
1878
|
+
};
|
|
1879
|
+
|
|
1880
|
+
class RestStd {
|
|
1881
|
+
static resource;
|
|
1882
|
+
static isFormData = false;
|
|
1883
|
+
static headers = {};
|
|
1884
|
+
static fetchComposable;
|
|
1885
|
+
/**
|
|
1886
|
+
* Set global headers for all requests.
|
|
1887
|
+
* @param headers Object containing headers to be set globally.
|
|
1888
|
+
*/
|
|
1889
|
+
static setHeaders(headers) {
|
|
1890
|
+
this.headers = { ...this.headers, ...headers };
|
|
1891
|
+
}
|
|
1892
|
+
/**
|
|
1893
|
+
* Convert data to FormData if isFormData is true, otherwise return the data as is.
|
|
1894
|
+
* @param data Data to be converted.
|
|
1895
|
+
* @returns Data in FormData format or as is.
|
|
1896
|
+
*/
|
|
1897
|
+
static transformData(data) {
|
|
1898
|
+
if (this.isFormData) {
|
|
1899
|
+
const formData = objectToFormData(data);
|
|
1900
|
+
return formData;
|
|
1901
|
+
}
|
|
1902
|
+
return data;
|
|
1903
|
+
}
|
|
1904
|
+
/**
|
|
1905
|
+
* Fetch a list of items from the server.
|
|
1906
|
+
*
|
|
1907
|
+
* @param params Query parameters for filtering the results.
|
|
1908
|
+
* @param options Additional options for the fetch composable.
|
|
1909
|
+
* @returns The result of the fetch composable (typically a promise).
|
|
1910
|
+
*/
|
|
1911
|
+
static getMany(params, options = {}) {
|
|
1912
|
+
return this.fetchComposable({
|
|
1913
|
+
method: 'GET',
|
|
1914
|
+
url: this.resource,
|
|
1915
|
+
params,
|
|
1916
|
+
headers: this.headers,
|
|
1917
|
+
}, options);
|
|
1918
|
+
}
|
|
1919
|
+
/**
|
|
1920
|
+
* Fetch a single item by ID from the server.
|
|
1921
|
+
*
|
|
1922
|
+
* @param id The ID of the item to fetch.
|
|
1923
|
+
* @param params Additional query parameters for the request.
|
|
1924
|
+
* @param options Additional options for the fetch composable.
|
|
1925
|
+
* @returns The result of the fetch composable (typically a promise).
|
|
1926
|
+
*/
|
|
1927
|
+
static getOne(id, params, options = {}) {
|
|
1928
|
+
return this.fetchComposable({
|
|
1929
|
+
method: 'GET',
|
|
1930
|
+
url: `${this.resource}/${id}`,
|
|
1931
|
+
params,
|
|
1932
|
+
headers: this.headers,
|
|
1933
|
+
}, options);
|
|
1934
|
+
}
|
|
1935
|
+
/**
|
|
1936
|
+
* Create a new item on the server.
|
|
1937
|
+
*
|
|
1938
|
+
* @param data The data for the new item to create.
|
|
1939
|
+
* @param options Additional options for the fetch composable.
|
|
1940
|
+
* @returns The result of the fetch composable (typically a promise).
|
|
1941
|
+
*/
|
|
1942
|
+
static create(data, options = {}) {
|
|
1943
|
+
const transformedData = this.transformData(data);
|
|
1944
|
+
return this.fetchComposable({
|
|
1945
|
+
method: 'POST',
|
|
1946
|
+
url: this.resource,
|
|
1947
|
+
data: transformedData,
|
|
1948
|
+
headers: {
|
|
1949
|
+
...this.headers,
|
|
1950
|
+
'Content-Type': this.isFormData ? ContentTypeEnum.FORM_DATA : ContentTypeEnum.JSON,
|
|
1951
|
+
},
|
|
1952
|
+
}, options);
|
|
1953
|
+
}
|
|
1954
|
+
/**
|
|
1955
|
+
* Update an existing item on the server.
|
|
1956
|
+
*
|
|
1957
|
+
* @param id The ID of the item to update.
|
|
1958
|
+
* @param data The updated data for the item.
|
|
1959
|
+
* @param options Additional options for the fetch composable.
|
|
1960
|
+
* @returns The result of the fetch composable (typically a promise).
|
|
1961
|
+
*/
|
|
1962
|
+
static update(id, data, options = {}) {
|
|
1963
|
+
const transformedData = this.transformData(data);
|
|
1964
|
+
return this.fetchComposable({
|
|
1965
|
+
method: 'PUT',
|
|
1966
|
+
url: `${this.resource}/${id}`,
|
|
1967
|
+
data: transformedData,
|
|
1968
|
+
headers: {
|
|
1969
|
+
...this.headers,
|
|
1970
|
+
'Content-Type': this.isFormData ? ContentTypeEnum.FORM_DATA : ContentTypeEnum.JSON,
|
|
1971
|
+
},
|
|
1972
|
+
}, options);
|
|
1973
|
+
}
|
|
1974
|
+
/**
|
|
1975
|
+
* Partially update an existing item on the server.
|
|
1976
|
+
*
|
|
1977
|
+
* @param id The ID of the item to update.
|
|
1978
|
+
* @param data The updated data for the item.
|
|
1979
|
+
* @param options Additional options for the fetch composable.
|
|
1980
|
+
* @returns The result of the fetch composable (typically a promise).
|
|
1981
|
+
*/
|
|
1982
|
+
static patch(id, data, options = {}) {
|
|
1983
|
+
const transformedData = this.transformData(data);
|
|
1984
|
+
return this.fetchComposable({
|
|
1985
|
+
method: 'PATCH',
|
|
1986
|
+
url: `${this.resource}/${id}`,
|
|
1987
|
+
data: transformedData,
|
|
1988
|
+
headers: {
|
|
1989
|
+
...this.headers,
|
|
1990
|
+
'Content-Type': this.isFormData ? ContentTypeEnum.FORM_DATA : ContentTypeEnum.JSON,
|
|
1991
|
+
},
|
|
1992
|
+
}, options);
|
|
1993
|
+
}
|
|
1994
|
+
/**
|
|
1995
|
+
* Delete an item from the server.
|
|
1996
|
+
*
|
|
1997
|
+
* @param id The ID of the item to delete.
|
|
1998
|
+
* @param options Additional options for the fetch composable.
|
|
1999
|
+
* @returns The result of the fetch composable (typically a promise).
|
|
2000
|
+
*/
|
|
2001
|
+
static delete(id, options = {}) {
|
|
2002
|
+
return this.fetchComposable({
|
|
2003
|
+
method: 'DELETE',
|
|
2004
|
+
url: `${this.resource}/${id}`,
|
|
2005
|
+
headers: this.headers,
|
|
2006
|
+
}, options);
|
|
2007
|
+
}
|
|
2008
|
+
/**
|
|
2009
|
+
* Custom request method for more flexibility.
|
|
2010
|
+
*
|
|
2011
|
+
* @param method HTTP method (GET, POST, etc.).
|
|
2012
|
+
* @param params Query parameters.
|
|
2013
|
+
* @param data Request body data.
|
|
2014
|
+
* @param options Additional options for the fetch composable.
|
|
2015
|
+
* @returns The result of the fetch composable (typically a promise).
|
|
2016
|
+
*/
|
|
2017
|
+
static customRequest(method, params, data, options = {}) {
|
|
2018
|
+
const transformedData = this.transformData(data);
|
|
2019
|
+
return this.fetchComposable({
|
|
2020
|
+
method: method,
|
|
2021
|
+
url: this.resource,
|
|
2022
|
+
params: params,
|
|
2023
|
+
data: transformedData,
|
|
2024
|
+
headers: {
|
|
2025
|
+
...this.headers,
|
|
2026
|
+
'Content-Type': this.isFormData ? ContentTypeEnum.FORM_DATA : ContentTypeEnum.JSON,
|
|
2027
|
+
},
|
|
2028
|
+
}, options);
|
|
2029
|
+
}
|
|
2030
|
+
/**
|
|
2031
|
+
* Save method that decides whether to create or update based on the presence of 'id'.
|
|
2032
|
+
* It calls either create or update based on the data.
|
|
2033
|
+
*
|
|
2034
|
+
* @param data The data for the item to create or update.
|
|
2035
|
+
* @param options Additional options for the fetch composable.
|
|
2036
|
+
* @returns The result of the fetch composable (typically a promise).
|
|
2037
|
+
*/
|
|
2038
|
+
static save(data, options = {}) {
|
|
2039
|
+
if (data.id) {
|
|
2040
|
+
return this.update(data.id, data, options);
|
|
2041
|
+
}
|
|
2042
|
+
else {
|
|
2043
|
+
return this.create(data, options);
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2160
2046
|
}
|
|
2161
|
-
// Example usage:
|
|
2162
|
-
// console.log(generateRandomString(10)); // 'A1b2C3d4E5'
|
|
2163
2047
|
|
|
2164
2048
|
/**
|
|
2165
|
-
*
|
|
2166
|
-
*
|
|
2167
|
-
* @
|
|
2049
|
+
* Custom composable function for making API requests with Axios.
|
|
2050
|
+
*
|
|
2051
|
+
* @template T The expected return type of the API request.
|
|
2052
|
+
* @param {AxiosInstance} axios - The Axios instance for making HTTP requests.
|
|
2053
|
+
* @param {AxiosRequestConfig} axiosRequest - Configuration for the Axios request.
|
|
2054
|
+
* @returns {Promise<T>} A promise with the result of the API request.
|
|
2168
2055
|
*/
|
|
2169
|
-
function
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
32, // Space
|
|
2176
|
-
241, // ñ
|
|
2177
|
-
209, // Ñ
|
|
2178
|
-
225, // á
|
|
2179
|
-
233, // é
|
|
2180
|
-
237, // í
|
|
2181
|
-
243, // ó
|
|
2182
|
-
250, // ú
|
|
2183
|
-
193, // Á
|
|
2184
|
-
201, // É
|
|
2185
|
-
205, // Í
|
|
2186
|
-
211, // Ó
|
|
2187
|
-
218 // Ú
|
|
2188
|
-
];
|
|
2189
|
-
if (validKeys.includes(key)) {
|
|
2190
|
-
return true;
|
|
2191
|
-
}
|
|
2192
|
-
e.preventDefault();
|
|
2193
|
-
return false;
|
|
2056
|
+
async function axiosFetch(axios, axiosRequest) {
|
|
2057
|
+
return axios(axiosRequest)
|
|
2058
|
+
.then(response => response.data)
|
|
2059
|
+
.catch(error => {
|
|
2060
|
+
throw error;
|
|
2061
|
+
});
|
|
2194
2062
|
}
|
|
2195
|
-
|
|
2196
|
-
// document.addEventListener('keydown', validateLetters);
|
|
2063
|
+
|
|
2197
2064
|
/**
|
|
2198
|
-
*
|
|
2199
|
-
* @param
|
|
2200
|
-
* @
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
95, // Underscore
|
|
2210
|
-
32 // Space
|
|
2211
|
-
];
|
|
2212
|
-
if (validKeys.includes(key)) {
|
|
2213
|
-
return true;
|
|
2065
|
+
* Maneja y registra errores en la consola, además de permitir redirecciones configurables.
|
|
2066
|
+
* @param error - Error capturado (string o Error)
|
|
2067
|
+
* @param redirect - `true` si se quiere redirigir a una página de error
|
|
2068
|
+
* @param route - Ruta personalizada para la página de error (por defecto: `/error`)
|
|
2069
|
+
* @param passMethod - Método para pasar el error a la ruta (`query`, `localStorage`, `sessionStorage`)
|
|
2070
|
+
* @returns Mensaje user-friendly del error
|
|
2071
|
+
*/
|
|
2072
|
+
function handleError(error, redirect = false, route = '/error', passMethod = 'query') {
|
|
2073
|
+
if (!(error instanceof Error)) {
|
|
2074
|
+
console.error("Unrecognized error:", error);
|
|
2075
|
+
return;
|
|
2214
2076
|
}
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
//
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2077
|
+
const router = useRouter();
|
|
2078
|
+
const type = inferErrorType(error);
|
|
2079
|
+
const message = ERROR_MESSAGES[type] || 'Error desconocido.';
|
|
2080
|
+
// Mostrar el error en la consola con estilos
|
|
2081
|
+
console.log(`%c[${type.toUpperCase()}] ${message}`, ERROR_STYLES[type]);
|
|
2082
|
+
// Pasar el error según el método elegido
|
|
2083
|
+
if (redirect) {
|
|
2084
|
+
if (passMethod === 'query') {
|
|
2085
|
+
router.push({ path: route, query: { errorMessage: encodeURIComponent(message) } });
|
|
2086
|
+
}
|
|
2087
|
+
else if (passMethod === 'localStorage') {
|
|
2088
|
+
localStorage.setItem('errorMessage', message);
|
|
2089
|
+
router.push(route);
|
|
2090
|
+
}
|
|
2091
|
+
else if (passMethod === 'sessionStorage') {
|
|
2092
|
+
sessionStorage.setItem('errorMessage', message);
|
|
2093
|
+
router.push(route);
|
|
2094
|
+
}
|
|
2229
2095
|
}
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2096
|
+
return message;
|
|
2097
|
+
}
|
|
2098
|
+
/**
|
|
2099
|
+
* Infiera el tipo de error basado en su mensaje o nombre.
|
|
2100
|
+
* @param error - El error que se desea analizar.
|
|
2101
|
+
* @returns El tipo de error determinado.
|
|
2102
|
+
*/
|
|
2103
|
+
function inferErrorType(error) {
|
|
2104
|
+
const msg = typeof error === 'string' ? error : error.message;
|
|
2105
|
+
if (typeof error === 'string')
|
|
2106
|
+
return 'error';
|
|
2107
|
+
if (error.name === 'ValidationError')
|
|
2108
|
+
return 'validation';
|
|
2109
|
+
if (error.name === 'NetworkError' || msg.includes('Network'))
|
|
2110
|
+
return 'network';
|
|
2111
|
+
if (error.name === 'AuthenticationError' || msg.includes('Unauthorized'))
|
|
2112
|
+
return 'authentication';
|
|
2113
|
+
if (msg.includes('Component'))
|
|
2114
|
+
return 'component';
|
|
2115
|
+
if (msg.includes('Runtime'))
|
|
2116
|
+
return 'runtime';
|
|
2117
|
+
return 'error';
|
|
2243
2118
|
}
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2119
|
+
|
|
2120
|
+
let tokenConfig = {
|
|
2121
|
+
ACCESS_TOKEN: 'authToken',
|
|
2122
|
+
REFRESH_TOKEN: 'refreshToken',
|
|
2123
|
+
};
|
|
2247
2124
|
/**
|
|
2248
|
-
*
|
|
2249
|
-
*
|
|
2250
|
-
* @
|
|
2125
|
+
* Función para configurar las claves globalmente.
|
|
2126
|
+
* Esta función congela el objeto para evitar modificaciones posteriores.
|
|
2127
|
+
* @param {string} accessTokenKey - Nombre de la clave del token de acceso
|
|
2128
|
+
* @param {string} refreshTokenKey - Nombre de la clave del refresh token
|
|
2251
2129
|
*/
|
|
2252
|
-
function
|
|
2253
|
-
|
|
2254
|
-
|
|
2130
|
+
function configureTokenKeys(accessTokenKey, refreshTokenKey) {
|
|
2131
|
+
tokenConfig = Object.freeze({
|
|
2132
|
+
ACCESS_TOKEN: accessTokenKey,
|
|
2133
|
+
REFRESH_TOKEN: refreshTokenKey,
|
|
2134
|
+
});
|
|
2255
2135
|
}
|
|
2256
|
-
// Example usage:
|
|
2257
|
-
// console.log(isValidEmail('example@domain.com')); // true
|
|
2258
|
-
// console.log(isValidEmail('invalid-email')); // false
|
|
2259
2136
|
/**
|
|
2260
|
-
*
|
|
2261
|
-
*
|
|
2262
|
-
* @returns {boolean} True if the URL is valid, otherwise false.
|
|
2137
|
+
* Obtiene las claves configuradas para los tokens.
|
|
2138
|
+
* El objeto está congelado, por lo que no se puede modificar directamente.
|
|
2263
2139
|
*/
|
|
2264
|
-
function
|
|
2265
|
-
|
|
2266
|
-
new URL(url);
|
|
2267
|
-
return true;
|
|
2268
|
-
}
|
|
2269
|
-
catch {
|
|
2270
|
-
return false;
|
|
2271
|
-
}
|
|
2140
|
+
function getTokenConfig() {
|
|
2141
|
+
return tokenConfig;
|
|
2272
2142
|
}
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2143
|
+
|
|
2144
|
+
let endpointsConfig = {
|
|
2145
|
+
LOGIN: '/login',
|
|
2146
|
+
REFRESH: '/refresh',
|
|
2147
|
+
LOGOUT: '/logout',
|
|
2148
|
+
};
|
|
2276
2149
|
/**
|
|
2277
|
-
*
|
|
2278
|
-
*
|
|
2279
|
-
*
|
|
2150
|
+
* Configura las URLs de los endpoints de autenticación globalmente.
|
|
2151
|
+
* Esta función congela el objeto para evitar modificaciones posteriores.
|
|
2152
|
+
*
|
|
2153
|
+
* @param {string} loginEndpoint - URL del endpoint para el login.
|
|
2154
|
+
* @param {string} refreshEndpoint - URL del endpoint para el refresh token.
|
|
2155
|
+
* @param {string} logoutEndpoint - URL del endpoint para el logout.
|
|
2156
|
+
*
|
|
2157
|
+
* @returns {void} No retorna nada, pero congela el objeto de configuración de endpoints.
|
|
2280
2158
|
*/
|
|
2281
|
-
function
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
return dateObj.getFullYear() === year && dateObj.getMonth() === month - 1 && dateObj.getDate() === day;
|
|
2159
|
+
function configureEndpoints(loginEndpoint, refreshEndpoint, logoutEndpoint) {
|
|
2160
|
+
endpointsConfig = Object.freeze({
|
|
2161
|
+
LOGIN: loginEndpoint,
|
|
2162
|
+
REFRESH: refreshEndpoint,
|
|
2163
|
+
LOGOUT: logoutEndpoint,
|
|
2164
|
+
});
|
|
2288
2165
|
}
|
|
2289
|
-
// Example usage:
|
|
2290
|
-
// console.log(isValidDate('2024-08-31')); // true
|
|
2291
|
-
// console.log(isValidDate('2024-02-30')); // false
|
|
2292
2166
|
/**
|
|
2293
|
-
*
|
|
2294
|
-
*
|
|
2295
|
-
* @returns {
|
|
2167
|
+
* Obtiene las URLs de los endpoints de autenticación configurados.
|
|
2168
|
+
*
|
|
2169
|
+
* @returns {Object} Objeto con las URLs de los endpoints configurados.
|
|
2170
|
+
* @returns {string} returns.LOGIN - URL del endpoint de login.
|
|
2171
|
+
* @returns {string} returns.REFRESH - URL del endpoint de refresh token.
|
|
2172
|
+
* @returns {string} returns.LOGOUT - URL del endpoint de logout.
|
|
2296
2173
|
*/
|
|
2297
|
-
function
|
|
2298
|
-
|
|
2299
|
-
const hasUpperCase = /[A-Z]/.test(password);
|
|
2300
|
-
const hasLowerCase = /[a-z]/.test(password);
|
|
2301
|
-
const hasNumbers = /[0-9]/.test(password);
|
|
2302
|
-
const hasSpecialChars = /[!@#$%^&*(),.?":{}|<>]/.test(password);
|
|
2303
|
-
return password.length >= minLength && hasUpperCase && hasLowerCase && hasNumbers && hasSpecialChars;
|
|
2174
|
+
function getEndpointsConfig() {
|
|
2175
|
+
return endpointsConfig;
|
|
2304
2176
|
}
|
|
2305
|
-
|
|
2306
|
-
// console.log(isStrongPassword('Strong1@password')); // true
|
|
2307
|
-
// console.log(isStrongPassword('weakpass')); // false
|
|
2177
|
+
|
|
2308
2178
|
/**
|
|
2309
|
-
*
|
|
2310
|
-
*
|
|
2311
|
-
* @returns {boolean} True if the credit card number is valid, otherwise false.
|
|
2179
|
+
* AxiosService class encapsulates the Axios configuration and logic.
|
|
2180
|
+
* It manages request and response interceptors and provides methods for making HTTP requests.
|
|
2312
2181
|
*/
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2182
|
+
class AxiosService {
|
|
2183
|
+
instance;
|
|
2184
|
+
cancelTokenSource;
|
|
2185
|
+
activeRequests = 0;
|
|
2186
|
+
refreshTokenInProgress = false;
|
|
2187
|
+
refreshTokenUrl;
|
|
2188
|
+
/**
|
|
2189
|
+
* Initializes the AxiosService instance by creating an Axios instance with default configuration
|
|
2190
|
+
* and setting up request and response interceptors.
|
|
2191
|
+
*/
|
|
2192
|
+
constructor(url, headers = {}) {
|
|
2193
|
+
this.cancelTokenSource = axios.CancelToken.source();
|
|
2194
|
+
this.instance = axios.create({
|
|
2195
|
+
baseURL: url ?? '',
|
|
2196
|
+
timeout: 300000,
|
|
2197
|
+
headers: {
|
|
2198
|
+
'Accept': 'application/json',
|
|
2199
|
+
'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate',
|
|
2200
|
+
'Pragma': 'no-cache',
|
|
2201
|
+
'Expires': '0',
|
|
2202
|
+
...headers,
|
|
2203
|
+
},
|
|
2204
|
+
withCredentials: false,
|
|
2205
|
+
});
|
|
2206
|
+
const endpointsConfig = getEndpointsConfig();
|
|
2207
|
+
this.refreshTokenUrl = endpointsConfig.REFRESH;
|
|
2208
|
+
this.initializeInterceptors();
|
|
2209
|
+
}
|
|
2210
|
+
/**
|
|
2211
|
+
* Initializes request and response interceptors for the Axios instance.
|
|
2212
|
+
*/
|
|
2213
|
+
initializeInterceptors() {
|
|
2214
|
+
const { ACCESS_TOKEN, REFRESH_TOKEN } = getTokenConfig();
|
|
2215
|
+
this.instance.interceptors.request.use((config) => {
|
|
2216
|
+
const token = typeof window !== 'undefined' ? localStorage.getItem(ACCESS_TOKEN) : null;
|
|
2217
|
+
if (token && config.headers) {
|
|
2218
|
+
config.headers.Authorization = `Bearer ${token}`;
|
|
2219
|
+
}
|
|
2220
|
+
config.cancelToken = this.cancelTokenSource.token;
|
|
2221
|
+
this.activeRequests++;
|
|
2222
|
+
return config;
|
|
2223
|
+
}, (error) => {
|
|
2224
|
+
handleError(error, false);
|
|
2225
|
+
this.activeRequests++;
|
|
2226
|
+
return Promise.reject(error);
|
|
2227
|
+
});
|
|
2228
|
+
this.instance.interceptors.response.use((response) => {
|
|
2229
|
+
this.activeRequests--;
|
|
2230
|
+
return response;
|
|
2231
|
+
}, async (error) => {
|
|
2232
|
+
this.activeRequests--;
|
|
2233
|
+
// Check for unauthorized access (401)
|
|
2234
|
+
if (axios.isAxiosError(error) && error.response?.status === 401 && !this.refreshTokenInProgress) {
|
|
2235
|
+
const refreshToken = localStorage.getItem(REFRESH_TOKEN);
|
|
2236
|
+
if (refreshToken) {
|
|
2237
|
+
this.refreshTokenInProgress = true;
|
|
2238
|
+
try {
|
|
2239
|
+
// Attempt to refresh the token
|
|
2240
|
+
const refreshResponse = await axios.post(this.refreshTokenUrl, { refreshToken });
|
|
2241
|
+
const { token: newAccessToken } = refreshResponse.data;
|
|
2242
|
+
if (newAccessToken) {
|
|
2243
|
+
// Save the new access token
|
|
2244
|
+
localStorage.setItem(ACCESS_TOKEN, newAccessToken);
|
|
2245
|
+
// Retry the original request with the new token
|
|
2246
|
+
if (error.config && error.config.headers) {
|
|
2247
|
+
error.config.headers['Authorization'] = `Bearer ${newAccessToken}`;
|
|
2248
|
+
}
|
|
2249
|
+
this.refreshTokenInProgress = false;
|
|
2250
|
+
if (error.config) {
|
|
2251
|
+
return this.instance(error.config);
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
catch (refreshError) {
|
|
2256
|
+
handleError(refreshError, false);
|
|
2257
|
+
this.refreshTokenInProgress = false;
|
|
2258
|
+
localStorage.removeItem(ACCESS_TOKEN);
|
|
2259
|
+
localStorage.removeItem(REFRESH_TOKEN);
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
handleError(error, false);
|
|
2264
|
+
return Promise.reject(error);
|
|
2265
|
+
});
|
|
2266
|
+
}
|
|
2267
|
+
/**
|
|
2268
|
+
* Returns the number of active requests.
|
|
2269
|
+
*/
|
|
2270
|
+
getActiveRequests() {
|
|
2271
|
+
return this.activeRequests;
|
|
2272
|
+
}
|
|
2273
|
+
/**
|
|
2274
|
+
* Returns the Axios instance with the configured settings and interceptors.
|
|
2275
|
+
* @returns {AxiosInstance} The configured Axios instance.
|
|
2276
|
+
*/
|
|
2277
|
+
getAxiosInstance() {
|
|
2278
|
+
return this.instance;
|
|
2279
|
+
}
|
|
2280
|
+
/**
|
|
2281
|
+
* Cancels all ongoing requests.
|
|
2282
|
+
*/
|
|
2283
|
+
cancelAllRequests() {
|
|
2284
|
+
this.cancelTokenSource.cancel('Operation canceled by the user.');
|
|
2285
|
+
this.cancelTokenSource = axios.CancelToken.source();
|
|
2286
|
+
}
|
|
2287
|
+
/**
|
|
2288
|
+
* Sets a new header for the Axios instance.
|
|
2289
|
+
* @param {string} key - The header key.
|
|
2290
|
+
* @param {string} value - The header value.
|
|
2291
|
+
*/
|
|
2292
|
+
setHeader(key, value) {
|
|
2293
|
+
this.instance.defaults.headers.common[key] = value;
|
|
2294
|
+
}
|
|
2295
|
+
/**
|
|
2296
|
+
* Removes a header from the Axios instance.
|
|
2297
|
+
* @param {string} key - The header key to remove.
|
|
2298
|
+
*/
|
|
2299
|
+
removeHeader(key) {
|
|
2300
|
+
delete this.instance.defaults.headers.common[key];
|
|
2326
2301
|
}
|
|
2327
|
-
return sum % 10 === 0;
|
|
2328
|
-
}
|
|
2329
|
-
// Example usage:
|
|
2330
|
-
// console.log(isValidCreditCard('4111111111111111')); // true
|
|
2331
|
-
// console.log(isValidCreditCard('1234567812345670')); // false
|
|
2332
|
-
/**
|
|
2333
|
-
* Validates if a string is a valid hex color code.
|
|
2334
|
-
* @param {string} color The color code to validate.
|
|
2335
|
-
* @returns {boolean} True if the color code is valid, otherwise false.
|
|
2336
|
-
*/
|
|
2337
|
-
function isValidHexColor(color) {
|
|
2338
|
-
const hexPattern = /^#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})$/;
|
|
2339
|
-
return hexPattern.test(color);
|
|
2340
2302
|
}
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
// console.log(isValidHexColor('#FFF')); // true
|
|
2344
|
-
// console.log(isValidHexColor('#12345G')); // false
|
|
2303
|
+
|
|
2304
|
+
let axiosInstance;
|
|
2345
2305
|
/**
|
|
2346
|
-
*
|
|
2347
|
-
* @param {string}
|
|
2348
|
-
* @returns {boolean} True if the time is valid, otherwise false.
|
|
2306
|
+
* Configures the global Axios instance with a base URL.
|
|
2307
|
+
* @param {string} baseURL - The base URL for the Axios instance.
|
|
2349
2308
|
*/
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
}
|
|
2354
|
-
// Example usage:
|
|
2355
|
-
// console.log(isValidTime('14:30')); // true
|
|
2356
|
-
// console.log(isValidTime('25:00')); // false
|
|
2309
|
+
const configureAxios = (baseURL) => {
|
|
2310
|
+
axiosInstance = new AxiosService(baseURL);
|
|
2311
|
+
};
|
|
2357
2312
|
/**
|
|
2358
|
-
*
|
|
2359
|
-
* @
|
|
2360
|
-
* @
|
|
2313
|
+
* Retrieves the configured Axios instance.
|
|
2314
|
+
* @returns {AxiosService} The configured Axios instance.
|
|
2315
|
+
* @throws Will throw an error if the Axios instance is not configured.
|
|
2361
2316
|
*/
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
}
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
// console.log(isValidIP('999.999.999.999')); // false
|
|
2317
|
+
const getAxiosInstance = () => {
|
|
2318
|
+
if (!axiosInstance) {
|
|
2319
|
+
throw new Error('Axios instance not configured. Call configureAxios first.');
|
|
2320
|
+
}
|
|
2321
|
+
return axiosInstance.getAxiosInstance();
|
|
2322
|
+
};
|
|
2369
2323
|
/**
|
|
2370
|
-
*
|
|
2371
|
-
* @param {string}
|
|
2372
|
-
* @
|
|
2324
|
+
* Creates a new AxiosService instance with custom headers.
|
|
2325
|
+
* @param {string} baseURL - The base URL for the Axios instance.
|
|
2326
|
+
* @param {Record<string, string>} headers - Custom headers to set.
|
|
2327
|
+
* @returns {AxiosService} The new AxiosService instance.
|
|
2373
2328
|
*/
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
// Example usage:
|
|
2379
|
-
// console.log(isValidSSN('123-45-6789')); // true
|
|
2380
|
-
// console.log(isValidSSN('123-45-678')); // false
|
|
2329
|
+
const createCustomAxiosInstance = (baseURL, headers) => {
|
|
2330
|
+
return new AxiosService(baseURL, headers).getAxiosInstance();
|
|
2331
|
+
};
|
|
2332
|
+
|
|
2381
2333
|
/**
|
|
2382
|
-
*
|
|
2383
|
-
* @param
|
|
2384
|
-
* @
|
|
2334
|
+
* Creates a fetchComposable for Axios requests with an optional custom instance.
|
|
2335
|
+
* @param fetchComposable - The composable to use for the request (can be any fetch function).
|
|
2336
|
+
* @param axiosCustomInstance - An optional custom Axios instance to use for the request.
|
|
2337
|
+
* @returns A function that handles the request using the provided composable.
|
|
2385
2338
|
*/
|
|
2386
|
-
function
|
|
2387
|
-
const
|
|
2388
|
-
return
|
|
2339
|
+
function createFetch(fetchComposable, axiosCustomInstance) {
|
|
2340
|
+
const instance = axiosCustomInstance || getAxiosInstance();
|
|
2341
|
+
return (axiosRequestConfig, options) => {
|
|
2342
|
+
return fetchComposable(instance, axiosRequestConfig, options);
|
|
2343
|
+
};
|
|
2389
2344
|
}
|
|
2390
|
-
|
|
2391
|
-
// console.log(isValidZIP('12345')); // true
|
|
2392
|
-
// console.log(isValidZIP('12345-6789')); // true
|
|
2393
|
-
// console.log(isValidZIP('1234')); // false
|
|
2345
|
+
|
|
2394
2346
|
/**
|
|
2395
|
-
*
|
|
2396
|
-
*
|
|
2397
|
-
* @
|
|
2347
|
+
* Custom composable for integrating Axios requests with Vue Query.
|
|
2348
|
+
*
|
|
2349
|
+
* @template T - The type of data expected from the query.
|
|
2350
|
+
* @param {AxiosInstance} axios - Axios instance used for making HTTP requests.
|
|
2351
|
+
* @param {AxiosRequestConfig} axiosRequest - Initial Axios request configuration.
|
|
2352
|
+
* @param {ExtendedQueryOptions<T>} [queryOptions] - Optional Vue Query options with server execution flag.
|
|
2353
|
+
* @returns {object} Composable functions and query state for managing API requests.
|
|
2398
2354
|
*/
|
|
2399
|
-
function
|
|
2400
|
-
const
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
const
|
|
2404
|
-
const
|
|
2405
|
-
const
|
|
2406
|
-
|
|
2355
|
+
function useVueQuery(axios, axiosRequest, queryOptions) {
|
|
2356
|
+
const key = ref([
|
|
2357
|
+
queryOptions?.queryKey || `${axiosRequest.url}-${JSON.stringify(axiosRequest)}`
|
|
2358
|
+
]);
|
|
2359
|
+
const axiosRequestRef = ref(axiosRequest);
|
|
2360
|
+
const queryOptionsRef = ref(queryOptions);
|
|
2361
|
+
const isInitialized = ref(false);
|
|
2362
|
+
const errorCallbacks = ref([]);
|
|
2363
|
+
const resultCallbacks = ref([]);
|
|
2364
|
+
/**
|
|
2365
|
+
* Vue Query instance for managing API requests.
|
|
2366
|
+
*/
|
|
2367
|
+
const query = useQuery({
|
|
2368
|
+
queryKey: key.value,
|
|
2369
|
+
queryFn: async () => {
|
|
2370
|
+
const response = await axios(axiosRequestRef.value);
|
|
2371
|
+
return response.data;
|
|
2372
|
+
},
|
|
2373
|
+
...(queryOptionsRef.value?.options ?? {})
|
|
2374
|
+
});
|
|
2375
|
+
/**
|
|
2376
|
+
* Executes the query manually with optional new key and request parameters.
|
|
2377
|
+
*
|
|
2378
|
+
* @param {Array<any>} [newKey] - New query key to use.
|
|
2379
|
+
* @param {object} [newRequestParams] - New request parameters to use.
|
|
2380
|
+
* @param {ExtendedQueryOptions<T>} [newQueryOptions] - New Vue Query options to use.
|
|
2381
|
+
*/
|
|
2382
|
+
function execute(newKey, newRequestParams, newQueryOptions) {
|
|
2383
|
+
if (newKey) {
|
|
2384
|
+
key.value = newKey;
|
|
2385
|
+
}
|
|
2386
|
+
if (newRequestParams) {
|
|
2387
|
+
axiosRequestRef.value.data = newRequestParams;
|
|
2388
|
+
}
|
|
2389
|
+
if (newQueryOptions) {
|
|
2390
|
+
queryOptionsRef.value = newQueryOptions;
|
|
2391
|
+
}
|
|
2392
|
+
query.refetch();
|
|
2393
|
+
}
|
|
2394
|
+
/**
|
|
2395
|
+
* Registers a callback to be executed on successful request result.
|
|
2396
|
+
* If data is already available, the callback will be executed immediately.
|
|
2397
|
+
*
|
|
2398
|
+
* @param {(data: T) => void} cb - Callback function to handle the result.
|
|
2399
|
+
*/
|
|
2400
|
+
function onResult(cb) {
|
|
2401
|
+
resultCallbacks.value.push(cb);
|
|
2402
|
+
if (query.data.value !== undefined && query.data.value !== null) {
|
|
2403
|
+
cb(query.data.value);
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
/**
|
|
2407
|
+
* Registers a callback to be executed when the request results in an error.
|
|
2408
|
+
* If an error is already present, the callback will be executed immediately.
|
|
2409
|
+
*
|
|
2410
|
+
* @param {(e: unknown) => void} callback - Callback function to handle errors.
|
|
2411
|
+
*/
|
|
2412
|
+
function onError(callback) {
|
|
2413
|
+
errorCallbacks.value.push(callback);
|
|
2414
|
+
if (query.error.value !== undefined) {
|
|
2415
|
+
callback(query.error.value);
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
/**
|
|
2419
|
+
* Watches for changes in the query result and triggers result callbacks.
|
|
2420
|
+
*/
|
|
2421
|
+
watch(query.data, (newData) => {
|
|
2422
|
+
if (newData !== undefined && newData !== null && isInitialized.value) {
|
|
2423
|
+
resultCallbacks.value.forEach((cb) => cb(newData));
|
|
2424
|
+
}
|
|
2425
|
+
});
|
|
2426
|
+
/**
|
|
2427
|
+
* Watches for changes in query errors and triggers error callbacks.
|
|
2428
|
+
*/
|
|
2429
|
+
watch(query.error, (newError) => {
|
|
2430
|
+
if (newError !== undefined && isInitialized.value) {
|
|
2431
|
+
errorCallbacks.value.forEach((cb) => cb(newError));
|
|
2432
|
+
}
|
|
2433
|
+
});
|
|
2434
|
+
/**
|
|
2435
|
+
* Handles server-side prefetching for SSR.
|
|
2436
|
+
* Ensures data is fetched before rendering on the server.
|
|
2437
|
+
*/
|
|
2438
|
+
onServerPrefetch(async () => {
|
|
2439
|
+
if (queryOptionsRef.value?.server !== false) {
|
|
2440
|
+
await query.suspense();
|
|
2441
|
+
if (query.data.value != null) {
|
|
2442
|
+
resultCallbacks.value.forEach((cb) => cb(query.data.value));
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
});
|
|
2446
|
+
/**
|
|
2447
|
+
* Ensures callbacks are executed after the component is mounted on the client-side.
|
|
2448
|
+
*/
|
|
2449
|
+
onMounted(() => {
|
|
2450
|
+
isInitialized.value = true;
|
|
2451
|
+
if (query.data.value != null) {
|
|
2452
|
+
resultCallbacks.value.forEach((cb) => cb(query.data.value));
|
|
2453
|
+
}
|
|
2454
|
+
if (query.error.value !== undefined) {
|
|
2455
|
+
errorCallbacks.value.forEach((cb) => cb(query.error.value));
|
|
2456
|
+
}
|
|
2457
|
+
});
|
|
2458
|
+
return {
|
|
2459
|
+
execute,
|
|
2460
|
+
onResult,
|
|
2461
|
+
onError,
|
|
2462
|
+
...query
|
|
2463
|
+
};
|
|
2407
2464
|
}
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
// console.log(isValidExpiryDate('12/22')); // false
|
|
2465
|
+
|
|
2466
|
+
let notified = false;
|
|
2411
2467
|
/**
|
|
2412
|
-
*
|
|
2413
|
-
*
|
|
2414
|
-
* @returns {
|
|
2468
|
+
* Composable to manage breakpoints using Tailwind CSS and shared window resize listener.
|
|
2469
|
+
*
|
|
2470
|
+
* @returns {Object} An object containing the breakpoints and their states.
|
|
2415
2471
|
*/
|
|
2416
|
-
function
|
|
2417
|
-
const
|
|
2418
|
-
|
|
2472
|
+
function useBreakpoint() {
|
|
2473
|
+
const breakpoints = useBreakpoints(breakpointsTailwind);
|
|
2474
|
+
const current = breakpoints.current();
|
|
2475
|
+
const active = breakpoints.active();
|
|
2476
|
+
// Define breakpoints
|
|
2477
|
+
const sm_S = breakpoints.smaller('sm');
|
|
2478
|
+
const sm_SE = breakpoints.smallerOrEqual('sm');
|
|
2479
|
+
const sm_GE = breakpoints.greaterOrEqual('sm');
|
|
2480
|
+
const sm_md = breakpoints.between('sm', 'md');
|
|
2481
|
+
const sm_xl = breakpoints.between('sm', 'xl');
|
|
2482
|
+
const sm_2xl = breakpoints.between('sm', '2xl');
|
|
2483
|
+
const md_S = breakpoints.smaller('md');
|
|
2484
|
+
const md_SE = breakpoints.smallerOrEqual('md');
|
|
2485
|
+
const md_GE = breakpoints.greaterOrEqual('md');
|
|
2486
|
+
const md_lg = breakpoints.between('md', 'lg');
|
|
2487
|
+
const md_xl = breakpoints.between('md', 'xl');
|
|
2488
|
+
const md_2xl = breakpoints.between('md', '2xl');
|
|
2489
|
+
const lg_S = breakpoints.smaller('lg');
|
|
2490
|
+
const lg_SE = breakpoints.smallerOrEqual('lg');
|
|
2491
|
+
const lg_GE = breakpoints.greaterOrEqual('lg');
|
|
2492
|
+
const lg_xl = breakpoints.between('lg', 'xl');
|
|
2493
|
+
const lg_2xl = breakpoints.between('lg', '2xl');
|
|
2494
|
+
const xl_S = breakpoints.smaller('xl');
|
|
2495
|
+
const xl_SE = breakpoints.smallerOrEqual('xl');
|
|
2496
|
+
const xl_GE = breakpoints.greaterOrEqual('xl');
|
|
2497
|
+
const xl_2xl = breakpoints.between('xl', '2xl');
|
|
2498
|
+
const _2xl_S = breakpoints.smaller('2xl');
|
|
2499
|
+
const _2xl_SE = breakpoints.smallerOrEqual('2xl');
|
|
2500
|
+
const _2xl_GE = breakpoints.greaterOrEqual('2xl');
|
|
2501
|
+
const reactiveBreakpoint = ref('sm');
|
|
2502
|
+
const isGreaterThanBreakpoint = breakpoints.greaterOrEqual(() => reactiveBreakpoint.value);
|
|
2503
|
+
// Define device ranges
|
|
2504
|
+
const mobile = breakpoints.smaller('md'); // <768px
|
|
2505
|
+
const tablet = breakpoints.between('md', 'lg'); // 768px - 1024px
|
|
2506
|
+
const laptop = breakpoints.between('lg', 'xl'); // 1024px - 1280px
|
|
2507
|
+
const desktop = breakpoints.greaterOrEqual('xl'); // >=1280px
|
|
2508
|
+
// Use VueUse for window size
|
|
2509
|
+
const { width: windowWidth, height: windowHeight } = useWindowSize();
|
|
2510
|
+
/**
|
|
2511
|
+
* Logs a styled "chip" message to the console.
|
|
2512
|
+
*
|
|
2513
|
+
* @param {string} label - The label to display inside the chip.
|
|
2514
|
+
* @param {string} backgroundColor - The background color of the chip.
|
|
2515
|
+
* @param {string} textColor - The text color of the chip.
|
|
2516
|
+
*/
|
|
2517
|
+
const logChip = (label, backgroundColor, textColor = 'white') => {
|
|
2518
|
+
console.log(`%c ${label} `, `background-color: ${backgroundColor}; color: ${textColor}; border-radius: 16px; padding: 4px 8px;`);
|
|
2519
|
+
};
|
|
2520
|
+
// Singleton logic: Only notify once
|
|
2521
|
+
const notifyOnce = () => {
|
|
2522
|
+
if (notified)
|
|
2523
|
+
return;
|
|
2524
|
+
watch([mobile, tablet, laptop, desktop], ([isMobile, isTablet, isLaptop, isDesktop]) => {
|
|
2525
|
+
if (isMobile) {
|
|
2526
|
+
logChip('Mobile', '#ff6b6b');
|
|
2527
|
+
}
|
|
2528
|
+
else if (isTablet) {
|
|
2529
|
+
logChip('Tablet', '#4ecdc4');
|
|
2530
|
+
}
|
|
2531
|
+
else if (isLaptop) {
|
|
2532
|
+
logChip('Laptop', '#1a535c');
|
|
2533
|
+
}
|
|
2534
|
+
else if (isDesktop) {
|
|
2535
|
+
logChip('Desktop', '#f7fff7', 'black');
|
|
2536
|
+
}
|
|
2537
|
+
notified = true; // Mark as notified
|
|
2538
|
+
}, { immediate: true });
|
|
2539
|
+
};
|
|
2540
|
+
notifyOnce(); // Ensure the notification happens only once globally
|
|
2541
|
+
return {
|
|
2542
|
+
current,
|
|
2543
|
+
active,
|
|
2544
|
+
sm_S,
|
|
2545
|
+
sm_SE,
|
|
2546
|
+
sm_GE,
|
|
2547
|
+
sm_md,
|
|
2548
|
+
sm_xl,
|
|
2549
|
+
sm_2xl,
|
|
2550
|
+
md_S,
|
|
2551
|
+
md_SE,
|
|
2552
|
+
md_GE,
|
|
2553
|
+
md_lg,
|
|
2554
|
+
md_xl,
|
|
2555
|
+
md_2xl,
|
|
2556
|
+
lg_S,
|
|
2557
|
+
lg_SE,
|
|
2558
|
+
lg_GE,
|
|
2559
|
+
lg_xl,
|
|
2560
|
+
lg_2xl,
|
|
2561
|
+
xl_S,
|
|
2562
|
+
xl_SE,
|
|
2563
|
+
xl_GE,
|
|
2564
|
+
xl_2xl,
|
|
2565
|
+
_2xl_S,
|
|
2566
|
+
_2xl_SE,
|
|
2567
|
+
_2xl_GE,
|
|
2568
|
+
isGreaterThanBreakpoint,
|
|
2569
|
+
reactiveBreakpoint,
|
|
2570
|
+
breakpointsTailwind,
|
|
2571
|
+
windowWidth,
|
|
2572
|
+
windowHeight,
|
|
2573
|
+
mobile,
|
|
2574
|
+
tablet,
|
|
2575
|
+
laptop,
|
|
2576
|
+
desktop,
|
|
2577
|
+
breakpoints,
|
|
2578
|
+
};
|
|
2419
2579
|
}
|
|
2420
|
-
|
|
2421
|
-
// console.log(isValidHexColorAlpha('#RRGGBBAA')); // true
|
|
2422
|
-
// console.log(isValidHexColorAlpha('#FFFFFF')); // false
|
|
2580
|
+
|
|
2423
2581
|
/**
|
|
2424
|
-
*
|
|
2425
|
-
*
|
|
2426
|
-
* @
|
|
2582
|
+
* A composable function that filters objects based on a specific field and criteria.
|
|
2583
|
+
*
|
|
2584
|
+
* @param {Object[]} items - The list of objects to filter.
|
|
2585
|
+
* @param {Object} filterConfig - Configuration for the filter, specifying the field, the data type to filter, and the criteria.
|
|
2586
|
+
* @param {string} filterConfig.field - The field in the objects to filter.
|
|
2587
|
+
* @param {string} filterConfig.type - The data type of the field ('date', 'string', 'number', 'boolean').
|
|
2588
|
+
* @param {any} filterConfig.criteria - The criteria for filtering. Can be a date range, a string to search, a number range, or a boolean value.
|
|
2589
|
+
*
|
|
2590
|
+
* @returns {Object[]} - The filtered list of objects.
|
|
2427
2591
|
*/
|
|
2428
|
-
function
|
|
2429
|
-
|
|
2430
|
-
|
|
2592
|
+
function useFilter(items, filterConfig) {
|
|
2593
|
+
// Ensure the filterConfig is correctly structured
|
|
2594
|
+
const { field, type, criteria } = filterConfig;
|
|
2595
|
+
// Define the filter functions
|
|
2596
|
+
const filterByDate = (objects, field, { startDate, endDate }) => objects.filter(item => {
|
|
2597
|
+
const date = new Date(item[field]);
|
|
2598
|
+
return date >= new Date(startDate) && date <= new Date(endDate);
|
|
2599
|
+
});
|
|
2600
|
+
const filterByString = (objects, field, searchTerm) => {
|
|
2601
|
+
const normalize = (str) => str.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
|
|
2602
|
+
const words = normalize(searchTerm).split(/\s+/).filter(Boolean);
|
|
2603
|
+
return objects.filter(item => {
|
|
2604
|
+
const normalizedFieldValue = normalize(item[field]);
|
|
2605
|
+
return words.every(word => normalizedFieldValue.includes(word));
|
|
2606
|
+
});
|
|
2607
|
+
};
|
|
2608
|
+
const filterByNumber = (objects, field, { min, max }) => objects.filter(item => {
|
|
2609
|
+
const value = item[field];
|
|
2610
|
+
return value >= min && value <= max;
|
|
2611
|
+
});
|
|
2612
|
+
const filterByBoolean = (objects, field, expectedValue) => objects.filter(item => item[field] === expectedValue);
|
|
2613
|
+
// Apply the appropriate filter and return the filtered objects
|
|
2614
|
+
const filteredObjects = computed(() => {
|
|
2615
|
+
if (!criteria ||
|
|
2616
|
+
(type === 'date' && (!criteria.startDate || !criteria.endDate)) ||
|
|
2617
|
+
(type === 'number' && (criteria.min == null || criteria.max == null)) ||
|
|
2618
|
+
(type === 'boolean' && typeof criteria !== 'boolean')) {
|
|
2619
|
+
return items; // Return all objects if criteria is not valid
|
|
2620
|
+
}
|
|
2621
|
+
// Apply the filter based on the type
|
|
2622
|
+
switch (type) {
|
|
2623
|
+
case 'date':
|
|
2624
|
+
return filterByDate(items, field, criteria);
|
|
2625
|
+
case 'string':
|
|
2626
|
+
return filterByString(items, field, criteria);
|
|
2627
|
+
case 'number':
|
|
2628
|
+
return filterByNumber(items, field, criteria);
|
|
2629
|
+
case 'boolean':
|
|
2630
|
+
return filterByBoolean(items, field, criteria);
|
|
2631
|
+
default:
|
|
2632
|
+
return items;
|
|
2633
|
+
}
|
|
2634
|
+
});
|
|
2635
|
+
return filteredObjects.value;
|
|
2431
2636
|
}
|
|
2432
|
-
|
|
2433
|
-
// console.log(isValidUsername('user_name123')); // true
|
|
2434
|
-
// console.log(isValidUsername('us')); // false
|
|
2637
|
+
|
|
2435
2638
|
/**
|
|
2436
|
-
*
|
|
2437
|
-
*
|
|
2438
|
-
* @
|
|
2639
|
+
* Composable to handle pagination logic.
|
|
2640
|
+
*
|
|
2641
|
+
* @param page - Reactive ref for the current page number.
|
|
2642
|
+
* @param total - Reactive ref for total number of items.
|
|
2643
|
+
* @param pageSize - Reactive ref for number of items per page.
|
|
2644
|
+
* @returns An object with pagination controls.
|
|
2439
2645
|
*/
|
|
2440
|
-
function
|
|
2441
|
-
|
|
2442
|
-
|
|
2646
|
+
function usePagination(page, total, pageSize) {
|
|
2647
|
+
// Calculate the total number of pages
|
|
2648
|
+
const totalPages = computed(() => Math.ceil(total.value / pageSize.value));
|
|
2649
|
+
// Determine if the next page can be fetched
|
|
2650
|
+
const canFetchNextPage = () => page.value < totalPages.value;
|
|
2651
|
+
// Determine if the previous page can be fetched
|
|
2652
|
+
const canFetchPreviousPage = () => page.value > 1;
|
|
2653
|
+
return {
|
|
2654
|
+
totalPages,
|
|
2655
|
+
canFetchNextPage,
|
|
2656
|
+
canFetchPreviousPage
|
|
2657
|
+
};
|
|
2443
2658
|
}
|
|
2444
|
-
|
|
2445
|
-
// console.log(isValidAge('25')); // true
|
|
2446
|
-
// console.log(isValidAge('121')); // false
|
|
2659
|
+
|
|
2447
2660
|
/**
|
|
2448
|
-
*
|
|
2449
|
-
* @param {
|
|
2450
|
-
* @
|
|
2661
|
+
* Custom composable for sorting products based on a selected criterion.
|
|
2662
|
+
* @param {Array} items - The array of products to sort.
|
|
2663
|
+
* @param {Array} criteriaList - The list of available sorting criteria.
|
|
2664
|
+
* @param {Number} selectedCriteria - The currently selected sorting criterion.
|
|
2665
|
+
* @returns {ComputedRef<Array>} - The sorted array of products.
|
|
2451
2666
|
*/
|
|
2452
|
-
function
|
|
2453
|
-
|
|
2454
|
-
|
|
2667
|
+
function useSorter(items, criteriaList, selectedCriteria) {
|
|
2668
|
+
return computed(() => {
|
|
2669
|
+
const criteria = criteriaList.find(item => item.value === Number(selectedCriteria));
|
|
2670
|
+
if (!criteria)
|
|
2671
|
+
return items;
|
|
2672
|
+
return [...items].sort((a, b) => {
|
|
2673
|
+
const aValue = a[criteria.field];
|
|
2674
|
+
const bValue = b[criteria.field];
|
|
2675
|
+
if (criteria.type === 'number') {
|
|
2676
|
+
const numA = typeof aValue === 'number' ? aValue : parseFloat(aValue) || 0;
|
|
2677
|
+
const numB = typeof bValue === 'number' ? bValue : parseFloat(bValue) || 0;
|
|
2678
|
+
return criteria.order === 'asc' ? numA - numB : numB - numA;
|
|
2679
|
+
}
|
|
2680
|
+
else if (criteria.type === 'date') {
|
|
2681
|
+
const dateA = new Date(aValue).getTime();
|
|
2682
|
+
const dateB = new Date(bValue).getTime();
|
|
2683
|
+
return criteria.order === 'asc' ? dateA - dateB : dateB - dateA;
|
|
2684
|
+
}
|
|
2685
|
+
else if (criteria.type === 'boolean') {
|
|
2686
|
+
const boolA = !!aValue;
|
|
2687
|
+
const boolB = !!bValue;
|
|
2688
|
+
return criteria.order === 'asc' ? (boolA === boolB ? 0 : boolA ? 1 : -1) : (boolA === boolB ? 0 : boolA ? -1 : 1);
|
|
2689
|
+
}
|
|
2690
|
+
else {
|
|
2691
|
+
// Fallback for string sorting
|
|
2692
|
+
const strA = (aValue || '').toString().toLowerCase();
|
|
2693
|
+
const strB = (bValue || '').toString().toLowerCase();
|
|
2694
|
+
return criteria.order === 'asc' ? strA.localeCompare(strB) : strB.localeCompare(strA);
|
|
2695
|
+
}
|
|
2696
|
+
});
|
|
2697
|
+
}).value;
|
|
2455
2698
|
}
|
|
2456
|
-
// Example usage:
|
|
2457
|
-
// console.log(isValidHexNumber('1A3F')); // true
|
|
2458
|
-
// console.log(isValidHexNumber('GHIJ')); // false
|
|
2459
2699
|
|
|
2460
|
-
export { APP_LOCAL_CACHE_KEY, APP_SESSION_CACHE_KEY, AxiosService, CacheTypeEnum, ContentTypeEnum, ExceptionEnum, FileMetaTypes, ImageTypes, KeyCodeEnum, LOCALE_KEY, LOCK_INFO_KEY, PROJ_CFG_KEY, ROLES_KEY, RestStd, TOKEN_KEY, USER_INFO_KEY, addCustomKeyboardShortcut, addDays, addDoubleClickListener, addKeyListener, ageAtDate, axiosFetch, blobToFormData, bufferToBlob, calculateAge, clickOutside, compareObject, configureAxios, copyToClipboard, countWords, createFetch, createKeyMap, customShortcut, daysBetween, daysToNextBirthday, debounce, debounceAsync, debounceAsyncValidator, debounceAsyncWithImmediate, debounceLeading, debounceLeadingTrailing, debounceTrailing, deepClone, deepEqual, deepMerge, detectKeyHold, disableCopy, disableF12Key, disableMouseButtons, disableRightClick, disableSpecificKeys, downloadBlob, enableMouseButtons, enableRightClick, enableSpecificKeys, exportToCSV, exportToExcel, exportToJSON, exportToText, exportToXML, filterObjectByKeys, flattenObject, formDataToObject, formatDate, generateRandomString, getAxiosInstance, getEndOfMonth, getObjectDifferences, getObjectKeys, getQueryParam, getStartOfMonth, hasNestedProperties, isEmptyObject, isLeapYear, isStrongPassword, isValidAge, isValidCreditCard, isValidDate, isValidEmail, isValidExpiryDate, isValidHexColor, isValidHexColorAlpha, isValidHexNumber, isValidIP, isValidPhoneNumber, isValidSSN, isValidTime, isValidURL, isValidUsername, isValidZIP, lowerFirst, objectToFormData, objectToFormDataEnhanced, objectToQueryString, openWindow, parseDate, proxyToPlainObject, readFileAsDataURL, readFileAsText, registerKeyboardShortcuts, removeAccent, removeClickOutside, removeCustomKeyboardShortcut, removeCustomShortcuts, removeDoubleClickListener, removeEmptyProperties, removeKeyListeners, replaceAll, reverseString, safeGet, screenEnum, screenMap, scrollToTop, simulateKeyPress, sizeEnum, stopDetectingKeyHold, stringToBlob, subtractDays, throttle, toCamelCase, toKebabCase, toggleTabNavigation, truncateString, unregisterKeyboardShortcuts, upperFirst, useBreakpoint, useFilter, usePagination, useSorter, useVueQuery, validateAlphanumeric, validateLetters, validateNumbers };
|
|
2700
|
+
export { APP_LOCAL_CACHE_KEY, APP_SESSION_CACHE_KEY, AxiosService, CacheTypeEnum, ContentTypeEnum, ERROR_MESSAGES, ERROR_STYLES, ExceptionEnum, FileMetaTypes, ImageTypes, KeyCodeEnum, LOCALE_KEY, LOCK_INFO_KEY, PROJ_CFG_KEY, ROLES_KEY, RestStd, TOKEN_KEY, USER_INFO_KEY, addCustomKeyboardShortcut, addDays, addDoubleClickListener, addKeyListener, ageAtDate, axiosFetch, blobToFormData, bufferToBlob, calculateAge, clickOutside, compareObject, configureAxios, configureEndpoints, configureTokenKeys, copyToClipboard, countWords, createCustomAxiosInstance, createFetch, createKeyMap, customShortcut, daysBetween, daysToNextBirthday, debounce, debounceAsync, debounceAsyncValidator, debounceAsyncWithImmediate, debounceLeading, debounceLeadingTrailing, debounceTrailing, deepClone, deepEqual, deepMerge, detectKeyHold, disableCopy, disableF12Key, disableMouseButtons, disableRightClick, disableSpecificKeys, downloadBlob, enableMouseButtons, enableRightClick, enableSpecificKeys, exportToCSV, exportToExcel, exportToJSON, exportToText, exportToXML, filterObjectByKeys, flattenObject, formDataToObject, formatDate, generateRandomString, getAxiosInstance, getEndOfMonth, getEndpointsConfig, getObjectDifferences, getObjectKeys, getQueryParam, getStartOfMonth, getTokenConfig, hasNestedProperties, isEmptyObject, isLeapYear, isStrongPassword, isValidAge, isValidCreditCard, isValidDate, isValidEmail, isValidExpiryDate, isValidHexColor, isValidHexColorAlpha, isValidHexNumber, isValidIP, isValidPhoneNumber, isValidSSN, isValidTime, isValidURL, isValidUsername, isValidZIP, lowerFirst, objectToFormData, objectToFormDataEnhanced, objectToQueryString, openWindow, parseDate, proxyToPlainObject, readFileAsDataURL, readFileAsText, registerKeyboardShortcuts, removeAccent, removeClickOutside, removeCustomKeyboardShortcut, removeCustomShortcuts, removeDoubleClickListener, removeEmptyProperties, removeKeyListeners, replaceAll, reverseString, safeGet, screenEnum, screenMap, scrollToTop, simulateKeyPress, sizeEnum, stopDetectingKeyHold, stringToBlob, subtractDays, throttle, toCamelCase, toKebabCase, toggleTabNavigation, truncateString, unregisterKeyboardShortcuts, upperFirst, useBreakpoint, useFilter, usePagination, useSorter, useVueQuery, validateAlphanumeric, validateLetters, validateNumbers };
|