@pacem/pacem-core 1.0.0-abel
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/LICENSE +191 -0
- package/NOTICE +4 -0
- package/dist/browser/pacem-core.js +9099 -0
- package/dist/browser/pacem-core.js.map +1 -0
- package/dist/browser/pacem-core.min.js +2 -0
- package/dist/browser/pacem-core.min.js.map +1 -0
- package/dist/bundle/pacem-core.min.mjs +3 -0
- package/dist/bundle/pacem-core.mjs +8479 -0
- package/dist/bundle/pacem-core.mjs.map +7 -0
- package/dist/esm/adapter.js +7 -0
- package/dist/esm/command.js +10 -0
- package/dist/esm/comparers.js +8 -0
- package/dist/esm/converters.js +70 -0
- package/dist/esm/decorators-next.js +783 -0
- package/dist/esm/decorators.js +736 -0
- package/dist/esm/defaults.js +5 -0
- package/dist/esm/events.js +89 -0
- package/dist/esm/eventtarget.js +131 -0
- package/dist/esm/expression.js +228 -0
- package/dist/esm/index-animations.js +1 -0
- package/dist/esm/index-behaviors.js +1 -0
- package/dist/esm/index-components.js +1 -0
- package/dist/esm/index-iife.js +3 -0
- package/dist/esm/index-logging.js +1 -0
- package/dist/esm/index-net.js +1 -0
- package/dist/esm/index-root.js +20 -0
- package/dist/esm/index-ui.js +1 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/logger.js +11 -0
- package/dist/esm/prefix.js +6 -0
- package/dist/esm/promise.js +34 -0
- package/dist/esm/regex.js +22 -0
- package/dist/esm/storage.js +56 -0
- package/dist/esm/template.js +4 -0
- package/dist/esm/transforms.js +248 -0
- package/dist/esm/types.js +10 -0
- package/dist/esm/utils-customelement.js +524 -0
- package/dist/esm/utils-customevent.js +60 -0
- package/dist/esm/utils.js +1073 -0
- package/package.json +37 -0
- package/typings/index.d.ts +2521 -0
|
@@ -0,0 +1,1073 @@
|
|
|
1
|
+
import { Keys, Dates, Colors, Strings, Blobs, Imaging, NullChecker, detect } from '@pacem/pacem-foundation';
|
|
2
|
+
import { Json } from './trees/json';
|
|
3
|
+
import { DeepCloner } from './trees/deepcloner';
|
|
4
|
+
import { DeferPromise } from './promise';
|
|
5
|
+
// namespace Pacem {
|
|
6
|
+
const PACEM_CORE_DEFAULT = 'pacem';
|
|
7
|
+
const DEFAULT_DOWNLOAD_FILENAME = 'download';
|
|
8
|
+
export const stopPropagationHandler = (evt) => {
|
|
9
|
+
evt.stopPropagation();
|
|
10
|
+
};
|
|
11
|
+
export const preventDefaultHandler = (evt) => {
|
|
12
|
+
evt.preventDefault();
|
|
13
|
+
};
|
|
14
|
+
export const avoidHandler = (evt) => {
|
|
15
|
+
evt.preventDefault();
|
|
16
|
+
evt.stopPropagation();
|
|
17
|
+
};
|
|
18
|
+
const SEGMENT_PATTERN = /^\/[^\/]+/;
|
|
19
|
+
const ROUTE_SEGMENT_PATTERN = /\/\{([a-z\$_][\w]*)\??\}/;
|
|
20
|
+
export class Utils {
|
|
21
|
+
static get core() {
|
|
22
|
+
const root = window['Pacem'];
|
|
23
|
+
var id = (root && root.Configuration && root.Configuration.core) || PACEM_CORE_DEFAULT;
|
|
24
|
+
return window[id] = window[id] || {};
|
|
25
|
+
}
|
|
26
|
+
static get customElements() {
|
|
27
|
+
return window['customElements'];
|
|
28
|
+
}
|
|
29
|
+
// #region GENERAL
|
|
30
|
+
static uniqueId() {
|
|
31
|
+
return Keys.uniqueId();
|
|
32
|
+
}
|
|
33
|
+
static uniqueCode() {
|
|
34
|
+
return Keys.uniqueCode();
|
|
35
|
+
}
|
|
36
|
+
static randomString(length) {
|
|
37
|
+
return Keys.randomString(length);
|
|
38
|
+
}
|
|
39
|
+
static parseDate(input) {
|
|
40
|
+
return Dates.parse(input);
|
|
41
|
+
}
|
|
42
|
+
static copyToClipboard(input) {
|
|
43
|
+
if (navigator.clipboard) {
|
|
44
|
+
return navigator.clipboard.writeText(input);
|
|
45
|
+
}
|
|
46
|
+
// deprecated code branch
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
const el = document.createElement('textarea');
|
|
49
|
+
el.value = input;
|
|
50
|
+
el.style.opacity = "0";
|
|
51
|
+
document.body.appendChild(el);
|
|
52
|
+
try {
|
|
53
|
+
el.select();
|
|
54
|
+
document.execCommand('copy');
|
|
55
|
+
el.remove();
|
|
56
|
+
resolve();
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
reject(e);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
// json-dedicated
|
|
64
|
+
static { this.Json = {
|
|
65
|
+
stringify: Json.serialize,
|
|
66
|
+
parse: Json.deserialize
|
|
67
|
+
}; }
|
|
68
|
+
// dates-dedicated (moved to foundations, kept for backwards compat)
|
|
69
|
+
static { this.Dates = {
|
|
70
|
+
now: () => Utils.parseDate(Date.now()),
|
|
71
|
+
parse: Utils.parseDate,
|
|
72
|
+
isLeapYear: Dates.isLeapYear,
|
|
73
|
+
daysInMonth: Dates.daysInMonth,
|
|
74
|
+
/**
|
|
75
|
+
* Gets whether a date is an `Invalid Date` or not.
|
|
76
|
+
* @param date
|
|
77
|
+
*/
|
|
78
|
+
isDate: Dates.isDate,
|
|
79
|
+
dateOnly: Dates.dateOnly,
|
|
80
|
+
addMonths: Dates.addMonths,
|
|
81
|
+
addDays: Dates.addDays,
|
|
82
|
+
daysBetween: Dates.daysBetween,
|
|
83
|
+
}; }
|
|
84
|
+
static colorize(elementOrRgb, rgb) {
|
|
85
|
+
const hsl0 = /*Colors.hsl(Colors.parse('#000'))*/ { h: 38, s: .245, l: .6 };
|
|
86
|
+
var realRgb, rgba, filter, element;
|
|
87
|
+
if (elementOrRgb instanceof HTMLElement) {
|
|
88
|
+
realRgb = rgb;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
realRgb = elementOrRgb;
|
|
92
|
+
}
|
|
93
|
+
// rgba
|
|
94
|
+
if (typeof realRgb === 'string') {
|
|
95
|
+
rgba = Colors.parse(realRgb);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
rgba = { r: realRgb[0], g: realRgb[1], b: realRgb[2] };
|
|
99
|
+
}
|
|
100
|
+
const hsl = Colors.hsl(rgba);
|
|
101
|
+
filter = `brightness(50%) sepia(1) hue-rotate(${(hsl.h - hsl0.h)}deg) saturate(${(1.0 + (hsl.s - hsl0.s))}) brightness(${(1.0 + (hsl.l - hsl0.l))})`;
|
|
102
|
+
if (realRgb === rgb) {
|
|
103
|
+
element.style.filter = filter;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
return filter;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
static cssEscape(input) {
|
|
110
|
+
return escape(input).replace('%', '\\');
|
|
111
|
+
}
|
|
112
|
+
// css-dedicated
|
|
113
|
+
static { this.Css = {
|
|
114
|
+
colorize: Utils.colorize,
|
|
115
|
+
escape: Utils.cssEscape,
|
|
116
|
+
getVariableValue(name) {
|
|
117
|
+
return getComputedStyle(document.documentElement).getPropertyValue(name);
|
|
118
|
+
},
|
|
119
|
+
setVariable(name, value) {
|
|
120
|
+
getComputedStyle(document.documentElement).setProperty(name, value);
|
|
121
|
+
},
|
|
122
|
+
/**
|
|
123
|
+
* Naive O(n^2) implementation for an actual css class presence (definition) in the current DOM.
|
|
124
|
+
* @param name CSS class name
|
|
125
|
+
*/
|
|
126
|
+
isClassDefined(name) {
|
|
127
|
+
const pattern = new RegExp('\.' + name + '($|[\s\.,])');
|
|
128
|
+
for (let j = 0; j < document.styleSheets.length; j++) {
|
|
129
|
+
const sheet = document.styleSheets.item(j);
|
|
130
|
+
try {
|
|
131
|
+
const rules = sheet.cssRules;
|
|
132
|
+
for (let i = 0; i < rules.length; i++) {
|
|
133
|
+
const rule = sheet.cssRules.item(i);
|
|
134
|
+
if (pattern.test(rule.selectorText)) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (e) {
|
|
140
|
+
if (e instanceof DOMException) {
|
|
141
|
+
console.warn('Couldn\'t access cssRules of stylesheet "' + sheet.href + '".');
|
|
142
|
+
// console.error(e);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return false;
|
|
147
|
+
},
|
|
148
|
+
deserializeTransform(element) {
|
|
149
|
+
const style = element instanceof Element ? getComputedStyle(element) : element;
|
|
150
|
+
let a = 1, b = 0, c = 0, d = 1, x = 0, y = 0, matches = /^matrix\((.*)\)$/.exec(style.transform), mij;
|
|
151
|
+
//matrix(1, 0, 0, 1, 2, 4)
|
|
152
|
+
if (matches && matches.length > 1 && (mij = matches[1].split(',')).length == 6) {
|
|
153
|
+
// scale + rot
|
|
154
|
+
a = parseFloat(mij[0]);
|
|
155
|
+
b = parseFloat(mij[1]);
|
|
156
|
+
c = parseFloat(mij[2]);
|
|
157
|
+
d = parseFloat(mij[3]);
|
|
158
|
+
// transl
|
|
159
|
+
x += parseFloat(mij[4]);
|
|
160
|
+
y += parseFloat(mij[5]);
|
|
161
|
+
}
|
|
162
|
+
return { a: a, b: b, c: c, d: d, e: x, f: y };
|
|
163
|
+
}
|
|
164
|
+
}; }
|
|
165
|
+
// #region strings
|
|
166
|
+
// legacy
|
|
167
|
+
static { this.leftPad = Strings.leftPad; }
|
|
168
|
+
static { this.formatString = Strings.format; }
|
|
169
|
+
static { this.diff = Strings.diff; }
|
|
170
|
+
static { this.Strings = {
|
|
171
|
+
leftPad: Strings.leftPad,
|
|
172
|
+
format: Strings.format,
|
|
173
|
+
diff: Strings.diff,
|
|
174
|
+
stripHTML: (html) => {
|
|
175
|
+
let doc = new DOMParser().parseFromString(html, 'text/html');
|
|
176
|
+
return doc.body.textContent || '';
|
|
177
|
+
}
|
|
178
|
+
}; }
|
|
179
|
+
// #endregion
|
|
180
|
+
// #endregion
|
|
181
|
+
// #region blob/files...
|
|
182
|
+
static { this.Blobs = {
|
|
183
|
+
toDataURL: Blobs.blobToDataURL,
|
|
184
|
+
fromDataURL: Blobs.dataURLToBlob,
|
|
185
|
+
toText: Blobs.blobToText,
|
|
186
|
+
fromText: Blobs.textToBlob
|
|
187
|
+
}; }
|
|
188
|
+
static { this.Images = {
|
|
189
|
+
load: Imaging.loadImage,
|
|
190
|
+
resize: Imaging.resizeImage
|
|
191
|
+
}; }
|
|
192
|
+
static loadImage(src) {
|
|
193
|
+
return new Promise((resolve, reject) => {
|
|
194
|
+
const img = new Image();
|
|
195
|
+
img.src = src;
|
|
196
|
+
if (img.complete) {
|
|
197
|
+
resolve(img);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
img.onload = (evt) => {
|
|
201
|
+
resolve(img);
|
|
202
|
+
};
|
|
203
|
+
img.onerror = (evt) => {
|
|
204
|
+
reject();
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
static blobToDataURL(blob) {
|
|
210
|
+
return Utils.Blobs.toDataURL(blob);
|
|
211
|
+
}
|
|
212
|
+
static dataURLToBlob(dataURL) {
|
|
213
|
+
return Utils.Blobs.fromDataURL(dataURL);
|
|
214
|
+
}
|
|
215
|
+
static blobToText(blob, encoding) {
|
|
216
|
+
return Utils.Blobs.toText(blob, encoding);
|
|
217
|
+
}
|
|
218
|
+
static textToBlob(content, type = 'text/plain') {
|
|
219
|
+
return Utils.Blobs.fromText(content, type);
|
|
220
|
+
}
|
|
221
|
+
/** @deprecated Use {Utils.Images.resize}, instead.
|
|
222
|
+
* Resizes an image when exceeding the provided size constraints.
|
|
223
|
+
* @param img {Blob} Input image
|
|
224
|
+
* @param maxWidth {number} Width constraint
|
|
225
|
+
* @param maxHeight {number} Height constraint
|
|
226
|
+
*/
|
|
227
|
+
static resizeImage(img, maxWidth, maxHeight, quality) {
|
|
228
|
+
var deferred = DeferPromise.defer();
|
|
229
|
+
Utils.blobToDataURL(img)
|
|
230
|
+
.then(Utils.loadImage)
|
|
231
|
+
.then(el => {
|
|
232
|
+
const origWidth = el.width, origHeight = el.height;
|
|
233
|
+
let cnv = document.createElement('canvas');
|
|
234
|
+
cnv.width = origWidth;
|
|
235
|
+
cnv.height = origHeight;
|
|
236
|
+
let ctx = cnv.getContext('2d');
|
|
237
|
+
//ctx.drawImage(el, 0, 0);
|
|
238
|
+
if (origWidth > maxWidth
|
|
239
|
+
|| origHeight > maxHeight) {
|
|
240
|
+
const origRatio = origWidth / origHeight, targetRatio = maxWidth / maxHeight;
|
|
241
|
+
let targetHeight, targetWidth;
|
|
242
|
+
if (targetRatio < origRatio) {
|
|
243
|
+
targetWidth = maxWidth;
|
|
244
|
+
targetHeight = maxWidth / origRatio;
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
targetHeight = maxHeight;
|
|
248
|
+
targetWidth = maxHeight * origRatio;
|
|
249
|
+
}
|
|
250
|
+
cnv.width = targetWidth;
|
|
251
|
+
cnv.height = targetHeight;
|
|
252
|
+
const jpeg = quality > 0 && quality <= 1;
|
|
253
|
+
if (jpeg) {
|
|
254
|
+
ctx.fillStyle = '#000';
|
|
255
|
+
ctx.fillRect(0, 0, cnv.width, cnv.height);
|
|
256
|
+
}
|
|
257
|
+
Utils.cropImageOntoCanvas(el, ctx);
|
|
258
|
+
let newUrl;
|
|
259
|
+
if (jpeg)
|
|
260
|
+
newUrl = ctx.canvas.toDataURL("image/jpeg", quality);
|
|
261
|
+
else
|
|
262
|
+
newUrl = ctx.canvas.toDataURL();
|
|
263
|
+
deferred.resolve(Utils.dataURLToBlob(newUrl));
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
deferred.resolve(img);
|
|
267
|
+
}
|
|
268
|
+
}, _ => {
|
|
269
|
+
// image loading error caught...
|
|
270
|
+
});
|
|
271
|
+
return deferred.promise;
|
|
272
|
+
}
|
|
273
|
+
/** @deprecated Use {Utils.Images.resize}, instead.
|
|
274
|
+
* Crops an image having the provided url (might be a dataURL) into another having the provided size
|
|
275
|
+
* @param url
|
|
276
|
+
* @param width
|
|
277
|
+
* @param height
|
|
278
|
+
* @param ctx
|
|
279
|
+
*/
|
|
280
|
+
static cropImage(url, width, height, quality) {
|
|
281
|
+
var deferred = DeferPromise.defer();
|
|
282
|
+
let el = new Image();
|
|
283
|
+
el.crossOrigin = 'anonymous';
|
|
284
|
+
el.onload = function (ev) {
|
|
285
|
+
let cnv = document.createElement('canvas');
|
|
286
|
+
let ctx = cnv.getContext('2d');
|
|
287
|
+
if (width)
|
|
288
|
+
cnv.width = width;
|
|
289
|
+
if (height)
|
|
290
|
+
cnv.height = height;
|
|
291
|
+
//
|
|
292
|
+
const jpeg = quality > 0 && quality <= 1;
|
|
293
|
+
if (jpeg) {
|
|
294
|
+
ctx.fillStyle = '#000';
|
|
295
|
+
ctx.fillRect(0, 0, cnv.width, cnv.height);
|
|
296
|
+
}
|
|
297
|
+
Utils.cropImageOntoCanvas(el, ctx);
|
|
298
|
+
deferred.resolve(jpeg ? cnv.toDataURL('image/jpeg', quality) : cnv.toDataURL());
|
|
299
|
+
};
|
|
300
|
+
el.src = url;
|
|
301
|
+
return deferred.promise;
|
|
302
|
+
}
|
|
303
|
+
static getUserMediaFunctions() {
|
|
304
|
+
var _getUserMedia = [];
|
|
305
|
+
let methods = [navigator['getUserMedia'], navigator['webkitGetUserMedia'], navigator['msGetUserMedia'], navigator['mozGetUserMedia']];
|
|
306
|
+
let fns = methods.filter(function (fn, j) { return typeof fn == 'function'; });
|
|
307
|
+
if (fns.length)
|
|
308
|
+
_getUserMedia = fns;
|
|
309
|
+
return _getUserMedia;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Crops the snapshot of a drawable element onto a provided canvas context. It gets centered in the area and cropped (`cover`-like behavior).
|
|
313
|
+
* @param el drawable element
|
|
314
|
+
* @param ctx canvas context
|
|
315
|
+
* @param sourceWidth forced source width
|
|
316
|
+
* @param sourceHeight forced source height
|
|
317
|
+
*/
|
|
318
|
+
static cropImageOntoCanvas(el, ctx, sourceWidth, sourceHeight) {
|
|
319
|
+
ctx.imageSmoothingEnabled =
|
|
320
|
+
ctx['mozImageSmoothingEnabled'] =
|
|
321
|
+
ctx['oImageSmoothingEnabled'] =
|
|
322
|
+
ctx['webkitImageSmoothingEnabled'] =
|
|
323
|
+
true;
|
|
324
|
+
//
|
|
325
|
+
let tgetW = ctx.canvas.width /*= parseFloat(scope.thumbWidth)*/;
|
|
326
|
+
let tgetH = ctx.canvas.height /*= parseFloat(scope.thumbHeight)*/;
|
|
327
|
+
let cnvW = tgetW, cnvH = tgetH;
|
|
328
|
+
let w = sourceWidth || 1.0 * el.width, h = sourceHeight || 1.0 * el.height;
|
|
329
|
+
//console.log('img original size: ' + w + 'x' + h);
|
|
330
|
+
var ratio = w / h;
|
|
331
|
+
var tgetRatio = tgetW / tgetH;
|
|
332
|
+
if (tgetRatio > ratio) {
|
|
333
|
+
// crop vertically
|
|
334
|
+
var f = tgetW / w;
|
|
335
|
+
tgetH = f * h;
|
|
336
|
+
ctx.drawImage(el, 0, .5 * (-tgetH + cnvH), cnvW, tgetH);
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
// crop horizontally
|
|
340
|
+
var f = tgetH / h;
|
|
341
|
+
tgetW = f * w;
|
|
342
|
+
ctx.drawImage(el, -Math.abs(.5 * (-tgetW + cnvW)), 0, tgetW, cnvH);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
static get _domURL() {
|
|
346
|
+
return window.URL || window['webkitURL'] || window;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Snapshots a DOM element and returns its blob image representation.
|
|
350
|
+
* @param el Element to snapshot
|
|
351
|
+
* @param background Background color to replace the original
|
|
352
|
+
* @param type Output mimetype (image/png, image/jpeg, images/webp, ...)
|
|
353
|
+
* @param quality Quality ratio between 0..1
|
|
354
|
+
*/
|
|
355
|
+
static snapshotElement(el, background, type = 'image/png', quality) {
|
|
356
|
+
function drawCanvas(img) {
|
|
357
|
+
return new Promise((resolve, reject) => {
|
|
358
|
+
const canvas = document.createElement('canvas'), width = img.width, height = img.height;
|
|
359
|
+
if (!(width > 0)) {
|
|
360
|
+
reject('Image has width zero.');
|
|
361
|
+
}
|
|
362
|
+
else if (!(height > 0)) {
|
|
363
|
+
reject('Image has height zero.');
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
canvas.width = width;
|
|
367
|
+
canvas.height = height;
|
|
368
|
+
const ctx = canvas.getContext('2d');
|
|
369
|
+
if (!Utils.isNullOrEmpty(background)) {
|
|
370
|
+
ctx.fillStyle = background;
|
|
371
|
+
ctx.fillRect(0, 0, width, height);
|
|
372
|
+
}
|
|
373
|
+
ctx.drawImage(img, 0, 0);
|
|
374
|
+
canvas.toBlob(b => {
|
|
375
|
+
resolve(b);
|
|
376
|
+
}, type, quality);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
if (el instanceof HTMLCanvasElement) {
|
|
381
|
+
return drawCanvas(el);
|
|
382
|
+
}
|
|
383
|
+
return new Promise((resolve, _) => {
|
|
384
|
+
const loadPromises = [];
|
|
385
|
+
//#region utils functions
|
|
386
|
+
function addLoadPromise(img) {
|
|
387
|
+
const p = new Promise((resolve0, _) => {
|
|
388
|
+
const getSrc = () => {
|
|
389
|
+
return img instanceof HTMLImageElement ? img.src : img.href.baseVal;
|
|
390
|
+
};
|
|
391
|
+
const setSrc = (src) => {
|
|
392
|
+
if (img instanceof HTMLImageElement)
|
|
393
|
+
img.src = src;
|
|
394
|
+
else {
|
|
395
|
+
img.setAttribute('href', src);
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
fetch(getSrc())
|
|
399
|
+
.then(r => r.blob())
|
|
400
|
+
.then(blob => Utils.blobToDataURL(blob))
|
|
401
|
+
.then(url => {
|
|
402
|
+
setSrc(url);
|
|
403
|
+
resolve0();
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
loadPromises.push(p);
|
|
407
|
+
}
|
|
408
|
+
function copyCanvas(copy, srcCanvas) {
|
|
409
|
+
const p = new Promise((resolve0, _) => {
|
|
410
|
+
const img = new Image();
|
|
411
|
+
img.crossOrigin = 'anonymous';
|
|
412
|
+
img.width = srcCanvas.width;
|
|
413
|
+
img.height = srcCanvas.height;
|
|
414
|
+
img.style.cssText = copy.style.cssText;
|
|
415
|
+
img.onload = () => {
|
|
416
|
+
copy.replaceWith(img);
|
|
417
|
+
resolve0();
|
|
418
|
+
};
|
|
419
|
+
img.src = srcCanvas.toDataURL();
|
|
420
|
+
});
|
|
421
|
+
loadPromises.push(p);
|
|
422
|
+
}
|
|
423
|
+
function applyCss(copy, src) {
|
|
424
|
+
const css = getComputedStyle(src);
|
|
425
|
+
for (let j = 0; j < css.length; j++) {
|
|
426
|
+
const item = css.item(j);
|
|
427
|
+
copy.style[item] = css[item];
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
function recSetupCopy(copy, src) {
|
|
431
|
+
// assign css text
|
|
432
|
+
applyCss(copy, src);
|
|
433
|
+
// bitmaps
|
|
434
|
+
if (copy instanceof HTMLImageElement || copy instanceof SVGImageElement) {
|
|
435
|
+
addLoadPromise(copy);
|
|
436
|
+
}
|
|
437
|
+
else if (copy instanceof HTMLCanvasElement) {
|
|
438
|
+
copyCanvas(copy, src);
|
|
439
|
+
}
|
|
440
|
+
// recursion
|
|
441
|
+
const childLength = copy.children.length;
|
|
442
|
+
for (let i = 0; i < childLength; i++) {
|
|
443
|
+
const copy1 = copy.children.item(i), src1 = src.children.item(i);
|
|
444
|
+
if (copy1.tagName === src1.tagName) {
|
|
445
|
+
recSetupCopy(copy1, src1);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
//#endregion
|
|
450
|
+
const isSvg = el instanceof SVGElement;
|
|
451
|
+
if (!isSvg) {
|
|
452
|
+
var doc = document.implementation.createHTMLDocument('');
|
|
453
|
+
doc.write(el.outerHTML);
|
|
454
|
+
doc.documentElement.setAttribute('xmlns', doc.documentElement.namespaceURI);
|
|
455
|
+
const xpath = document.evaluate(".//" + el.localName, doc.body, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
|
|
456
|
+
const node = xpath;
|
|
457
|
+
node.setAttribute('xmlns', doc.documentElement.namespaceURI);
|
|
458
|
+
recSetupCopy(node, el);
|
|
459
|
+
Promise.all(loadPromises).then(_ => {
|
|
460
|
+
// Get well-formed markup
|
|
461
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", 'svg');
|
|
462
|
+
const size = Utils.offset(el);
|
|
463
|
+
svg.setAttribute('viewBox', `0 0 ${size.width} ${size.height}`);
|
|
464
|
+
svg.setAttribute('width', size.width.toString());
|
|
465
|
+
svg.setAttribute('height', size.height.toString());
|
|
466
|
+
svg.innerHTML = `<foreignObject width="${size.width}" height="${size.height}">${node.outerHTML}</foreignObject>`;
|
|
467
|
+
el = svg;
|
|
468
|
+
execute();
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
const svg = el.cloneNode(true);
|
|
473
|
+
recSetupCopy(svg, el);
|
|
474
|
+
Promise.all(loadPromises).then(_ => {
|
|
475
|
+
el = svg;
|
|
476
|
+
execute();
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
function execute() {
|
|
480
|
+
const data = new XMLSerializer().serializeToString(el);
|
|
481
|
+
const img = new Image();
|
|
482
|
+
img.crossOrigin = 'anonymous';
|
|
483
|
+
// switching from blob to data url (see: https://bugs.chromium.org/p/chromium/issues/detail?id=294129)
|
|
484
|
+
//const domURL = this._domURL;
|
|
485
|
+
//var svg = new Blob([data], { type: 'image/svg+xml' });
|
|
486
|
+
//var url = domURL.createObjectURL(svg);
|
|
487
|
+
const url = 'data:image/svg+xml;charset=utf8,' + encodeURIComponent(data);
|
|
488
|
+
img.onload = () => {
|
|
489
|
+
drawCanvas(img).then(resolve);
|
|
490
|
+
//domURL.revokeObjectURL(url);
|
|
491
|
+
};
|
|
492
|
+
img.src = url;
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
static async download(arg0, arg1, mime = "application/download") {
|
|
497
|
+
if (typeof arg0 === 'string') {
|
|
498
|
+
const url = arg0;
|
|
499
|
+
let options = {}, filename;
|
|
500
|
+
if (!Utils.isNullOrEmpty(arg1)) {
|
|
501
|
+
if (typeof arg1 === 'object') {
|
|
502
|
+
options = { credentials: arg1.credentials, headers: arg1.headers };
|
|
503
|
+
filename = arg1.filename || filename;
|
|
504
|
+
mime = arg1.mime || mime;
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
filename = arg1;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
var response = await fetch(url, options);
|
|
511
|
+
if (response.ok) {
|
|
512
|
+
if (Utils.isNullOrEmpty(filename)) {
|
|
513
|
+
const headerName = 'Content-Disposition';
|
|
514
|
+
if (response.headers.has(headerName)) {
|
|
515
|
+
const header = response.headers.get(headerName), results = /^attachment; filename=\"?([^;\"]+)\"?(;|$)/.exec(header);
|
|
516
|
+
if (results.length > 1) {
|
|
517
|
+
filename = results[1];
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
filename = DEFAULT_DOWNLOAD_FILENAME;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
var blob = await response.blob();
|
|
525
|
+
const fn = filename.split(/[\\\/]/g).join('_');
|
|
526
|
+
return Utils.download(blob, fn);
|
|
527
|
+
}
|
|
528
|
+
{
|
|
529
|
+
const reason = await response.text();
|
|
530
|
+
throw new Error(reason);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
const content = arg0, filename = (typeof arg1 === 'string' && arg1) || DEFAULT_DOWNLOAD_FILENAME, fanchor = document.createElement('a');
|
|
535
|
+
fanchor.setAttribute('href', Utils._domURL.createObjectURL(content));
|
|
536
|
+
fanchor.setAttribute('download', filename);
|
|
537
|
+
if (document.createEvent) {
|
|
538
|
+
var event = document.createEvent('MouseEvents');
|
|
539
|
+
event.initEvent('click', true, true);
|
|
540
|
+
fanchor.dispatchEvent(event);
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
fanchor.click();
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
// #endregion
|
|
548
|
+
// #region DOM
|
|
549
|
+
static isDOMReady() {
|
|
550
|
+
// Must exclude 'interactive' state since module script haven't been processed yet.
|
|
551
|
+
return /* document.readyState === 'interactive' ||*/ document.readyState === 'complete';
|
|
552
|
+
}
|
|
553
|
+
static onDOMReady(listener) {
|
|
554
|
+
//window.addEventListener('DOMContentLoaded', listener, false);
|
|
555
|
+
// window 'load' event fires after 'DOMCOntentLoaded' since includes images load.
|
|
556
|
+
window.addEventListener('load', listener, false);
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Moves all the source element children to the target element and returns them as an array of nodes.
|
|
560
|
+
* @param source Source element.
|
|
561
|
+
* @param target Target element.
|
|
562
|
+
*/
|
|
563
|
+
static moveChildren(source, target) {
|
|
564
|
+
return Utils.moveItems(source.childNodes, target);
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Moves specific nodes to the target element and returns them as an array of nodes.
|
|
568
|
+
* @param nodes Nodes to move.
|
|
569
|
+
* @param target Target element.
|
|
570
|
+
*/
|
|
571
|
+
static moveItems(nodes, target) {
|
|
572
|
+
let dom = [], ref;
|
|
573
|
+
for (let j = nodes.length - 1; j >= 0; j--) {
|
|
574
|
+
let item = nodes instanceof NodeList ? nodes.item(j) : nodes[j];
|
|
575
|
+
target.insertBefore(item, ref);
|
|
576
|
+
dom.unshift(item);
|
|
577
|
+
ref = item;
|
|
578
|
+
}
|
|
579
|
+
return dom;
|
|
580
|
+
}
|
|
581
|
+
static is(el, selector) {
|
|
582
|
+
return (el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector)
|
|
583
|
+
.call(el, selector);
|
|
584
|
+
}
|
|
585
|
+
static lang(el) {
|
|
586
|
+
return el.lang || document.documentElement.lang || navigator.language;
|
|
587
|
+
}
|
|
588
|
+
static jsonSortStringify(obj) {
|
|
589
|
+
if (Utils.isNull(obj)) {
|
|
590
|
+
return obj;
|
|
591
|
+
}
|
|
592
|
+
return Utils.Json.stringify(obj);
|
|
593
|
+
}
|
|
594
|
+
static { this.Browsers = {
|
|
595
|
+
detect
|
|
596
|
+
}; }
|
|
597
|
+
static cookies(cookie) {
|
|
598
|
+
const cookies = (cookie ?? document.cookie).split(';'), retval = {};
|
|
599
|
+
for (var pair of cookies) {
|
|
600
|
+
const keyval = pair.split('=');
|
|
601
|
+
if (keyval.length < 2) {
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
const key = (keyval[0] || '').trim(), value = (keyval[1] || '').trim();
|
|
605
|
+
Object.defineProperty(retval, key, {
|
|
606
|
+
configurable: false, enumerable: true, writable: false,
|
|
607
|
+
value: decodeURIComponent(value.split('+').join(' '))
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
return retval;
|
|
611
|
+
}
|
|
612
|
+
static { this.Cookies = {
|
|
613
|
+
getAll: async (cookieName) => {
|
|
614
|
+
const store = 'cookieStore' in window ? window.cookieStore : null;
|
|
615
|
+
const retval = [];
|
|
616
|
+
if (Utils.isNull(store)) {
|
|
617
|
+
const nameValues = Utils.cookies();
|
|
618
|
+
for (let name in nameValues) {
|
|
619
|
+
if (!Utils.isNullOrEmpty(cookieName) && cookieName !== name) {
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
retval.push({ name, value: nameValues[name] });
|
|
623
|
+
}
|
|
624
|
+
return retval;
|
|
625
|
+
}
|
|
626
|
+
else {
|
|
627
|
+
return await store.getAll(cookieName);
|
|
628
|
+
}
|
|
629
|
+
},
|
|
630
|
+
stringify: (data) => {
|
|
631
|
+
let retval = `${data.name}=${encodeURIComponent(data.value ?? '')}`;
|
|
632
|
+
if (!Utils.isNullOrEmpty(data.domain)) {
|
|
633
|
+
retval += `; domain=${encodeURIComponent(data.domain)}`;
|
|
634
|
+
}
|
|
635
|
+
if (!Utils.isNullOrEmpty(data.path)) {
|
|
636
|
+
retval += `; path=${encodeURIComponent(data.path)}`;
|
|
637
|
+
}
|
|
638
|
+
if (data.maxAge > 0) {
|
|
639
|
+
retval += `; max-age=${data.maxAge.toFixed(0)}`;
|
|
640
|
+
}
|
|
641
|
+
if (!Utils.isNullOrEmpty(data.expires)) {
|
|
642
|
+
retval += `; expires=${data.expires}`;
|
|
643
|
+
}
|
|
644
|
+
if (data.partitioned) {
|
|
645
|
+
retval += '; Partitioned';
|
|
646
|
+
}
|
|
647
|
+
if (!Utils.isNullOrEmpty(data.sameSite)) {
|
|
648
|
+
retval += `; SameSite=${encodeURIComponent(data.sameSite)}`;
|
|
649
|
+
}
|
|
650
|
+
if (data.secure) {
|
|
651
|
+
retval += '; Secure';
|
|
652
|
+
}
|
|
653
|
+
return retval;
|
|
654
|
+
}
|
|
655
|
+
}; }
|
|
656
|
+
static isVisible(el) {
|
|
657
|
+
if ('checkVisibility' in el) {
|
|
658
|
+
return el.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true });
|
|
659
|
+
}
|
|
660
|
+
return getComputedStyle(el).visibility !== 'hidden' && (el.clientWidth > 0 || el.clientHeight > 0);
|
|
661
|
+
}
|
|
662
|
+
//#region classList DOMTokenList (css)
|
|
663
|
+
static hasClass(el, className) {
|
|
664
|
+
if (el.classList) {
|
|
665
|
+
return el.classList.contains(className);
|
|
666
|
+
}
|
|
667
|
+
else {
|
|
668
|
+
return new RegExp('(^| )' + className + '( |$)', 'gi').test(el.className);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
static addClass(el, className) {
|
|
672
|
+
const css = className.trim();
|
|
673
|
+
if (el.classList) {
|
|
674
|
+
DOMTokenList.prototype.add.apply(el.classList, css.split(' '));
|
|
675
|
+
}
|
|
676
|
+
else {
|
|
677
|
+
el.setAttribute('class', el.className + ' ' + css);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
static removeClass(el, className) {
|
|
681
|
+
const css = className.trim();
|
|
682
|
+
if (el.classList) {
|
|
683
|
+
DOMTokenList.prototype.remove.apply(el.classList, css.split(' '));
|
|
684
|
+
}
|
|
685
|
+
else {
|
|
686
|
+
el.setAttribute('class', el.className.replace(new RegExp('(^|\\b)' + css.split(' ').join('|') + '(\\b|$)', 'gi'), ' '));
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
static toggleClass(el, className, on) {
|
|
690
|
+
if (on) {
|
|
691
|
+
Utils.addClass(el, className);
|
|
692
|
+
}
|
|
693
|
+
else {
|
|
694
|
+
Utils.removeClass(el, className);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
//#endregion
|
|
698
|
+
//#region part DOMTokenList (shadow-DOM)
|
|
699
|
+
static hasPart(el, partName) {
|
|
700
|
+
return el.part.contains(partName);
|
|
701
|
+
}
|
|
702
|
+
static addPart(el, partName) {
|
|
703
|
+
DOMTokenList.prototype.add.apply(el.part, partName.trim().split(' '));
|
|
704
|
+
}
|
|
705
|
+
static removePart(el, partName) {
|
|
706
|
+
DOMTokenList.prototype.remove.apply(el.part, partName.trim().split(' '));
|
|
707
|
+
}
|
|
708
|
+
static togglePart(el, partName, on) {
|
|
709
|
+
if (on) {
|
|
710
|
+
Utils.addPart(el, partName);
|
|
711
|
+
}
|
|
712
|
+
else {
|
|
713
|
+
Utils.removePart(el, partName);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
//#endregion
|
|
717
|
+
/**
|
|
718
|
+
* Retrieve the bounding box of the provided element as of its rendered appearance on the stage (takes transformations into account).
|
|
719
|
+
* @param el The element.
|
|
720
|
+
*/
|
|
721
|
+
static offset(el) {
|
|
722
|
+
var rect = el.getBoundingClientRect();
|
|
723
|
+
return {
|
|
724
|
+
top: Math.round(rect.top) + Utils.scrollTop,
|
|
725
|
+
left: Math.round(rect.left) + Utils.scrollLeft,
|
|
726
|
+
width: Math.round(rect.width),
|
|
727
|
+
height: Math.round(rect.height)
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Retrieve the bounding box of the provided element as of its rendered appearance on the stage (takes transformations into account).
|
|
732
|
+
* @param el The element.
|
|
733
|
+
*/
|
|
734
|
+
static offsetRect(el) {
|
|
735
|
+
var rect = this.offset(el);
|
|
736
|
+
return {
|
|
737
|
+
y: rect.top,
|
|
738
|
+
x: rect.left,
|
|
739
|
+
width: rect.width,
|
|
740
|
+
height: rect.height
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
static naturalOffsetTop(el) {
|
|
744
|
+
return (el?.offsetTop ?? 0) + ((el?.offsetParent && el.offsetParent instanceof HTMLElement) ? this.naturalOffsetTop(el.offsetParent) : 0);
|
|
745
|
+
}
|
|
746
|
+
static naturalOffsetLeft(el) {
|
|
747
|
+
return (el?.offsetTop ?? 0) + ((el?.offsetParent && el.offsetParent instanceof HTMLElement) ? this.naturalOffsetLeft(el.offsetParent) : 0);
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* Retrieve the natural bounding box of the provided element (does not takes transformations into account).
|
|
751
|
+
* @param el {HTMLElement} The element.
|
|
752
|
+
*/
|
|
753
|
+
static naturalOffset(el) {
|
|
754
|
+
return { left: this.naturalOffsetLeft(el), top: this.naturalOffsetTop(el), width: el?.offsetWidth ?? 0, height: el?.offsetHeight ?? 0 };
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Retrieve the natural bounding box of the provided element (does not takes transformations into account).
|
|
758
|
+
* @param el {HTMLElement} The element.
|
|
759
|
+
*/
|
|
760
|
+
static naturalOffsetRect(el) {
|
|
761
|
+
var rect = this.naturalOffset(el);
|
|
762
|
+
return {
|
|
763
|
+
y: rect.top,
|
|
764
|
+
x: rect.left,
|
|
765
|
+
width: rect.width,
|
|
766
|
+
height: rect.height
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
/** @deprecated */
|
|
770
|
+
static deserializeTransform(obj) {
|
|
771
|
+
return Utils.Css.deserializeTransform(obj);
|
|
772
|
+
}
|
|
773
|
+
static set scrollTop(y) {
|
|
774
|
+
window.scrollTo(Utils.scrollLeft, y);
|
|
775
|
+
}
|
|
776
|
+
static set scrollLeft(x) {
|
|
777
|
+
window.scrollTo(x, Utils.scrollTop);
|
|
778
|
+
}
|
|
779
|
+
static get scrollTop() {
|
|
780
|
+
return window.scrollY /*window.pageYOffset*/ || document.documentElement.scrollTop || document.body.scrollTop || 0;
|
|
781
|
+
}
|
|
782
|
+
static get scrollLeft() {
|
|
783
|
+
return window.scrollX /*window.pageXOffset*/ || document.documentElement.scrollLeft || document.body.scrollLeft || 0;
|
|
784
|
+
}
|
|
785
|
+
static get windowSize() {
|
|
786
|
+
let win = window;
|
|
787
|
+
return {
|
|
788
|
+
width: win.innerWidth || win.document.documentElement.clientWidth || win.document.body.clientWidth || 0,
|
|
789
|
+
height: win.innerHeight || win.document.documentElement.clientHeight || win.document.body.clientHeight || 0
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Adds a callback at the end of a CSS animation/transition for a given element (execute once).
|
|
794
|
+
* @param element The very element that has to trigger the animation/transitionend event.
|
|
795
|
+
* @param callback Then callback.
|
|
796
|
+
* @param timeout Fallback timeout (ms) in case the animation/transitionend event won't fire.
|
|
797
|
+
*/
|
|
798
|
+
static addAnimationEndCallback(element, callback, timeout = 500) {
|
|
799
|
+
const fn = (e) => {
|
|
800
|
+
if (!e || e.target == element) {
|
|
801
|
+
clearTimeout(handle);
|
|
802
|
+
element.removeEventListener('animationend', fn, false);
|
|
803
|
+
element.removeEventListener('transitionend', fn, false);
|
|
804
|
+
callback.apply(this, [element]);
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
element.addEventListener('animationend', fn, false);
|
|
808
|
+
element.addEventListener('transitionend', fn, false);
|
|
809
|
+
// fallback in case animation is missing
|
|
810
|
+
const handle = setTimeout(fn, timeout);
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Promisifies the addAnimationEndCallback function.
|
|
814
|
+
* @param element The very element that has to trigger the animation/transitionend event.
|
|
815
|
+
* @param timeout Fallback timeout (ms) in case the animation/transitionend event won't fire.
|
|
816
|
+
*/
|
|
817
|
+
static waitForAnimationEnd(element, timeout = 500) {
|
|
818
|
+
return new Promise((resolve, _) => {
|
|
819
|
+
Utils.addAnimationEndCallback(element, () => {
|
|
820
|
+
resolve();
|
|
821
|
+
}, timeout);
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Waits until the provided timespan elapses.
|
|
826
|
+
* @param msecs timespan in milliseconds
|
|
827
|
+
*/
|
|
828
|
+
static idle(msecs) {
|
|
829
|
+
return new Promise((resolve, _) => {
|
|
830
|
+
setTimeout(() => resolve(), msecs);
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Increases a callback loop call until cancelation is called.
|
|
835
|
+
* @param callback Callback looped.
|
|
836
|
+
* @param interval First callback call delay in ms (default 500).
|
|
837
|
+
* @param pace Acceleration in ms per loop (default 5).
|
|
838
|
+
* @param min Minimum loop rate in ms (default 20).
|
|
839
|
+
*
|
|
840
|
+
* Use case/example: while keeping a button pressed, the related callback execution increases in frequency.
|
|
841
|
+
*/
|
|
842
|
+
static accelerateCallback(callback, interval = 500, pace = 5, min = 20) {
|
|
843
|
+
let token = { cancel: false }, int = interval, last = pace + min;
|
|
844
|
+
let fn = () => {
|
|
845
|
+
callback(token);
|
|
846
|
+
if (!token.cancel) {
|
|
847
|
+
setTimeout(fn, int);
|
|
848
|
+
if (int < last) {
|
|
849
|
+
int = min;
|
|
850
|
+
}
|
|
851
|
+
else {
|
|
852
|
+
int -= pace;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
};
|
|
856
|
+
fn();
|
|
857
|
+
}
|
|
858
|
+
// #endregion
|
|
859
|
+
// #region other
|
|
860
|
+
static isEmpty(obj) {
|
|
861
|
+
return NullChecker.isEmpty(obj) && !(obj instanceof Node);
|
|
862
|
+
}
|
|
863
|
+
static isNull(obj) {
|
|
864
|
+
return NullChecker.isNull(obj);
|
|
865
|
+
}
|
|
866
|
+
// legacy
|
|
867
|
+
static isArray(val) {
|
|
868
|
+
return Array.isArray(val);
|
|
869
|
+
}
|
|
870
|
+
static isNullOrEmpty(val) {
|
|
871
|
+
return Utils.isNull(val) || Utils.isEmpty(val);
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* It is a `valueOf()` based comparison.
|
|
875
|
+
* @param v1 term 1
|
|
876
|
+
* @param v2 term 2
|
|
877
|
+
*/
|
|
878
|
+
static areSemanticallyEqual(v1, v2) {
|
|
879
|
+
// undefined and null are semantically equal
|
|
880
|
+
v1 ??= null;
|
|
881
|
+
v2 ??= null;
|
|
882
|
+
const sv1 = v1 && v1.valueOf && v1.valueOf();
|
|
883
|
+
const sv2 = v2 && v2.valueOf && v2.valueOf();
|
|
884
|
+
return sv1 === sv2;
|
|
885
|
+
}
|
|
886
|
+
/** Legacy alias for Object.assign(). */
|
|
887
|
+
static { this.extend = Object.assign; }
|
|
888
|
+
static clone(obj) {
|
|
889
|
+
if (obj === undefined)
|
|
890
|
+
return undefined;
|
|
891
|
+
return DeepCloner.clone(obj);
|
|
892
|
+
}
|
|
893
|
+
/** Legacy alias for Promise.resolve(). */
|
|
894
|
+
static fromResult(v) {
|
|
895
|
+
return Promise.resolve(v);
|
|
896
|
+
}
|
|
897
|
+
static fromResultAsync(task) {
|
|
898
|
+
return new Promise(resolve => requestAnimationFrame(() => resolve(task())));
|
|
899
|
+
}
|
|
900
|
+
static readonlyCopy(obj) {
|
|
901
|
+
if (Utils.isNull(obj)) {
|
|
902
|
+
return null;
|
|
903
|
+
}
|
|
904
|
+
if (Utils.isArray(obj)) {
|
|
905
|
+
const output = obj.slice(0);
|
|
906
|
+
Object.freeze(output);
|
|
907
|
+
return output;
|
|
908
|
+
}
|
|
909
|
+
else {
|
|
910
|
+
const output = {};
|
|
911
|
+
for (let name in obj) {
|
|
912
|
+
output[name] = obj[name];
|
|
913
|
+
}
|
|
914
|
+
Object.freeze(output);
|
|
915
|
+
return output;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
//#endregion
|
|
919
|
+
//#region Net
|
|
920
|
+
static { this.URIs = {
|
|
921
|
+
format: function (url, parameters, removeMatchedParameters) {
|
|
922
|
+
// replace segments
|
|
923
|
+
for (let name in parameters || {}) {
|
|
924
|
+
let tmpl = new RegExp("(^|\\/)\\{" + name + "\\??\\}(\\/|$)");
|
|
925
|
+
let arr = tmpl.exec(url);
|
|
926
|
+
if (arr && arr.length) {
|
|
927
|
+
let rpl = new RegExp("\\{" + name + "\\??\\}"), ndx = arr.index;
|
|
928
|
+
url = url.substring(0, ndx) + url.substring(ndx).replace(rpl, encodeURIComponent(parameters[name]));
|
|
929
|
+
if (removeMatchedParameters) {
|
|
930
|
+
delete parameters[name];
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
// clean-up optional
|
|
935
|
+
const optPattern = /(^|\/)\{[\w]+\?\}(\/|$)/;
|
|
936
|
+
var optArr;
|
|
937
|
+
while ((optArr = optPattern.exec(url)) && optArr.length > 0) {
|
|
938
|
+
let wholeMatch = optArr[0];
|
|
939
|
+
url = url.replace(wholeMatch, wholeMatch.endsWith('/') ? "/" : "");
|
|
940
|
+
}
|
|
941
|
+
return url;
|
|
942
|
+
},
|
|
943
|
+
appendQuery: function (url, parameters, removeNullEntries = false, hash = false) {
|
|
944
|
+
const char = hash ? '#' : '?', pattern = hash ? /#/ : /\?/;
|
|
945
|
+
const query = (pattern.test(url) ? '&' : char) +
|
|
946
|
+
Object.keys(parameters)
|
|
947
|
+
.filter(k => !Utils.isNull(parameters[k]) || !removeNullEntries)
|
|
948
|
+
.map(k => encodeURIComponent(k) + '=' + encodeURIComponent(parameters[k] ?? '')).join('&');
|
|
949
|
+
return url + (query.length > 1 ? query : '');
|
|
950
|
+
},
|
|
951
|
+
parseQuery: (query = window.location) => {
|
|
952
|
+
if (typeof query !== 'string') {
|
|
953
|
+
query = query.search;
|
|
954
|
+
}
|
|
955
|
+
const obj = {};
|
|
956
|
+
new URLSearchParams(query).forEach((value, name) => {
|
|
957
|
+
Object.defineProperty(obj, name, { enumerable: true, value });
|
|
958
|
+
});
|
|
959
|
+
return obj;
|
|
960
|
+
},
|
|
961
|
+
hasMandatoryTemplateSegments: function (url) {
|
|
962
|
+
return /(^|\/)\{\.*\}(\/|$)/.test(url);
|
|
963
|
+
},
|
|
964
|
+
parseRoute: function (pathTemplate) {
|
|
965
|
+
let tmpl = pathTemplate;
|
|
966
|
+
const trunks = [];
|
|
967
|
+
if (!Utils.isNullOrEmpty(tmpl)) {
|
|
968
|
+
let res;
|
|
969
|
+
let j = 0;
|
|
970
|
+
while ((res = SEGMENT_PATTERN.exec(tmpl)) != null) {
|
|
971
|
+
const item = res[0];
|
|
972
|
+
const res1 = ROUTE_SEGMENT_PATTERN.exec(item);
|
|
973
|
+
if (!Utils.isNullOrEmpty(res1)) {
|
|
974
|
+
const prop = res1[1];
|
|
975
|
+
trunks.push({ name: prop, optional: item.charAt(item.length - 2) === '?', index: j });
|
|
976
|
+
}
|
|
977
|
+
tmpl = tmpl.substring(res.index + item.length);
|
|
978
|
+
j++;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
return trunks;
|
|
982
|
+
},
|
|
983
|
+
split: function (url) {
|
|
984
|
+
const URL_PATTERN = /^((https?:)?\/\/[^\/]+)?([^\?#]+)(\?[^#]*)?(#[^#]*)?$/;
|
|
985
|
+
const regArr = URL_PATTERN.exec(url);
|
|
986
|
+
if (!regArr /*|| regArr.length <= 3*/) {
|
|
987
|
+
return null;
|
|
988
|
+
}
|
|
989
|
+
return { domain: regArr[2] ?? '', protocol: regArr[1] ?? '', path: regArr[3] ?? '', query: regArr[4] ?? '', hash: regArr[5] ?? '' };
|
|
990
|
+
},
|
|
991
|
+
parseState: function (metadata, url, path, query, hash) {
|
|
992
|
+
let fullPath = url;
|
|
993
|
+
if (Utils.isNullOrEmpty(path)) {
|
|
994
|
+
const split = Utils.URIs.split(url);
|
|
995
|
+
path = split.path;
|
|
996
|
+
if (Utils.isNullOrEmpty(query)) {
|
|
997
|
+
query = split.query ?? '';
|
|
998
|
+
}
|
|
999
|
+
if (Utils.isNullOrEmpty(hash)) {
|
|
1000
|
+
hash = split.hash ?? '';
|
|
1001
|
+
}
|
|
1002
|
+
fullPath = path + query + hash;
|
|
1003
|
+
}
|
|
1004
|
+
const parseHash = (hash) => {
|
|
1005
|
+
const ndx = hash.indexOf('#');
|
|
1006
|
+
if (ndx !== 0) {
|
|
1007
|
+
return null;
|
|
1008
|
+
}
|
|
1009
|
+
return hash.substring(ndx + 1);
|
|
1010
|
+
};
|
|
1011
|
+
const parseQuery = (search) => {
|
|
1012
|
+
const ndx = search.indexOf('?');
|
|
1013
|
+
if (ndx === 0) {
|
|
1014
|
+
return Utils.URIs.parseQuery(search.substring(ndx + 1));
|
|
1015
|
+
}
|
|
1016
|
+
return {};
|
|
1017
|
+
};
|
|
1018
|
+
let obj = {
|
|
1019
|
+
$path: fullPath,
|
|
1020
|
+
$pathname: path,
|
|
1021
|
+
$querystring: query.length > 0 ? query.substring(1) : query,
|
|
1022
|
+
$query: parseQuery(query),
|
|
1023
|
+
$hash: parseHash(hash)
|
|
1024
|
+
}, i = 0;
|
|
1025
|
+
const tmpl = metadata.slice();
|
|
1026
|
+
if (!Utils.isNullOrEmpty(tmpl)) {
|
|
1027
|
+
let res;
|
|
1028
|
+
while ((res = SEGMENT_PATTERN.exec(path)) != null) {
|
|
1029
|
+
const v = res[0];
|
|
1030
|
+
const tmplIndex = tmpl.findIndex(t => t.index === i);
|
|
1031
|
+
if (tmplIndex >= 0) {
|
|
1032
|
+
const tmplItem = tmpl[tmplIndex];
|
|
1033
|
+
const { name: prop } = tmplItem;
|
|
1034
|
+
Object.defineProperty(obj, prop, { enumerable: true, value: v.substring(1) });
|
|
1035
|
+
// remove used value
|
|
1036
|
+
tmpl.splice(tmplIndex, 1);
|
|
1037
|
+
}
|
|
1038
|
+
i++;
|
|
1039
|
+
path = path.substring(res.index + v.length);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
for (let k = 0; k < tmpl.length; k++) {
|
|
1043
|
+
if (!tmpl[k].optional) {
|
|
1044
|
+
throw `Must provide "${tmpl[k].name}" route value.`;
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
return obj;
|
|
1048
|
+
}
|
|
1049
|
+
}; }
|
|
1050
|
+
/**
|
|
1051
|
+
* Ensures the proper parsing of Web API results, including deprecated/legacy Pacem wrapped results.
|
|
1052
|
+
*/
|
|
1053
|
+
static getApiResult(json) {
|
|
1054
|
+
if (typeof json === 'object'
|
|
1055
|
+
&& !Utils.isNull(json)
|
|
1056
|
+
&& json.hasOwnProperty('success')) {
|
|
1057
|
+
switch (json.success) {
|
|
1058
|
+
case true:
|
|
1059
|
+
if (json.hasOwnProperty('result')) {
|
|
1060
|
+
return json.result;
|
|
1061
|
+
}
|
|
1062
|
+
break;
|
|
1063
|
+
case false:
|
|
1064
|
+
if (json.hasOwnProperty('error')) {
|
|
1065
|
+
return null;
|
|
1066
|
+
}
|
|
1067
|
+
break;
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
return json;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
// }
|