@iobroker/json-config 7.3.0 → 7.3.2
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/build/JsonConfig.js +1 -2
- package/build/JsonConfig.js.map +1 -1
- package/build/JsonConfigComponent/ChipInput.js +1 -1
- package/build/JsonConfigComponent/ChipInput.js.map +1 -1
- package/build/JsonConfigComponent/ConfigAccordion.js +1 -2
- package/build/JsonConfigComponent/ConfigAccordion.js.map +1 -1
- package/build/JsonConfigComponent/ConfigFileSelector.js +1 -1
- package/build/JsonConfigComponent/ConfigFileSelector.js.map +1 -1
- package/build/JsonConfigComponent/ConfigGeneric.js +1 -1
- package/build/JsonConfigComponent/ConfigGeneric.js.map +1 -1
- package/build/JsonConfigComponent/ConfigPanel.js +1 -1
- package/build/JsonConfigComponent/ConfigPanel.js.map +1 -1
- package/build/JsonConfigComponent/ConfigPassword.js +1 -1
- package/build/JsonConfigComponent/ConfigPassword.js.map +1 -1
- package/build/JsonConfigComponent/ConfigStaticDivider.js +1 -1
- package/build/JsonConfigComponent/ConfigStaticDivider.js.map +1 -1
- package/package.json +3 -3
- package/build/Utils.d.ts +0 -377
- package/build/Utils.js +0 -1642
- package/build/Utils.js.map +0 -1
package/build/Utils.js
DELETED
|
@@ -1,1642 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright 2018-2023 Denis Haev <dogafox@gmail.com>
|
|
3
|
-
*
|
|
4
|
-
* MIT License
|
|
5
|
-
*
|
|
6
|
-
* */
|
|
7
|
-
import React from 'react';
|
|
8
|
-
import { Utils as _Utils, I18n } from '@iobroker/adapter-react-v5';
|
|
9
|
-
const NAMESPACE = 'material';
|
|
10
|
-
const days = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
|
|
11
|
-
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
12
|
-
const QUALITY_BITS = {
|
|
13
|
-
0x00: '0x00 - good',
|
|
14
|
-
0x01: '0x01 - general problem',
|
|
15
|
-
0x02: '0x02 - no connection problem',
|
|
16
|
-
0x10: '0x10 - substitute value from controller',
|
|
17
|
-
0x20: '0x20 - substitute initial value',
|
|
18
|
-
0x40: '0x40 - substitute value from device or instance',
|
|
19
|
-
0x80: '0x80 - substitute value from sensor',
|
|
20
|
-
0x11: '0x11 - general problem by instance',
|
|
21
|
-
0x41: '0x41 - general problem by device',
|
|
22
|
-
0x81: '0x81 - general problem by sensor',
|
|
23
|
-
0x12: '0x12 - instance not connected',
|
|
24
|
-
0x42: '0x42 - device not connected',
|
|
25
|
-
0x82: '0x82 - sensor not connected',
|
|
26
|
-
0x44: '0x44 - device reports error',
|
|
27
|
-
0x84: '0x84 - sensor reports error',
|
|
28
|
-
};
|
|
29
|
-
const SIGNATURES = {
|
|
30
|
-
JVBERi0: 'pdf',
|
|
31
|
-
R0lGODdh: 'gif',
|
|
32
|
-
R0lGODlh: 'gif',
|
|
33
|
-
iVBORw0KGgo: 'png',
|
|
34
|
-
'/9j/': 'jpg',
|
|
35
|
-
PHN2Zw: 'svg',
|
|
36
|
-
Qk1: 'bmp',
|
|
37
|
-
AAABAA: 'ico', // 00 00 01 00 according to https://en.wikipedia.org/wiki/List_of_file_signatures
|
|
38
|
-
};
|
|
39
|
-
class Utils {
|
|
40
|
-
static namespace = NAMESPACE;
|
|
41
|
-
static INSTANCES = 'instances';
|
|
42
|
-
static dateFormat = ['DD', 'MM'];
|
|
43
|
-
static FORBIDDEN_CHARS = /[^._\-/ :!#$%&()+=@^{}|~\p{Ll}\p{Lu}\p{Nd}]+/gu;
|
|
44
|
-
/**
|
|
45
|
-
* Capitalize words.
|
|
46
|
-
* @param {string | undefined} name
|
|
47
|
-
* @returns {string}
|
|
48
|
-
*/
|
|
49
|
-
static CapitalWords(name) {
|
|
50
|
-
return (name || '')
|
|
51
|
-
.split(/[\s_]/)
|
|
52
|
-
.filter(item => item)
|
|
53
|
-
.map(word => (word ? word[0].toUpperCase() + word.substring(1).toLowerCase() : ''))
|
|
54
|
-
.join(' ');
|
|
55
|
-
}
|
|
56
|
-
static formatSeconds(seconds) {
|
|
57
|
-
const days_ = Math.floor(seconds / (3600 * 24));
|
|
58
|
-
seconds %= 3600 * 24;
|
|
59
|
-
let hours = Math.floor(seconds / 3600);
|
|
60
|
-
if (hours < 10) {
|
|
61
|
-
hours = `0${hours}`;
|
|
62
|
-
}
|
|
63
|
-
seconds %= 3600;
|
|
64
|
-
let minutes = Math.floor(seconds / 60);
|
|
65
|
-
if (minutes < 10) {
|
|
66
|
-
minutes = `0${minutes}`;
|
|
67
|
-
}
|
|
68
|
-
seconds %= 60;
|
|
69
|
-
seconds = Math.floor(seconds);
|
|
70
|
-
if (seconds < 10) {
|
|
71
|
-
seconds = `0${seconds}`;
|
|
72
|
-
}
|
|
73
|
-
let text = '';
|
|
74
|
-
if (days_) {
|
|
75
|
-
text += `${days_} ${I18n.t('ra_daysShortText')} `;
|
|
76
|
-
}
|
|
77
|
-
text += `${hours}:${minutes}:${seconds}`;
|
|
78
|
-
return text;
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Get the name of the object by id from the name or description.
|
|
82
|
-
* @param {Record<string, ioBroker.Object>} objects
|
|
83
|
-
* @param {string} id
|
|
84
|
-
* @param {{ name: any; } | ioBroker.Languages | null} settings
|
|
85
|
-
* @param {{ language?: ioBroker.Languages; }} options
|
|
86
|
-
* @param {boolean} [isDesc] Set to true to get the description.
|
|
87
|
-
* @returns {string}
|
|
88
|
-
*/
|
|
89
|
-
static getObjectName(objects, id, settings, options, isDesc) {
|
|
90
|
-
const item = objects[id];
|
|
91
|
-
let text;
|
|
92
|
-
const attr = isDesc ? 'desc' : 'name';
|
|
93
|
-
if (typeof settings === 'string' && !options) {
|
|
94
|
-
options = { language: settings };
|
|
95
|
-
settings = null;
|
|
96
|
-
}
|
|
97
|
-
options = options || {};
|
|
98
|
-
if (!options.language) {
|
|
99
|
-
options.language =
|
|
100
|
-
(objects['system.config'] &&
|
|
101
|
-
objects['system.config'].common &&
|
|
102
|
-
objects['system.config'].common.language) ||
|
|
103
|
-
window.sysLang ||
|
|
104
|
-
'en';
|
|
105
|
-
}
|
|
106
|
-
if (settings && settings.name) {
|
|
107
|
-
text = settings.name;
|
|
108
|
-
if (typeof text === 'object') {
|
|
109
|
-
text = text[options.language] || text.en;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
else if (item && item.common && item.common[attr]) {
|
|
113
|
-
text = item.common[attr];
|
|
114
|
-
if (attr !== 'desc' && !text && item.common.desc) {
|
|
115
|
-
text = item.common.desc;
|
|
116
|
-
}
|
|
117
|
-
if (typeof text === 'object') {
|
|
118
|
-
text = text[options.language] || text.en || text.de || text.ru || '';
|
|
119
|
-
}
|
|
120
|
-
text = (text || '').toString().replace(/[_.]/g, ' ');
|
|
121
|
-
if (text === text.toUpperCase()) {
|
|
122
|
-
text = text[0] + text.substring(1).toLowerCase();
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
else {
|
|
126
|
-
const pos = id.lastIndexOf('.');
|
|
127
|
-
text = id.substring(pos + 1).replace(/[_.]/g, ' ');
|
|
128
|
-
text = Utils.CapitalWords(text);
|
|
129
|
-
}
|
|
130
|
-
return text.trim();
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* Get the name of the object from the name or description.
|
|
134
|
-
* @param {ioBroker.PartialObject} obj
|
|
135
|
-
* @param {{ name: any; } | ioBroker.Languages | null } settings or language
|
|
136
|
-
* @param {{ language?: ioBroker.Languages; } } options
|
|
137
|
-
* @param {boolean} [isDesc] Set to true to get the description.
|
|
138
|
-
* @param {boolean} [noTrim] Allow to use spaces in name (by edit)
|
|
139
|
-
* @returns {string}
|
|
140
|
-
*/
|
|
141
|
-
static getObjectNameFromObj(obj, settings, options, isDesc, noTrim) {
|
|
142
|
-
const item = obj;
|
|
143
|
-
let text = (obj && obj._id) || '';
|
|
144
|
-
const attr = isDesc ? 'desc' : 'name';
|
|
145
|
-
if (typeof settings === 'string' && !options) {
|
|
146
|
-
options = { language: settings };
|
|
147
|
-
settings = null;
|
|
148
|
-
}
|
|
149
|
-
options = options || {};
|
|
150
|
-
if (settings && settings.name) {
|
|
151
|
-
text = settings.name;
|
|
152
|
-
if (typeof text === 'object') {
|
|
153
|
-
text = text[options.language] || text.en;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
else if (item && item.common && item.common[attr]) {
|
|
157
|
-
text = item.common[attr];
|
|
158
|
-
if (attr !== 'desc' && !text && item.common.desc) {
|
|
159
|
-
text = item.common.desc;
|
|
160
|
-
}
|
|
161
|
-
if (typeof text === 'object') {
|
|
162
|
-
text = text[options.language] || text.en;
|
|
163
|
-
}
|
|
164
|
-
text = (text || '').toString().replace(/[_.]/g, ' ');
|
|
165
|
-
if (text === text.toUpperCase()) {
|
|
166
|
-
text = text[0] + text.substring(1).toLowerCase();
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
return noTrim ? text : text.trim();
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* @param {ioBroker.PartialObject | ioBroker.ObjectCommon} obj
|
|
173
|
-
* @param {string} forEnumId
|
|
174
|
-
* @param {{ user: string; }} options
|
|
175
|
-
* @returns {string | null}
|
|
176
|
-
*/
|
|
177
|
-
static getSettingsOrder(obj, forEnumId, options) {
|
|
178
|
-
if (obj && Object.prototype.hasOwnProperty.call(obj, 'common')) {
|
|
179
|
-
obj = obj.common;
|
|
180
|
-
}
|
|
181
|
-
let settings;
|
|
182
|
-
if (obj && obj.custom) {
|
|
183
|
-
settings = (obj.custom || {})[NAMESPACE];
|
|
184
|
-
const user = options.user || 'admin';
|
|
185
|
-
if (settings && settings[user]) {
|
|
186
|
-
if (forEnumId) {
|
|
187
|
-
if (settings[user].subOrder && settings[user].subOrder[forEnumId]) {
|
|
188
|
-
return JSON.parse(JSON.stringify(settings[user].subOrder[forEnumId]));
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
else if (settings[user].order) {
|
|
192
|
-
return JSON.parse(JSON.stringify(settings[user].order));
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
return null;
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* @param {ioBroker.PartialObject | ioBroker.ObjectCommon} obj
|
|
200
|
-
* @param {string} forEnumId
|
|
201
|
-
* @param {{ user: string; }} options
|
|
202
|
-
*/
|
|
203
|
-
static getSettingsCustomURLs(obj, forEnumId, options) {
|
|
204
|
-
if (obj && Object.prototype.hasOwnProperty.call(obj, 'common')) {
|
|
205
|
-
obj = obj.common;
|
|
206
|
-
}
|
|
207
|
-
let settings;
|
|
208
|
-
if (obj && obj.custom) {
|
|
209
|
-
settings = (obj.custom || {})[NAMESPACE];
|
|
210
|
-
const user = options.user || 'admin';
|
|
211
|
-
if (settings && settings[user]) {
|
|
212
|
-
if (forEnumId) {
|
|
213
|
-
if (settings[user].subURLs && settings[user].subURLs[forEnumId]) {
|
|
214
|
-
return JSON.parse(JSON.stringify(settings[user].subURLs[forEnumId]));
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
else if (settings[user].URLs) {
|
|
218
|
-
return JSON.parse(JSON.stringify(settings[user].URLs));
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
return null;
|
|
223
|
-
}
|
|
224
|
-
/**
|
|
225
|
-
* Reorder the array items in list between source and dest.
|
|
226
|
-
* @param {Iterable<any> | ArrayLike<any>} list
|
|
227
|
-
* @param {number} source
|
|
228
|
-
* @param {number} dest
|
|
229
|
-
*/
|
|
230
|
-
static reorder(list, source, dest) {
|
|
231
|
-
const result = Array.from(list);
|
|
232
|
-
const [removed] = result.splice(source, 1);
|
|
233
|
-
result.splice(dest, 0, removed);
|
|
234
|
-
return result;
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* @param {any} obj
|
|
238
|
-
* @param {{ id: any; user: any; name: any; icon: any; color: any; language: ioBroker.Languages; }} options
|
|
239
|
-
* @param {boolean} [defaultEnabling]
|
|
240
|
-
*/
|
|
241
|
-
static getSettings(obj, options, defaultEnabling) {
|
|
242
|
-
let settings;
|
|
243
|
-
const id = (obj && obj._id) || (options && options.id);
|
|
244
|
-
if (obj && Object.prototype.hasOwnProperty.call(obj, 'common')) {
|
|
245
|
-
obj = obj.common;
|
|
246
|
-
}
|
|
247
|
-
if (obj?.custom) {
|
|
248
|
-
settings = obj.custom;
|
|
249
|
-
settings =
|
|
250
|
-
settings[NAMESPACE] && settings[NAMESPACE][options.user || 'admin']
|
|
251
|
-
? JSON.parse(JSON.stringify(settings[NAMESPACE][options.user || 'admin']))
|
|
252
|
-
: { enabled: true };
|
|
253
|
-
}
|
|
254
|
-
else {
|
|
255
|
-
settings = { enabled: defaultEnabling === undefined ? true : defaultEnabling, useCustom: false };
|
|
256
|
-
}
|
|
257
|
-
if (!Object.prototype.hasOwnProperty.call(settings, 'enabled')) {
|
|
258
|
-
settings.enabled = defaultEnabling === undefined ? true : defaultEnabling;
|
|
259
|
-
}
|
|
260
|
-
// if (false && settings.useCommon) {
|
|
261
|
-
// if (obj.color) settings.color = obj.color;
|
|
262
|
-
// if (obj.icon) settings.icon = obj.icon;
|
|
263
|
-
// if (obj.name) settings.name = obj.name;
|
|
264
|
-
// } else {
|
|
265
|
-
if (options) {
|
|
266
|
-
if (!settings.name && options.name)
|
|
267
|
-
settings.name = options.name;
|
|
268
|
-
if (!settings.icon && options.icon)
|
|
269
|
-
settings.icon = options.icon;
|
|
270
|
-
if (!settings.color && options.color)
|
|
271
|
-
settings.color = options.color;
|
|
272
|
-
}
|
|
273
|
-
if (obj) {
|
|
274
|
-
if (!settings.color && obj.color)
|
|
275
|
-
settings.color = obj.color;
|
|
276
|
-
if (!settings.icon && obj.icon)
|
|
277
|
-
settings.icon = obj.icon;
|
|
278
|
-
if (!settings.name && obj.name)
|
|
279
|
-
settings.name = obj.name;
|
|
280
|
-
}
|
|
281
|
-
// }
|
|
282
|
-
if (typeof settings.name === 'object') {
|
|
283
|
-
settings.name = settings.name[options.language] || settings.name.en;
|
|
284
|
-
settings.name = (settings.name || '').toString().replace(/_/g, ' ');
|
|
285
|
-
if (settings.name === settings.name.toUpperCase()) {
|
|
286
|
-
settings.name = settings.name[0] + settings.name.substring(1).toLowerCase();
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
if (!settings.name && id) {
|
|
290
|
-
const pos = id.lastIndexOf('.');
|
|
291
|
-
settings.name = id.substring(pos + 1).replace(/[_.]/g, ' ');
|
|
292
|
-
settings.name = (settings.name || '').toString().replace(/_/g, ' ');
|
|
293
|
-
settings.name = Utils.CapitalWords(settings.name);
|
|
294
|
-
}
|
|
295
|
-
return settings;
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
|
-
* @param {any} obj
|
|
299
|
-
* @param {any} settings
|
|
300
|
-
* @param {{ user: any; language: ioBroker.Languages; }} options
|
|
301
|
-
*/
|
|
302
|
-
static setSettings(obj, settings, options) {
|
|
303
|
-
if (obj) {
|
|
304
|
-
obj.common = obj.common || {};
|
|
305
|
-
obj.common.custom = obj.common.custom || {};
|
|
306
|
-
obj.common.custom[NAMESPACE] = obj.common.custom[NAMESPACE] || {};
|
|
307
|
-
obj.common.custom[NAMESPACE][options.user || 'admin'] = settings;
|
|
308
|
-
const s = obj.common.custom[NAMESPACE][options.user || 'admin'];
|
|
309
|
-
if (s.useCommon) {
|
|
310
|
-
if (s.color !== undefined) {
|
|
311
|
-
obj.common.color = s.color;
|
|
312
|
-
delete s.color;
|
|
313
|
-
}
|
|
314
|
-
if (s.icon !== undefined) {
|
|
315
|
-
obj.common.icon = s.icon;
|
|
316
|
-
delete s.icon;
|
|
317
|
-
}
|
|
318
|
-
if (s.name !== undefined) {
|
|
319
|
-
if (typeof obj.common.name !== 'object') {
|
|
320
|
-
obj.common.name = {};
|
|
321
|
-
obj.common.name[options.language] = s.name;
|
|
322
|
-
}
|
|
323
|
-
else {
|
|
324
|
-
obj.common.name[options.language] = s.name;
|
|
325
|
-
}
|
|
326
|
-
delete s.name;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
return true;
|
|
330
|
-
}
|
|
331
|
-
return false;
|
|
332
|
-
}
|
|
333
|
-
/**
|
|
334
|
-
* Get the icon for the given settings.
|
|
335
|
-
* @param {{ icon: string | undefined; name: string | undefined; prefix: string | undefined}} settings
|
|
336
|
-
* @param {any} style
|
|
337
|
-
* @returns {JSX.Element | null}
|
|
338
|
-
*/
|
|
339
|
-
static getIcon(settings, style) {
|
|
340
|
-
if (settings && settings.icon) {
|
|
341
|
-
// If UTF-8 icon
|
|
342
|
-
if (settings.icon.length <= 2) {
|
|
343
|
-
return React.createElement("span", { style: style || {} }, settings.icon);
|
|
344
|
-
}
|
|
345
|
-
if (settings.icon.startsWith('data:image')) {
|
|
346
|
-
return (React.createElement("img", { alt: settings.name, src: settings.icon, style: style || {} }));
|
|
347
|
-
}
|
|
348
|
-
// maybe later some changes for a second type
|
|
349
|
-
return (React.createElement("img", { alt: settings.name, src: (settings.prefix || '') + settings.icon, style: style || {} }));
|
|
350
|
-
}
|
|
351
|
-
return null;
|
|
352
|
-
}
|
|
353
|
-
/**
|
|
354
|
-
* Get the icon for the given object.
|
|
355
|
-
* @param {string} id
|
|
356
|
-
* @param {{ common: { icon: any; }; }} obj
|
|
357
|
-
* @returns {string | null}
|
|
358
|
-
*/
|
|
359
|
-
static getObjectIcon(id, obj) {
|
|
360
|
-
// If id is Object
|
|
361
|
-
if (typeof id === 'object') {
|
|
362
|
-
obj = id;
|
|
363
|
-
id = obj._id;
|
|
364
|
-
}
|
|
365
|
-
if (obj && obj.common && obj.common.icon) {
|
|
366
|
-
let icon = obj.common.icon;
|
|
367
|
-
// If UTF-8 icon
|
|
368
|
-
if (typeof icon === 'string' && icon.length <= 2) {
|
|
369
|
-
return icon;
|
|
370
|
-
}
|
|
371
|
-
if (icon.startsWith('data:image')) {
|
|
372
|
-
return icon;
|
|
373
|
-
}
|
|
374
|
-
const parts = id.split('.');
|
|
375
|
-
if (parts[0] === 'system') {
|
|
376
|
-
icon = `adapter/${parts[2]}${icon.startsWith('/') ? '' : '/'}${icon}`;
|
|
377
|
-
}
|
|
378
|
-
else {
|
|
379
|
-
icon = `adapter/${parts[0]}${icon.startsWith('/') ? '' : '/'}${icon}`;
|
|
380
|
-
}
|
|
381
|
-
if (window.location.pathname.match(/adapter\/[^/]+\/[^/]+\.html/)) {
|
|
382
|
-
icon = `../../${icon}`;
|
|
383
|
-
}
|
|
384
|
-
else if (window.location.pathname.match(/material\/[.\d]+/)) {
|
|
385
|
-
icon = `../../${icon}`;
|
|
386
|
-
}
|
|
387
|
-
else if (window.location.pathname.match(/material\//)) {
|
|
388
|
-
icon = `../${icon}`;
|
|
389
|
-
}
|
|
390
|
-
return icon;
|
|
391
|
-
}
|
|
392
|
-
return null;
|
|
393
|
-
}
|
|
394
|
-
/**
|
|
395
|
-
* Splits CamelCase into words.
|
|
396
|
-
* @param {string | undefined} text
|
|
397
|
-
* @returns {string}
|
|
398
|
-
*/
|
|
399
|
-
static splitCamelCase(text) {
|
|
400
|
-
// if (false && text !== text.toUpperCase()) {
|
|
401
|
-
// const words = text.split(/\s+/);
|
|
402
|
-
// for (let i = 0; i < words.length; i++) {
|
|
403
|
-
// const word = words[i];
|
|
404
|
-
// if (word.toLowerCase() !== word && word.toUpperCase() !== word) {
|
|
405
|
-
// let z = 0;
|
|
406
|
-
// const ww = [];
|
|
407
|
-
// let start = 0;
|
|
408
|
-
// while (z < word.length) {
|
|
409
|
-
// if (word[z].match(/[A-ZÜÄÖА-Я]/)) {
|
|
410
|
-
// ww.push(word.substring(start, z));
|
|
411
|
-
// start = z;
|
|
412
|
-
// }
|
|
413
|
-
// z++;
|
|
414
|
-
// }
|
|
415
|
-
// if (start !== z) {
|
|
416
|
-
// ww.push(word.substring(start, z));
|
|
417
|
-
// }
|
|
418
|
-
// for (let k = 0; k < ww.length; k++) {
|
|
419
|
-
// words.splice(i + k, 0, ww[k]);
|
|
420
|
-
// }
|
|
421
|
-
// i += ww.length;
|
|
422
|
-
// }
|
|
423
|
-
// }
|
|
424
|
-
//
|
|
425
|
-
// return words.map(w => {
|
|
426
|
-
// w = w.trim();
|
|
427
|
-
// if (w) {
|
|
428
|
-
// return w[0].toUpperCase() + w.substring(1).toLowerCase();
|
|
429
|
-
// }
|
|
430
|
-
// return '';
|
|
431
|
-
// }).join(' ');
|
|
432
|
-
// }
|
|
433
|
-
return Utils.CapitalWords(text);
|
|
434
|
-
}
|
|
435
|
-
/**
|
|
436
|
-
* Check if the given color is bright.
|
|
437
|
-
* https://stackoverflow.com/questions/35969656/how-can-i-generate-the-opposite-color-according-to-current-color
|
|
438
|
-
* @param {string | null | undefined} color
|
|
439
|
-
* @param {boolean} [defaultValue]
|
|
440
|
-
* @returns {boolean}
|
|
441
|
-
*/
|
|
442
|
-
static isUseBright(color, defaultValue) {
|
|
443
|
-
if (color === null || color === undefined || color === '') {
|
|
444
|
-
return defaultValue === undefined ? true : defaultValue;
|
|
445
|
-
}
|
|
446
|
-
color = color.toString();
|
|
447
|
-
if (color.startsWith('#')) {
|
|
448
|
-
color = color.slice(1);
|
|
449
|
-
}
|
|
450
|
-
let r;
|
|
451
|
-
let g;
|
|
452
|
-
let b;
|
|
453
|
-
const rgb = color.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i);
|
|
454
|
-
if (rgb && rgb.length === 4) {
|
|
455
|
-
r = parseInt(rgb[1], 10);
|
|
456
|
-
g = parseInt(rgb[2], 10);
|
|
457
|
-
b = parseInt(rgb[3], 10);
|
|
458
|
-
}
|
|
459
|
-
else {
|
|
460
|
-
// convert 3-digit hex to 6-digits.
|
|
461
|
-
if (color.length === 3) {
|
|
462
|
-
color = color[0] + color[0] + color[1] + color[1] + color[2] + color[2];
|
|
463
|
-
}
|
|
464
|
-
// remove alfa channel
|
|
465
|
-
if (color.length === 8) {
|
|
466
|
-
color = color.substring(0, 6);
|
|
467
|
-
}
|
|
468
|
-
else if (color.length !== 6) {
|
|
469
|
-
return false;
|
|
470
|
-
}
|
|
471
|
-
r = parseInt(color.slice(0, 2), 16);
|
|
472
|
-
g = parseInt(color.slice(2, 4), 16);
|
|
473
|
-
b = parseInt(color.slice(4, 6), 16);
|
|
474
|
-
}
|
|
475
|
-
// http://stackoverflow.com/a/3943023/112731
|
|
476
|
-
return r * 0.299 + g * 0.587 + b * 0.114 <= 186;
|
|
477
|
-
}
|
|
478
|
-
/**
|
|
479
|
-
* Get the time string in the format 00:00.
|
|
480
|
-
* @param {string | number} seconds
|
|
481
|
-
*/
|
|
482
|
-
static getTimeString(seconds) {
|
|
483
|
-
seconds = parseFloat(seconds);
|
|
484
|
-
if (Number.isNaN(seconds)) {
|
|
485
|
-
return '--:--';
|
|
486
|
-
}
|
|
487
|
-
const hours = Math.floor(seconds / 3600);
|
|
488
|
-
let minutes = Math.floor((seconds % 3600) / 60);
|
|
489
|
-
let secs = seconds % 60;
|
|
490
|
-
if (hours) {
|
|
491
|
-
if (minutes < 10) {
|
|
492
|
-
minutes = `0${minutes}`;
|
|
493
|
-
}
|
|
494
|
-
if (secs < 10) {
|
|
495
|
-
secs = `0${secs}`;
|
|
496
|
-
}
|
|
497
|
-
return `${hours}:${minutes}:${secs}`;
|
|
498
|
-
}
|
|
499
|
-
if (secs < 10) {
|
|
500
|
-
secs = `0${secs}`;
|
|
501
|
-
}
|
|
502
|
-
return `${minutes}:${secs}`;
|
|
503
|
-
}
|
|
504
|
-
/**
|
|
505
|
-
* Gets the wind direction with the given angle (degrees).
|
|
506
|
-
* @param {number} angle in degrees.
|
|
507
|
-
* @returns {string | undefined}
|
|
508
|
-
*/
|
|
509
|
-
static getWindDirection(angle) {
|
|
510
|
-
if (angle >= 0 && angle < 11.25) {
|
|
511
|
-
return 'N';
|
|
512
|
-
}
|
|
513
|
-
if (angle >= 11.25 && angle < 33.75) {
|
|
514
|
-
return 'NNE';
|
|
515
|
-
}
|
|
516
|
-
if (angle >= 33.75 && angle < 56.25) {
|
|
517
|
-
return 'NE';
|
|
518
|
-
}
|
|
519
|
-
if (angle >= 56.25 && angle < 78.75) {
|
|
520
|
-
return 'ENE';
|
|
521
|
-
}
|
|
522
|
-
if (angle >= 78.75 && angle < 101.25) {
|
|
523
|
-
return 'E';
|
|
524
|
-
}
|
|
525
|
-
if (angle >= 101.25 && angle < 123.75) {
|
|
526
|
-
return 'ESE';
|
|
527
|
-
}
|
|
528
|
-
if (angle >= 123.75 && angle < 146.25) {
|
|
529
|
-
return 'SE';
|
|
530
|
-
}
|
|
531
|
-
if (angle >= 146.25 && angle < 168.75) {
|
|
532
|
-
return 'SSE';
|
|
533
|
-
}
|
|
534
|
-
if (angle >= 168.75 && angle < 191.25) {
|
|
535
|
-
return 'S';
|
|
536
|
-
}
|
|
537
|
-
if (angle >= 191.25 && angle < 213.75) {
|
|
538
|
-
return 'SSW';
|
|
539
|
-
}
|
|
540
|
-
if (angle >= 213.75 && angle < 236.25) {
|
|
541
|
-
return 'SW';
|
|
542
|
-
}
|
|
543
|
-
if (angle >= 236.25 && angle < 258.75) {
|
|
544
|
-
return 'WSW';
|
|
545
|
-
}
|
|
546
|
-
if (angle >= 258.75 && angle < 281.25) {
|
|
547
|
-
return 'W';
|
|
548
|
-
}
|
|
549
|
-
if (angle >= 281.25 && angle < 303.75) {
|
|
550
|
-
return 'WNW';
|
|
551
|
-
}
|
|
552
|
-
if (angle >= 303.75 && angle < 326.25) {
|
|
553
|
-
return 'NW';
|
|
554
|
-
}
|
|
555
|
-
if (angle >= 326.25 && angle < 348.75) {
|
|
556
|
-
return 'NNW';
|
|
557
|
-
}
|
|
558
|
-
// if (angle >= 348.75) {
|
|
559
|
-
return 'N';
|
|
560
|
-
}
|
|
561
|
-
/**
|
|
562
|
-
* Pad the given number with a zero if it's not 2 digits long.
|
|
563
|
-
* @param {string | number} num
|
|
564
|
-
*/
|
|
565
|
-
static padding(num) {
|
|
566
|
-
if (typeof num === 'string') {
|
|
567
|
-
if (num.length < 2) {
|
|
568
|
-
return `0${num}`;
|
|
569
|
-
}
|
|
570
|
-
return num;
|
|
571
|
-
}
|
|
572
|
-
if (num < 10) {
|
|
573
|
-
return `0${num}`;
|
|
574
|
-
}
|
|
575
|
-
return num;
|
|
576
|
-
}
|
|
577
|
-
/**
|
|
578
|
-
* Sets the date format.
|
|
579
|
-
* @param {string} format
|
|
580
|
-
*/
|
|
581
|
-
static setDataFormat(format) {
|
|
582
|
-
if (format) {
|
|
583
|
-
Utils.dateFormat = format.toUpperCase().split(/[.-/]/);
|
|
584
|
-
Utils.dateFormat.splice(Utils.dateFormat.indexOf('YYYY'), 1);
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
/**
|
|
588
|
-
* Converts the date to a string.
|
|
589
|
-
* @param {string | number | Date} now
|
|
590
|
-
* @returns {string}
|
|
591
|
-
*/
|
|
592
|
-
static date2string(now) {
|
|
593
|
-
if (typeof now === 'string') {
|
|
594
|
-
now = now.trim();
|
|
595
|
-
if (!now)
|
|
596
|
-
return '';
|
|
597
|
-
// only letters
|
|
598
|
-
if (now.match(/^[\w\s]+$/)) {
|
|
599
|
-
// Day of the week
|
|
600
|
-
return now;
|
|
601
|
-
}
|
|
602
|
-
const m = now.match(/(\d{1,4})[-./](\d{1,2})[-./](\d{1,4})/);
|
|
603
|
-
if (m) {
|
|
604
|
-
const a = [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10)];
|
|
605
|
-
const year = a.find(y => y > 31);
|
|
606
|
-
a.splice(a.indexOf(year), 1);
|
|
607
|
-
const day = a.find(mm => mm > 12);
|
|
608
|
-
if (day) {
|
|
609
|
-
a.splice(a.indexOf(day), 1);
|
|
610
|
-
now = new Date(year, a[0] - 1, day);
|
|
611
|
-
}
|
|
612
|
-
else if (Utils.dateFormat[0][0] === 'M' && Utils.dateFormat[1][0] === 'D') {
|
|
613
|
-
// MM DD
|
|
614
|
-
now = new Date(year, a[0] - 1, a[1]);
|
|
615
|
-
if (Math.abs(now.getTime - Date.now()) > 3600000 * 24 * 10) {
|
|
616
|
-
now = new Date(year, a[1] - 1, a[0]);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
else if (Utils.dateFormat[0][0] === 'D' && Utils.dateFormat[1][0] === 'M') {
|
|
620
|
-
// DD MM
|
|
621
|
-
now = new Date(year, a[1] - 1, a[0]);
|
|
622
|
-
if (Math.abs(now.getTime - Date.now()) > 3600000 * 24 * 10) {
|
|
623
|
-
now = new Date(year, a[0] - 1, a[1]);
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
else {
|
|
627
|
-
now = new Date(now);
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
else {
|
|
631
|
-
now = new Date(now);
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
else {
|
|
635
|
-
now = new Date(now);
|
|
636
|
-
}
|
|
637
|
-
let date = I18n.t(`ra_dow_${days[now.getDay()]}`).replace('ra_dow_', '');
|
|
638
|
-
date += `. ${now.getDate()} ${I18n.t(`ra_month_${months[now.getMonth()]}`).replace('ra_month_', '')}`;
|
|
639
|
-
return date;
|
|
640
|
-
}
|
|
641
|
-
/**
|
|
642
|
-
* Render a text as a link.
|
|
643
|
-
* @param {string} text
|
|
644
|
-
* @returns {string | JSX.Element[]}
|
|
645
|
-
*/
|
|
646
|
-
static renderTextWithA(text) {
|
|
647
|
-
let m = text.match(/<a [^<]+<\/a>|<br\/?>|<b>[^<]+<\/b>|<i>[^<]+<\/i>/);
|
|
648
|
-
if (m) {
|
|
649
|
-
const result = [];
|
|
650
|
-
let key = 1;
|
|
651
|
-
do {
|
|
652
|
-
const start = text.substring(0, m.index);
|
|
653
|
-
text = text.substring(m.index + m[0].length);
|
|
654
|
-
if (start) {
|
|
655
|
-
result.push(React.createElement("span", { key: `a${key++}` }, start));
|
|
656
|
-
}
|
|
657
|
-
if (m[0].startsWith('<b>')) {
|
|
658
|
-
result.push(React.createElement("b", { key: `a${key++}` }, m[0].substring(3, m[0].length - 4)));
|
|
659
|
-
}
|
|
660
|
-
else if (m[0].startsWith('<i>')) {
|
|
661
|
-
result.push(React.createElement("i", { key: `a${key++}` }, m[0].substring(3, m[0].length - 4)));
|
|
662
|
-
}
|
|
663
|
-
else if (m[0].startsWith('<br')) {
|
|
664
|
-
result.push(React.createElement("br", { key: `a${key++}` }));
|
|
665
|
-
}
|
|
666
|
-
else {
|
|
667
|
-
const href = m[0].match(/href="([^"]+)"/) || m[0].match(/href='([^']+)'/);
|
|
668
|
-
const target = m[0].match(/target="([^"]+)"/) || m[0].match(/target='([^']+)'/);
|
|
669
|
-
const rel = m[0].match(/rel="([^"]+)"/) || m[0].match(/rel='([^']+)'/);
|
|
670
|
-
const title = m[0].match(/>([^<]*)</);
|
|
671
|
-
// eslint-disable-next-line
|
|
672
|
-
result.push(React.createElement("a", { key: `a${key++}`, href: href ? href[1] : '', target: target ? target[1] : '_blank', rel: rel ? rel[1] : '', style: { color: 'inherit' } }, title ? title[1] : ''));
|
|
673
|
-
}
|
|
674
|
-
m = text && text.match(/<a [^<]+<\/a>|<br\/?>|<b>[^<]+<\/b>|<i>[^<]+<\/i>/);
|
|
675
|
-
if (!m && text) {
|
|
676
|
-
result.push(React.createElement("span", { key: `a${key++}` }, text));
|
|
677
|
-
}
|
|
678
|
-
} while (m);
|
|
679
|
-
return result;
|
|
680
|
-
}
|
|
681
|
-
return text;
|
|
682
|
-
}
|
|
683
|
-
/**
|
|
684
|
-
* Get the smart name of the given state.
|
|
685
|
-
* @param {Record<string, ioBroker.StateObject> | ioBroker.StateObject} states
|
|
686
|
-
* @param {string} id
|
|
687
|
-
* @param {string} instanceId
|
|
688
|
-
* @param {boolean} [noCommon]
|
|
689
|
-
*/
|
|
690
|
-
static getSmartName(states, id, instanceId, noCommon) {
|
|
691
|
-
if (!id) {
|
|
692
|
-
if (!noCommon) {
|
|
693
|
-
if (!states.common) {
|
|
694
|
-
return states.smartName;
|
|
695
|
-
}
|
|
696
|
-
if (states && !states.common) {
|
|
697
|
-
return states.smartName;
|
|
698
|
-
}
|
|
699
|
-
return states.common.smartName;
|
|
700
|
-
}
|
|
701
|
-
if (states && !states.common) {
|
|
702
|
-
return states.smartName;
|
|
703
|
-
}
|
|
704
|
-
return states?.common?.custom && states.common.custom[instanceId]
|
|
705
|
-
? states.common.custom[instanceId].smartName
|
|
706
|
-
: undefined;
|
|
707
|
-
}
|
|
708
|
-
if (!noCommon) {
|
|
709
|
-
return states[id].common.smartName;
|
|
710
|
-
}
|
|
711
|
-
return states[id] && states[id].common?.custom && states[id].common.custom[instanceId]
|
|
712
|
-
? states[id].common.custom[instanceId].smartName || null
|
|
713
|
-
: null;
|
|
714
|
-
}
|
|
715
|
-
/**
|
|
716
|
-
* Get the smart name from a state.
|
|
717
|
-
* @param {ioBroker.StateObject} obj
|
|
718
|
-
* @param {string} instanceId
|
|
719
|
-
* @param {boolean} [noCommon]
|
|
720
|
-
*/
|
|
721
|
-
static getSmartNameFromObj(obj, instanceId, noCommon) {
|
|
722
|
-
if (!noCommon) {
|
|
723
|
-
if (!obj.common) {
|
|
724
|
-
return obj.smartName;
|
|
725
|
-
}
|
|
726
|
-
if (obj && !obj.common) {
|
|
727
|
-
return obj.smartName;
|
|
728
|
-
}
|
|
729
|
-
return obj.common.smartName;
|
|
730
|
-
}
|
|
731
|
-
if (obj && !obj.common) {
|
|
732
|
-
return obj.smartName;
|
|
733
|
-
}
|
|
734
|
-
return obj?.common?.custom && obj.common.custom[instanceId]
|
|
735
|
-
? obj.common.custom[instanceId].smartName
|
|
736
|
-
: undefined;
|
|
737
|
-
}
|
|
738
|
-
/**
|
|
739
|
-
* Enable smart name for a state.
|
|
740
|
-
* @param {ioBroker.StateObject} obj
|
|
741
|
-
* @param {string} instanceId
|
|
742
|
-
* @param {boolean} [noCommon]
|
|
743
|
-
*/
|
|
744
|
-
static enableSmartName(obj, instanceId, noCommon) {
|
|
745
|
-
if (noCommon) {
|
|
746
|
-
obj.common.custom = obj.common.custom || {};
|
|
747
|
-
obj.common.custom[instanceId] = obj.common.custom[instanceId] || {};
|
|
748
|
-
obj.common.custom[instanceId].smartName = {};
|
|
749
|
-
}
|
|
750
|
-
else {
|
|
751
|
-
obj.common.smartName = {};
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
/**
|
|
755
|
-
* Completely remove smart name from a state.
|
|
756
|
-
* @param {ioBroker.StateObject} obj
|
|
757
|
-
* @param {string | number} instanceId
|
|
758
|
-
* @param {boolean} [noCommon]
|
|
759
|
-
*/
|
|
760
|
-
static removeSmartName(obj, instanceId, noCommon) {
|
|
761
|
-
if (noCommon) {
|
|
762
|
-
if (obj.common && obj.common.custom && obj.common.custom[instanceId]) {
|
|
763
|
-
obj.common.custom[instanceId] = null;
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
else {
|
|
767
|
-
obj.common.smartName = null;
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
/**
|
|
771
|
-
* Update the smartname of a state.
|
|
772
|
-
* @param {ioBroker.StateObject} obj
|
|
773
|
-
* @param {string} newSmartName
|
|
774
|
-
* @param {string | undefined} byON
|
|
775
|
-
* @param {string | undefined} smartType
|
|
776
|
-
* @param {string} instanceId
|
|
777
|
-
* @param {boolean} [noCommon]
|
|
778
|
-
*/
|
|
779
|
-
static updateSmartName(obj, newSmartName, byON, smartType, instanceId, noCommon) {
|
|
780
|
-
const language = I18n.getLanguage();
|
|
781
|
-
// convert the old format
|
|
782
|
-
if (typeof obj.common.smartName === 'string') {
|
|
783
|
-
const nnn = obj.common.smartName;
|
|
784
|
-
obj.common.smartName = {};
|
|
785
|
-
obj.common.smartName[language] = nnn;
|
|
786
|
-
}
|
|
787
|
-
// convert the old settings
|
|
788
|
-
if (obj.native && obj.native.byON) {
|
|
789
|
-
delete obj.native.byON;
|
|
790
|
-
let _smartName = obj.common.smartName;
|
|
791
|
-
if (!_smartName || typeof _smartName !== 'object') {
|
|
792
|
-
_smartName = { en: _smartName };
|
|
793
|
-
_smartName[language] = _smartName.en;
|
|
794
|
-
}
|
|
795
|
-
obj.common.smartName = _smartName;
|
|
796
|
-
}
|
|
797
|
-
if (smartType !== undefined) {
|
|
798
|
-
if (noCommon) {
|
|
799
|
-
obj.common.custom = obj.common.custom || {};
|
|
800
|
-
obj.common.custom[instanceId] = obj.common.custom[instanceId] || {};
|
|
801
|
-
obj.common.custom[instanceId].smartName = obj.common.custom[instanceId].smartName || {};
|
|
802
|
-
if (!smartType) {
|
|
803
|
-
delete obj.common.custom[instanceId].smartName.smartType;
|
|
804
|
-
}
|
|
805
|
-
else {
|
|
806
|
-
obj.common.custom[instanceId].smartName.smartType = smartType;
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
else {
|
|
810
|
-
obj.common.smartName = obj.common.smartName || {};
|
|
811
|
-
if (!smartType) {
|
|
812
|
-
delete obj.common.smartName.smartType;
|
|
813
|
-
}
|
|
814
|
-
else {
|
|
815
|
-
obj.common.smartName.smartType = smartType;
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
if (byON !== undefined) {
|
|
820
|
-
if (noCommon) {
|
|
821
|
-
obj.common.custom = obj.common.custom || {};
|
|
822
|
-
obj.common.custom[instanceId] = obj.common.custom[instanceId] || {};
|
|
823
|
-
obj.common.custom[instanceId].smartName = obj.common.custom[instanceId].smartName || {};
|
|
824
|
-
obj.common.custom[instanceId].smartName.byON = byON;
|
|
825
|
-
}
|
|
826
|
-
else {
|
|
827
|
-
obj.common.smartName = obj.common.smartName || {};
|
|
828
|
-
obj.common.smartName.byON = byON;
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
if (newSmartName !== undefined) {
|
|
832
|
-
let smartName;
|
|
833
|
-
if (noCommon) {
|
|
834
|
-
obj.common.custom = obj.common.custom || {};
|
|
835
|
-
obj.common.custom[instanceId] = obj.common.custom[instanceId] || {};
|
|
836
|
-
obj.common.custom[instanceId].smartName = obj.common.custom[instanceId].smartName || {};
|
|
837
|
-
smartName = obj.common.custom[instanceId].smartName;
|
|
838
|
-
}
|
|
839
|
-
else {
|
|
840
|
-
obj.common.smartName = obj.common.smartName || {};
|
|
841
|
-
smartName = obj.common.smartName;
|
|
842
|
-
}
|
|
843
|
-
smartName[language] = newSmartName;
|
|
844
|
-
// If smart name deleted
|
|
845
|
-
if (smartName &&
|
|
846
|
-
(!smartName[language] ||
|
|
847
|
-
(smartName[language] === obj.common.name &&
|
|
848
|
-
(!obj.common.role || obj.common.role.includes('button'))))) {
|
|
849
|
-
delete smartName[language];
|
|
850
|
-
let empty = true;
|
|
851
|
-
// Check if the structure has any definitions
|
|
852
|
-
for (const key in smartName) {
|
|
853
|
-
if (Object.prototype.hasOwnProperty.call(smartName, key)) {
|
|
854
|
-
empty = false;
|
|
855
|
-
break;
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
// If empty => delete smartName completely
|
|
859
|
-
if (empty) {
|
|
860
|
-
if (noCommon) {
|
|
861
|
-
if (obj.common.custom[instanceId].smartName.byON === undefined) {
|
|
862
|
-
delete obj.common.custom[instanceId];
|
|
863
|
-
}
|
|
864
|
-
else {
|
|
865
|
-
delete obj.common.custom[instanceId].en;
|
|
866
|
-
delete obj.common.custom[instanceId].de;
|
|
867
|
-
delete obj.common.custom[instanceId].ru;
|
|
868
|
-
delete obj.common.custom[instanceId].nl;
|
|
869
|
-
delete obj.common.custom[instanceId].pl;
|
|
870
|
-
delete obj.common.custom[instanceId].it;
|
|
871
|
-
delete obj.common.custom[instanceId].fr;
|
|
872
|
-
delete obj.common.custom[instanceId].pt;
|
|
873
|
-
delete obj.common.custom[instanceId].es;
|
|
874
|
-
delete obj.common.custom[instanceId].uk;
|
|
875
|
-
delete obj.common.custom[instanceId]['zh-cn'];
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
else if (obj.common.smartName.byON !== undefined) {
|
|
879
|
-
delete obj.common.smartName.en;
|
|
880
|
-
delete obj.common.smartName.de;
|
|
881
|
-
delete obj.common.smartName.ru;
|
|
882
|
-
delete obj.common.smartName.nl;
|
|
883
|
-
delete obj.common.smartName.pl;
|
|
884
|
-
delete obj.common.smartName.it;
|
|
885
|
-
delete obj.common.smartName.fr;
|
|
886
|
-
delete obj.common.smartName.pt;
|
|
887
|
-
delete obj.common.smartName.es;
|
|
888
|
-
delete obj.common.smartName.uk;
|
|
889
|
-
delete obj.common.smartName['zh-cn'];
|
|
890
|
-
}
|
|
891
|
-
else {
|
|
892
|
-
obj.common.smartName = null;
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
/**
|
|
899
|
-
* Disable the smart name of a state.
|
|
900
|
-
* @param {ioBroker.StateObject} obj
|
|
901
|
-
* @param {string} instanceId
|
|
902
|
-
* @param {boolean} [noCommon]
|
|
903
|
-
*/
|
|
904
|
-
static disableSmartName(obj, instanceId, noCommon) {
|
|
905
|
-
if (noCommon) {
|
|
906
|
-
obj.common.custom = obj.common.custom || {};
|
|
907
|
-
obj.common.custom[instanceId] = obj.common.custom[instanceId] || {};
|
|
908
|
-
obj.common.custom[instanceId].smartName = false;
|
|
909
|
-
}
|
|
910
|
-
else {
|
|
911
|
-
obj.common.smartName = false;
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
/**
|
|
915
|
-
* Copy text to the clipboard.
|
|
916
|
-
* @param {string} text
|
|
917
|
-
* @param {Event} [e]
|
|
918
|
-
*/
|
|
919
|
-
static copyToClipboard(text, e) {
|
|
920
|
-
if (e) {
|
|
921
|
-
e.stopPropagation();
|
|
922
|
-
e.preventDefault();
|
|
923
|
-
}
|
|
924
|
-
return _Utils.copyToClipboard(text);
|
|
925
|
-
}
|
|
926
|
-
/**
|
|
927
|
-
* Gets the extension of a file name.
|
|
928
|
-
* @param {string | null} [fileName] the file name.
|
|
929
|
-
* @returns {string | null} The extension in lower case.
|
|
930
|
-
*/
|
|
931
|
-
static getFileExtension(fileName) {
|
|
932
|
-
const pos = (fileName || '').lastIndexOf('.');
|
|
933
|
-
if (pos !== -1) {
|
|
934
|
-
return fileName.substring(pos + 1).toLowerCase();
|
|
935
|
-
}
|
|
936
|
-
return null;
|
|
937
|
-
}
|
|
938
|
-
/**
|
|
939
|
-
* Format number of bytes as a string with B, KB, MB or GB.
|
|
940
|
-
* The base for all calculations is 1024.
|
|
941
|
-
* @param {number} bytes The number of bytes.
|
|
942
|
-
* @returns {string} The formatted string (e.g. '723.5 KB')
|
|
943
|
-
*/
|
|
944
|
-
static formatBytes(bytes) {
|
|
945
|
-
if (Math.abs(bytes) < 1024) {
|
|
946
|
-
return `${bytes} B`;
|
|
947
|
-
}
|
|
948
|
-
const units = ['KB', 'MB', 'GB'];
|
|
949
|
-
// const units = ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'];
|
|
950
|
-
let u = -1;
|
|
951
|
-
do {
|
|
952
|
-
bytes /= 1024;
|
|
953
|
-
++u;
|
|
954
|
-
} while (Math.abs(bytes) >= 1024 && u < units.length - 1);
|
|
955
|
-
return `${bytes.toFixed(1)} ${units[u]}`;
|
|
956
|
-
}
|
|
957
|
-
/**
|
|
958
|
-
* Invert the given color according to a theme type to get the inverted text color for background
|
|
959
|
-
* @param {string} color Color in the format '#rrggbb' or '#rgb' (or without a hash)
|
|
960
|
-
* @param {string} themeType theme type
|
|
961
|
-
* @param {string} invert dark theme has light color in control or light theme has light color in control
|
|
962
|
-
* @returns {string | undefined}
|
|
963
|
-
*/
|
|
964
|
-
static getInvertedColor(color, themeType, invert) {
|
|
965
|
-
if (!color) {
|
|
966
|
-
return undefined;
|
|
967
|
-
}
|
|
968
|
-
const invertedColor = Utils.invertColor(color, true);
|
|
969
|
-
if (invertedColor === '#FFFFFF' && (themeType === 'dark' || (invert && themeType === 'light'))) {
|
|
970
|
-
return '#DDD';
|
|
971
|
-
}
|
|
972
|
-
if (invertedColor === '#000000' && (themeType === 'light' || (invert && themeType === 'dark'))) {
|
|
973
|
-
return '#222';
|
|
974
|
-
}
|
|
975
|
-
return undefined;
|
|
976
|
-
}
|
|
977
|
-
// Big thanks to: https://stackoverflow.com/questions/35969656/how-can-i-generate-the-opposite-color-according-to-current-color
|
|
978
|
-
/**
|
|
979
|
-
* Invert the given color
|
|
980
|
-
* @param {string} hex Color in the format '#rrggbb' or '#rgb' (or without hash)
|
|
981
|
-
* @param {boolean} bw Set to black or white.
|
|
982
|
-
* @returns {string}
|
|
983
|
-
*/
|
|
984
|
-
static invertColor(hex, bw) {
|
|
985
|
-
if (hex === undefined || hex === null || hex === '' || typeof hex !== 'string') {
|
|
986
|
-
return '';
|
|
987
|
-
}
|
|
988
|
-
if (hex.startsWith('rgba')) {
|
|
989
|
-
const m = hex.match(/rgba?\((\d+),\s*(\d+),\s*(\d+),\s*([.\d]+)\)/);
|
|
990
|
-
if (m) {
|
|
991
|
-
hex =
|
|
992
|
-
parseInt(m[1], 10).toString(16).padStart(2, '0') +
|
|
993
|
-
parseInt(m[2], 10).toString(16).padStart(2, '0') +
|
|
994
|
-
parseInt(m[2], 10).toString(16).padStart(2, '0');
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
else if (hex.startsWith('rgb')) {
|
|
998
|
-
const m = hex.match(/rgb?\((\d+),\s*(\d+),\s*(\d+)\)/);
|
|
999
|
-
if (m) {
|
|
1000
|
-
hex =
|
|
1001
|
-
parseInt(m[1], 10).toString(16).padStart(2, '0') +
|
|
1002
|
-
parseInt(m[2], 10).toString(16).padStart(2, '0') +
|
|
1003
|
-
parseInt(m[2], 10).toString(16).padStart(2, '0');
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
else if (hex.startsWith('#')) {
|
|
1007
|
-
hex = hex.slice(1);
|
|
1008
|
-
}
|
|
1009
|
-
// convert 3-digit hex to 6-digits.
|
|
1010
|
-
if (hex.length === 3) {
|
|
1011
|
-
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
|
1012
|
-
}
|
|
1013
|
-
let alfa = null;
|
|
1014
|
-
if (hex.length === 8) {
|
|
1015
|
-
alfa = hex.substring(6, 8);
|
|
1016
|
-
hex = hex.substring(0, 6);
|
|
1017
|
-
}
|
|
1018
|
-
else if (hex.length !== 6) {
|
|
1019
|
-
console.warn(`Cannot invert color: ${hex}`);
|
|
1020
|
-
return hex;
|
|
1021
|
-
}
|
|
1022
|
-
let r = parseInt(hex.slice(0, 2), 16);
|
|
1023
|
-
let g = parseInt(hex.slice(2, 4), 16);
|
|
1024
|
-
let b = parseInt(hex.slice(4, 6), 16);
|
|
1025
|
-
if (bw) {
|
|
1026
|
-
// http://stackoverflow.com/a/3943023/112731
|
|
1027
|
-
return r * 0.299 + g * 0.587 + b * 0.114 > 186 ? `#000000${alfa || ''}` : `#FFFFFF${alfa || ''}`;
|
|
1028
|
-
}
|
|
1029
|
-
// invert color components
|
|
1030
|
-
r = (255 - r).toString(16);
|
|
1031
|
-
g = (255 - g).toString(16);
|
|
1032
|
-
b = (255 - b).toString(16);
|
|
1033
|
-
// pad each with zeros and return
|
|
1034
|
-
return `#${r.padStart(2, '0')}${g.padStart(2, '0')}${b.padStart(2, '0')}${alfa || ''}`;
|
|
1035
|
-
}
|
|
1036
|
-
/**
|
|
1037
|
-
* Convert RGB to array [r, g, b]
|
|
1038
|
-
* @param {string} hex Color in the format '#rrggbb' or '#rgb' (or without hash) or rgb(r,g,b) or rgba(r,g,b,a)
|
|
1039
|
-
* @returns {Array<number>} Array with 3 elements [r, g, b]
|
|
1040
|
-
*/
|
|
1041
|
-
static color2rgb(hex) {
|
|
1042
|
-
if (hex === undefined || hex === null || hex === '' || typeof hex !== 'string') {
|
|
1043
|
-
return '';
|
|
1044
|
-
}
|
|
1045
|
-
if (hex.startsWith('rgba')) {
|
|
1046
|
-
const m = hex.match(/rgba?\((\d+),\s*(\d+),\s*(\d+),\s*([.\d]+)\)/);
|
|
1047
|
-
if (m) {
|
|
1048
|
-
hex =
|
|
1049
|
-
parseInt(m[1], 10).toString(16).padStart(2, '0') +
|
|
1050
|
-
parseInt(m[2], 10).toString(16).padStart(2, '0') +
|
|
1051
|
-
parseInt(m[2], 10).toString(16).padStart(2, '0');
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
else if (hex.startsWith('rgb')) {
|
|
1055
|
-
const m = hex.match(/rgb?\((\d+),\s*(\d+),\s*(\d+)\)/);
|
|
1056
|
-
if (m) {
|
|
1057
|
-
hex =
|
|
1058
|
-
parseInt(m[1], 10).toString(16).padStart(2, '0') +
|
|
1059
|
-
parseInt(m[2], 10).toString(16).padStart(2, '0') +
|
|
1060
|
-
parseInt(m[2], 10).toString(16).padStart(2, '0');
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
else if (hex.startsWith('#')) {
|
|
1064
|
-
hex = hex.slice(1);
|
|
1065
|
-
}
|
|
1066
|
-
// convert 3-digit hex to 6-digits.
|
|
1067
|
-
if (hex.length === 3) {
|
|
1068
|
-
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
|
1069
|
-
}
|
|
1070
|
-
if (hex.length !== 6 && hex.length !== 8) {
|
|
1071
|
-
console.warn(`Cannot invert color: ${hex}`);
|
|
1072
|
-
return false;
|
|
1073
|
-
}
|
|
1074
|
-
return [parseInt(hex.slice(0, 2), 16), parseInt(hex.slice(2, 4), 16), parseInt(hex.slice(4, 6), 16)];
|
|
1075
|
-
}
|
|
1076
|
-
// Big thanks to: https://github.com/antimatter15/rgb-lab
|
|
1077
|
-
/**
|
|
1078
|
-
* Convert RGB to LAB
|
|
1079
|
-
* @param {Array<number>} rgb color in format [r,g,b]
|
|
1080
|
-
* @returns {Array<number>} lab color in format [l,a,b]
|
|
1081
|
-
*/
|
|
1082
|
-
static rgb2lab(rgb) {
|
|
1083
|
-
let r = rgb[0] / 255;
|
|
1084
|
-
let g = rgb[1] / 255;
|
|
1085
|
-
let b = rgb[2] / 255;
|
|
1086
|
-
r = r > 0.04045 ? ((r + 0.055) / 1.055) ** 2.4 : r / 12.92;
|
|
1087
|
-
g = g > 0.04045 ? ((g + 0.055) / 1.055) ** 2.4 : g / 12.92;
|
|
1088
|
-
b = b > 0.04045 ? ((b + 0.055) / 1.055) ** 2.4 : b / 12.92;
|
|
1089
|
-
let x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047;
|
|
1090
|
-
let y = r * 0.2126 + g * 0.7152 + b * 0.0722; /* / 1.00000; */
|
|
1091
|
-
let z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883;
|
|
1092
|
-
x = x > 0.008856 ? x ** 0.33333333 : 7.787 * x + 0.137931; // 16 / 116;
|
|
1093
|
-
y = y > 0.008856 ? y ** 0.33333333 : 7.787 * y + 0.137931; // 16 / 116;
|
|
1094
|
-
z = z > 0.008856 ? z ** 0.33333333 : 7.787 * z + 0.137931; // 16 / 116;
|
|
1095
|
-
return [116 * y - 16, 500 * (x - y), 200 * (y - z)];
|
|
1096
|
-
}
|
|
1097
|
-
/**
|
|
1098
|
-
* Calculate the distance between two colors in LAB color space in the range 0-100^2
|
|
1099
|
-
* If distance is less than 1000, the colors are similar
|
|
1100
|
-
* @param {string} color1 Color in the format '#rrggbb' or '#rgb' (or without hash) or rgb(r,g,b) or rgba(r,g,b,a)
|
|
1101
|
-
* @param {string} color2 Color in the format '#rrggbb' or '#rgb' (or without hash) or rgb(r,g,b) or rgba(r,g,b,a)
|
|
1102
|
-
* @returns {number} distance in the range 0-100^2
|
|
1103
|
-
*/
|
|
1104
|
-
static colorDistance(color1, color2) {
|
|
1105
|
-
const lab1 = Utils.rgb2lab(Utils.color2rgb(color1));
|
|
1106
|
-
const lab2 = Utils.rgb2lab(Utils.color2rgb(color2));
|
|
1107
|
-
const dltL = lab1[0] - lab2[0];
|
|
1108
|
-
const dltA = lab1[1] - lab2[1];
|
|
1109
|
-
const dltB = lab1[2] - lab2[2];
|
|
1110
|
-
const c1 = Math.sqrt(lab1[1] * lab1[1] + lab1[2] * lab1[2]);
|
|
1111
|
-
const c2 = Math.sqrt(lab2[1] * lab2[1] + lab2[2] * lab2[2]);
|
|
1112
|
-
const dltC = c1 - c2;
|
|
1113
|
-
let dltH = dltA * dltA + dltB * dltB - dltC * dltC;
|
|
1114
|
-
dltH = dltH < 0 ? 0 : Math.sqrt(dltH);
|
|
1115
|
-
const sc = 1.0 + 0.045 * c1;
|
|
1116
|
-
const sh = 1.0 + 0.015 * c1;
|
|
1117
|
-
const dltLKlsl = dltL;
|
|
1118
|
-
const dltCkcsc = dltC / sc;
|
|
1119
|
-
const dltHkhsh = dltH / sh;
|
|
1120
|
-
const i = dltLKlsl * dltLKlsl + dltCkcsc * dltCkcsc + dltHkhsh * dltHkhsh;
|
|
1121
|
-
return i < 0 ? 0 : i;
|
|
1122
|
-
}
|
|
1123
|
-
// https://github.com/lukeed/clsx/blob/master/src/index.js
|
|
1124
|
-
// License
|
|
1125
|
-
// MIT © Luke Edwards
|
|
1126
|
-
/**
|
|
1127
|
-
* @private
|
|
1128
|
-
* @param {any} mix
|
|
1129
|
-
* @returns {string}
|
|
1130
|
-
*/
|
|
1131
|
-
static _toVal(mix) {
|
|
1132
|
-
let y;
|
|
1133
|
-
let str = '';
|
|
1134
|
-
if (typeof mix === 'string' || typeof mix === 'number') {
|
|
1135
|
-
str += mix;
|
|
1136
|
-
}
|
|
1137
|
-
else if (typeof mix === 'object') {
|
|
1138
|
-
if (Array.isArray(mix)) {
|
|
1139
|
-
for (let k = 0; k < mix.length; k++) {
|
|
1140
|
-
if (mix[k]) {
|
|
1141
|
-
y = Utils._toVal(mix[k]);
|
|
1142
|
-
if (y) {
|
|
1143
|
-
str += (str ? ' ' : '') + y;
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
else {
|
|
1149
|
-
for (const k in mix) {
|
|
1150
|
-
if (mix[k]) {
|
|
1151
|
-
str += (str ? ' ' : '') + k;
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
return str;
|
|
1157
|
-
}
|
|
1158
|
-
// https://github.com/lukeed/clsx/blob/master/src/index.js
|
|
1159
|
-
// License
|
|
1160
|
-
// MIT © Luke Edwards
|
|
1161
|
-
/**
|
|
1162
|
-
* Convert any object to a string with its values.
|
|
1163
|
-
* @returns {string}
|
|
1164
|
-
*/
|
|
1165
|
-
static clsx() {
|
|
1166
|
-
let i = 0;
|
|
1167
|
-
let tmp;
|
|
1168
|
-
let x;
|
|
1169
|
-
let str = '';
|
|
1170
|
-
while (i < arguments.length) {
|
|
1171
|
-
// eslint-disable-next-line prefer-rest-params
|
|
1172
|
-
tmp = arguments[i++];
|
|
1173
|
-
if (tmp) {
|
|
1174
|
-
x = Utils._toVal(tmp);
|
|
1175
|
-
if (x) {
|
|
1176
|
-
str += (str ? ' ' : '') + x;
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
return str;
|
|
1181
|
-
}
|
|
1182
|
-
/**
|
|
1183
|
-
* Get the current theme name (either from local storage or the browser settings).
|
|
1184
|
-
* @param {string} [themeName]
|
|
1185
|
-
* @returns {string}
|
|
1186
|
-
*/
|
|
1187
|
-
static getThemeName(themeName = '') {
|
|
1188
|
-
if (window.vendorPrefix && window.vendorPrefix !== '@@vendorPrefix@@') {
|
|
1189
|
-
return window.vendorPrefix;
|
|
1190
|
-
}
|
|
1191
|
-
return (themeName ||
|
|
1192
|
-
((window._localStorage || window.localStorage).getItem('App.themeName')
|
|
1193
|
-
? (window._localStorage || window.localStorage).getItem('App.themeName')
|
|
1194
|
-
: window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
1195
|
-
? 'dark'
|
|
1196
|
-
: 'colored'));
|
|
1197
|
-
}
|
|
1198
|
-
/**
|
|
1199
|
-
* Get the type of theme.
|
|
1200
|
-
* @param {string} [themeName]
|
|
1201
|
-
* @returns {'dark' | 'light'}
|
|
1202
|
-
*/
|
|
1203
|
-
static getThemeType(themeName = '') {
|
|
1204
|
-
if (window.vendorPrefix && window.vendorPrefix !== '@@vendorPrefix@@') {
|
|
1205
|
-
return 'light';
|
|
1206
|
-
}
|
|
1207
|
-
themeName = themeName || (window._localStorage || window.localStorage).getItem('App.themeName');
|
|
1208
|
-
return themeName === 'dark' || themeName === 'blue' ? 'dark' : 'light';
|
|
1209
|
-
}
|
|
1210
|
-
/**
|
|
1211
|
-
* Set the theme name and theme type.
|
|
1212
|
-
* @param {string} themeName
|
|
1213
|
-
*/
|
|
1214
|
-
static setThemeName(themeName) {
|
|
1215
|
-
if (window.vendorPrefix && window.vendorPrefix !== '@@vendorPrefix@@') {
|
|
1216
|
-
return; // ignore
|
|
1217
|
-
}
|
|
1218
|
-
(window._localStorage || window.localStorage).setItem('App.themeName', themeName);
|
|
1219
|
-
(window._localStorage || window.localStorage).setItem('App.theme', themeName === 'dark' || themeName === 'blue' ? 'dark' : 'light');
|
|
1220
|
-
}
|
|
1221
|
-
/**
|
|
1222
|
-
* Toggle the theme name between 'dark' and 'colored'.
|
|
1223
|
-
* @param {string | null} themeName
|
|
1224
|
-
* @returns {string} the new theme name.
|
|
1225
|
-
*/
|
|
1226
|
-
static toggleTheme(themeName) {
|
|
1227
|
-
if (window.vendorPrefix && window.vendorPrefix !== '@@vendorPrefix@@') {
|
|
1228
|
-
return window.vendorPrefix;
|
|
1229
|
-
}
|
|
1230
|
-
themeName = themeName || (window._localStorage || window.localStorage).getItem('App.themeName');
|
|
1231
|
-
// dark => blue => colored => light => dark
|
|
1232
|
-
const themes = Utils.getThemeNames();
|
|
1233
|
-
const pos = themes.indexOf(themeName);
|
|
1234
|
-
let newTheme;
|
|
1235
|
-
if (pos !== -1) {
|
|
1236
|
-
newTheme = themes[(pos + 1) % themes.length];
|
|
1237
|
-
}
|
|
1238
|
-
else {
|
|
1239
|
-
newTheme = themes[0];
|
|
1240
|
-
}
|
|
1241
|
-
Utils.setThemeName(newTheme);
|
|
1242
|
-
return newTheme;
|
|
1243
|
-
}
|
|
1244
|
-
/**
|
|
1245
|
-
* Get the list of themes
|
|
1246
|
-
* @returns {array<string>} list of possible themes
|
|
1247
|
-
*/
|
|
1248
|
-
static getThemeNames() {
|
|
1249
|
-
if (window.vendorPrefix && window.vendorPrefix !== '@@vendorPrefix@@') {
|
|
1250
|
-
return [window.vendorPrefix];
|
|
1251
|
-
}
|
|
1252
|
-
return ['light', 'dark', 'blue', 'colored'];
|
|
1253
|
-
}
|
|
1254
|
-
/**
|
|
1255
|
-
* Parse a query string into its parts.
|
|
1256
|
-
* @param {string} query
|
|
1257
|
-
* @returns {Record<string, string | boolean | number>}
|
|
1258
|
-
*/
|
|
1259
|
-
static parseQuery(query) {
|
|
1260
|
-
query = (query || '').toString().replace(/^\?/, '');
|
|
1261
|
-
/** @type {Record<string, string | boolean | number>} */
|
|
1262
|
-
const result = {};
|
|
1263
|
-
query.split('&').forEach(part => {
|
|
1264
|
-
part = part.trim();
|
|
1265
|
-
if (part) {
|
|
1266
|
-
const parts = part.split('=');
|
|
1267
|
-
const attr = decodeURIComponent(parts[0]).trim();
|
|
1268
|
-
if (parts.length > 1) {
|
|
1269
|
-
result[attr] = decodeURIComponent(parts[1]);
|
|
1270
|
-
if (result[attr] === 'true') {
|
|
1271
|
-
result[attr] = true;
|
|
1272
|
-
}
|
|
1273
|
-
else if (result[attr] === 'false') {
|
|
1274
|
-
result[attr] = false;
|
|
1275
|
-
}
|
|
1276
|
-
else {
|
|
1277
|
-
const f = parseFloat(result[attr]);
|
|
1278
|
-
if (f.toString() === result[attr]) {
|
|
1279
|
-
result[attr] = f;
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
else {
|
|
1284
|
-
result[attr] = true;
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1287
|
-
});
|
|
1288
|
-
return result;
|
|
1289
|
-
}
|
|
1290
|
-
/**
|
|
1291
|
-
* Returns parent ID.
|
|
1292
|
-
* @param {string} id
|
|
1293
|
-
* @returns {string | null} parent ID or null if no parent
|
|
1294
|
-
*/
|
|
1295
|
-
static getParentId(id) {
|
|
1296
|
-
const p = (id || '').toString().split('.');
|
|
1297
|
-
if (p.length > 1) {
|
|
1298
|
-
p.pop();
|
|
1299
|
-
return p.join('.');
|
|
1300
|
-
}
|
|
1301
|
-
return null;
|
|
1302
|
-
}
|
|
1303
|
-
static formatDate(dateObj, dateFormat) {
|
|
1304
|
-
// format could be DD.MM.YYYY, YYYY.MM.DD or MM/DD/YYYY
|
|
1305
|
-
if (!dateObj) {
|
|
1306
|
-
return '';
|
|
1307
|
-
}
|
|
1308
|
-
let text;
|
|
1309
|
-
let mm = dateObj.getMonth() + 1;
|
|
1310
|
-
if (mm < 10) {
|
|
1311
|
-
mm = `0${mm}`;
|
|
1312
|
-
}
|
|
1313
|
-
let dd = dateObj.getDate();
|
|
1314
|
-
if (dd < 10) {
|
|
1315
|
-
dd = `0${dd}`;
|
|
1316
|
-
}
|
|
1317
|
-
if (dateFormat === 'MM/DD/YYYY') {
|
|
1318
|
-
text = `${mm}/${dd}/${dateObj.getFullYear()}`;
|
|
1319
|
-
}
|
|
1320
|
-
else {
|
|
1321
|
-
text = `${dateObj.getFullYear()}-${mm}-${dd}`;
|
|
1322
|
-
}
|
|
1323
|
-
// time
|
|
1324
|
-
let v = dateObj.getHours();
|
|
1325
|
-
if (v < 10) {
|
|
1326
|
-
text += ` 0${v}`;
|
|
1327
|
-
}
|
|
1328
|
-
else {
|
|
1329
|
-
text += ` ${v}`;
|
|
1330
|
-
}
|
|
1331
|
-
v = dateObj.getMinutes();
|
|
1332
|
-
if (v < 10) {
|
|
1333
|
-
text += `:0${v}`;
|
|
1334
|
-
}
|
|
1335
|
-
else {
|
|
1336
|
-
text += `:${v}`;
|
|
1337
|
-
}
|
|
1338
|
-
v = dateObj.getSeconds();
|
|
1339
|
-
if (v < 10) {
|
|
1340
|
-
text += `:0${v}`;
|
|
1341
|
-
}
|
|
1342
|
-
else {
|
|
1343
|
-
text += `:${v}`;
|
|
1344
|
-
}
|
|
1345
|
-
v = dateObj.getMilliseconds();
|
|
1346
|
-
if (v < 10) {
|
|
1347
|
-
text += `.00${v}`;
|
|
1348
|
-
}
|
|
1349
|
-
else if (v < 100) {
|
|
1350
|
-
text += `.0${v}`;
|
|
1351
|
-
}
|
|
1352
|
-
else {
|
|
1353
|
-
text += `.${v}`;
|
|
1354
|
-
}
|
|
1355
|
-
return text;
|
|
1356
|
-
}
|
|
1357
|
-
static formatTime(seconds) {
|
|
1358
|
-
if (seconds) {
|
|
1359
|
-
seconds = Math.round(seconds);
|
|
1360
|
-
const d = Math.floor(seconds / (3600 * 24));
|
|
1361
|
-
const h = Math.floor((seconds % (3600 * 24)) / 3600);
|
|
1362
|
-
const m = Math.floor((seconds % 3600) / 60);
|
|
1363
|
-
const s = seconds % 60;
|
|
1364
|
-
if (d) {
|
|
1365
|
-
return `${d}.${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
|
|
1366
|
-
}
|
|
1367
|
-
if (h) {
|
|
1368
|
-
return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
|
|
1369
|
-
}
|
|
1370
|
-
return `0:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
|
|
1371
|
-
}
|
|
1372
|
-
return '0:00:00';
|
|
1373
|
-
}
|
|
1374
|
-
static MDtext2link(text) {
|
|
1375
|
-
const m = text.match(/\d+\.\)\s/);
|
|
1376
|
-
if (m) {
|
|
1377
|
-
text = text.replace(m[0], m[0].replace(/\s/, ' '));
|
|
1378
|
-
}
|
|
1379
|
-
return text
|
|
1380
|
-
.replace(/[^a-zA-Zа-яА-Я0-9]/g, '')
|
|
1381
|
-
.trim()
|
|
1382
|
-
.replace(/\s/g, '')
|
|
1383
|
-
.toLowerCase();
|
|
1384
|
-
}
|
|
1385
|
-
static openLink(url, target) {
|
|
1386
|
-
// replace IPv6 Address with [ipv6]:port
|
|
1387
|
-
url = url.replace(/\/\/([0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*)(:\d+)?\//i, '//[$1]$2/');
|
|
1388
|
-
if (target === 'this') {
|
|
1389
|
-
window.location = url;
|
|
1390
|
-
}
|
|
1391
|
-
else {
|
|
1392
|
-
window.open(url, target || '_blank');
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
static MDgetTitle(text) {
|
|
1396
|
-
const result = Utils.extractHeader(text);
|
|
1397
|
-
const header = result.header;
|
|
1398
|
-
let body = result.body;
|
|
1399
|
-
if (!header.title) {
|
|
1400
|
-
// remove {docsify-bla}
|
|
1401
|
-
body = body.replace(/{[^}]*}/g, '');
|
|
1402
|
-
body = body.trim();
|
|
1403
|
-
const lines = body.replace(/\r/g, '').split('\n');
|
|
1404
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1405
|
-
if (lines[i].startsWith('# ')) {
|
|
1406
|
-
return lines[i].substring(2).trim();
|
|
1407
|
-
}
|
|
1408
|
-
}
|
|
1409
|
-
return '';
|
|
1410
|
-
}
|
|
1411
|
-
return header.title;
|
|
1412
|
-
}
|
|
1413
|
-
static MDextractHeader(text) {
|
|
1414
|
-
const attrs = {};
|
|
1415
|
-
if (text.substring(0, 3) === '---') {
|
|
1416
|
-
const pos = text.substring(3).indexOf('\n---');
|
|
1417
|
-
if (pos !== -1) {
|
|
1418
|
-
const _header = text.substring(3, pos + 3);
|
|
1419
|
-
const lines = _header.replace(/\r/g, '').split('\n');
|
|
1420
|
-
lines.forEach(line => {
|
|
1421
|
-
if (!line.trim()) {
|
|
1422
|
-
return;
|
|
1423
|
-
}
|
|
1424
|
-
const pos_ = line.indexOf(':');
|
|
1425
|
-
if (pos_ !== -1) {
|
|
1426
|
-
const attr = line.substring(0, pos_).trim();
|
|
1427
|
-
attrs[attr] = line.substring(pos_ + 1).trim();
|
|
1428
|
-
attrs[attr] = attrs[attr].replace(/^['"]|['"]$/g, '');
|
|
1429
|
-
if (attrs[attr] === 'true') {
|
|
1430
|
-
attrs[attr] = true;
|
|
1431
|
-
}
|
|
1432
|
-
else if (attrs[attr] === 'false') {
|
|
1433
|
-
attrs[attr] = false;
|
|
1434
|
-
}
|
|
1435
|
-
else if (parseFloat(attrs[attr]).toString() === attrs[attr]) {
|
|
1436
|
-
attrs[attr] = parseFloat(attrs[attr]);
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
|
-
else {
|
|
1440
|
-
attrs[line.trim()] = true;
|
|
1441
|
-
}
|
|
1442
|
-
});
|
|
1443
|
-
text = text.substring(pos + 7);
|
|
1444
|
-
}
|
|
1445
|
-
}
|
|
1446
|
-
return { header: attrs, body: text };
|
|
1447
|
-
}
|
|
1448
|
-
static MDremoveDocsify(text) {
|
|
1449
|
-
const m = text.match(/{docsify-[^}]*}/g);
|
|
1450
|
-
if (m) {
|
|
1451
|
-
m.forEach(doc => (text = text.replace(doc, '')));
|
|
1452
|
-
}
|
|
1453
|
-
return text;
|
|
1454
|
-
}
|
|
1455
|
-
/**
|
|
1456
|
-
* Generate the json file on the file for download.
|
|
1457
|
-
* @param {string} filename file name
|
|
1458
|
-
* @param {Record<string, unknown>} json file data
|
|
1459
|
-
* @returns {object} json structure (not stringified)
|
|
1460
|
-
*/
|
|
1461
|
-
static generateFile(filename, json) {
|
|
1462
|
-
const el = document.createElement('a');
|
|
1463
|
-
el.setAttribute('href', `data:application/json;charset=utf-8,${encodeURIComponent(JSON.stringify(json, null, 2))}`);
|
|
1464
|
-
el.setAttribute('download', filename);
|
|
1465
|
-
el.style.display = 'none';
|
|
1466
|
-
document.body.appendChild(el);
|
|
1467
|
-
el.click();
|
|
1468
|
-
document.body.removeChild(el);
|
|
1469
|
-
}
|
|
1470
|
-
/**
|
|
1471
|
-
* Convert quality code into text
|
|
1472
|
-
* @param {number} quality code
|
|
1473
|
-
* @returns {array<string>} lines that decode quality
|
|
1474
|
-
*/
|
|
1475
|
-
static quality2text(quality) {
|
|
1476
|
-
// eslint-disable-next-line no-bitwise
|
|
1477
|
-
const custom = quality & 0xffff0000;
|
|
1478
|
-
const text = QUALITY_BITS[quality];
|
|
1479
|
-
let result;
|
|
1480
|
-
if (text) {
|
|
1481
|
-
result = [text];
|
|
1482
|
-
// eslint-disable-next-line no-bitwise
|
|
1483
|
-
}
|
|
1484
|
-
else if (quality & 0x01) {
|
|
1485
|
-
// eslint-disable-next-line no-bitwise
|
|
1486
|
-
result = [QUALITY_BITS[0x01], `0x${(quality & (0xffff & ~1)).toString(16)}`];
|
|
1487
|
-
// eslint-disable-next-line no-bitwise
|
|
1488
|
-
}
|
|
1489
|
-
else if (quality & 0x02) {
|
|
1490
|
-
// eslint-disable-next-line no-bitwise
|
|
1491
|
-
result = [QUALITY_BITS[0x02], `0x${(quality & (0xffff & ~2)).toString(16)}`];
|
|
1492
|
-
}
|
|
1493
|
-
else {
|
|
1494
|
-
result = [`0x${quality.toString(16)}`];
|
|
1495
|
-
}
|
|
1496
|
-
if (custom) {
|
|
1497
|
-
// eslint-disable-next-line no-bitwise
|
|
1498
|
-
result.push(`0x${(custom >> 16).toString(16).toUpperCase()}`);
|
|
1499
|
-
}
|
|
1500
|
-
return result;
|
|
1501
|
-
}
|
|
1502
|
-
/**
|
|
1503
|
-
* Deep copy object
|
|
1504
|
-
* @param {object} object
|
|
1505
|
-
* @returns {object}
|
|
1506
|
-
*/
|
|
1507
|
-
static clone(object) {
|
|
1508
|
-
return JSON.parse(JSON.stringify(object));
|
|
1509
|
-
}
|
|
1510
|
-
/**
|
|
1511
|
-
* Get states of object
|
|
1512
|
-
* @param {object} obj
|
|
1513
|
-
* @returns {object} states as an object in form {"value1": "label1", "value2": "label2"} or null
|
|
1514
|
-
*/
|
|
1515
|
-
static getStates(obj) {
|
|
1516
|
-
let states = obj?.common?.states;
|
|
1517
|
-
if (states) {
|
|
1518
|
-
if (typeof states === 'string' && states[0] === '{') {
|
|
1519
|
-
try {
|
|
1520
|
-
states = JSON.parse(states);
|
|
1521
|
-
}
|
|
1522
|
-
catch {
|
|
1523
|
-
console.error(`Cannot parse states: ${states}`);
|
|
1524
|
-
states = null;
|
|
1525
|
-
}
|
|
1526
|
-
}
|
|
1527
|
-
else if (typeof states === 'string') {
|
|
1528
|
-
// if old format val1:text1;val2:text2
|
|
1529
|
-
const parts = states.split(';');
|
|
1530
|
-
states = {};
|
|
1531
|
-
for (let p = 0; p < parts.length; p++) {
|
|
1532
|
-
const s = parts[p].split(':');
|
|
1533
|
-
states[s[0]] = s[1];
|
|
1534
|
-
}
|
|
1535
|
-
}
|
|
1536
|
-
else if (Array.isArray(states)) {
|
|
1537
|
-
const result = {};
|
|
1538
|
-
if (obj.common.type === 'number') {
|
|
1539
|
-
states.forEach((value, key) => (result[key] = value));
|
|
1540
|
-
}
|
|
1541
|
-
else if (obj.common.type === 'string') {
|
|
1542
|
-
states.forEach(value => (result[value] = value));
|
|
1543
|
-
}
|
|
1544
|
-
else if (obj.common.type === 'boolean') {
|
|
1545
|
-
result.false = states[0];
|
|
1546
|
-
result.true = states[1];
|
|
1547
|
-
}
|
|
1548
|
-
return result;
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
1551
|
-
return states;
|
|
1552
|
-
}
|
|
1553
|
-
/**
|
|
1554
|
-
* Get svg file as text
|
|
1555
|
-
* @param {string} url URL of SVG file
|
|
1556
|
-
* @returns {object} Promise with "data:image..."
|
|
1557
|
-
*/
|
|
1558
|
-
static getSvg(url) {
|
|
1559
|
-
return fetch(url)
|
|
1560
|
-
.then(response => response.blob())
|
|
1561
|
-
.then(blob => new Promise(resolve => {
|
|
1562
|
-
const reader = new FileReader();
|
|
1563
|
-
// eslint-disable-next-line func-names
|
|
1564
|
-
reader.onload = function () {
|
|
1565
|
-
// do not optimize this function. "this" is important.
|
|
1566
|
-
resolve(this.result);
|
|
1567
|
-
};
|
|
1568
|
-
reader.readAsDataURL(blob);
|
|
1569
|
-
}));
|
|
1570
|
-
}
|
|
1571
|
-
/**
|
|
1572
|
-
* Detect file extension by its content
|
|
1573
|
-
* @param {string} base64 Base64 encoded binary file
|
|
1574
|
-
* @returns {string} Detected extension, like 'jpg'
|
|
1575
|
-
*/
|
|
1576
|
-
static detectMimeType(base64) {
|
|
1577
|
-
const signature = Object.keys(SIGNATURES).find(s => base64.startsWith(s));
|
|
1578
|
-
return signature ? SIGNATURES[signature] : null;
|
|
1579
|
-
}
|
|
1580
|
-
/**
|
|
1581
|
-
* Check if configured repository is the stable repository
|
|
1582
|
-
*
|
|
1583
|
-
* @param {string | string[]} activeRepo current configured repository or multi repository
|
|
1584
|
-
* @return {boolean}
|
|
1585
|
-
*/
|
|
1586
|
-
static isStableRepository(activeRepo) {
|
|
1587
|
-
return !!((typeof activeRepo === 'string' && activeRepo.toLowerCase().startsWith('stable')) ||
|
|
1588
|
-
(activeRepo && typeof activeRepo !== 'string' && activeRepo.find(r => r.toLowerCase().startsWith('stable'))));
|
|
1589
|
-
}
|
|
1590
|
-
/**
|
|
1591
|
-
* Check if given string is an integer
|
|
1592
|
-
*
|
|
1593
|
-
* @param {string} str string to check
|
|
1594
|
-
* @return {boolean}
|
|
1595
|
-
*/
|
|
1596
|
-
static isStringInteger(str) {
|
|
1597
|
-
return parseInt(str).toString() === str;
|
|
1598
|
-
}
|
|
1599
|
-
/**
|
|
1600
|
-
* Check if the date is valid
|
|
1601
|
-
*
|
|
1602
|
-
* @param {Date} date
|
|
1603
|
-
* @return {boolean}
|
|
1604
|
-
*/
|
|
1605
|
-
static isValidDate(date) {
|
|
1606
|
-
// eslint-disable-next-line no-restricted-globals
|
|
1607
|
-
return date instanceof Date && !isNaN(date);
|
|
1608
|
-
}
|
|
1609
|
-
static getStyle(theme, ...args) {
|
|
1610
|
-
const result = {};
|
|
1611
|
-
for (let a = 0; a < args.length; a++) {
|
|
1612
|
-
if (typeof args[a] === 'function') {
|
|
1613
|
-
Object.assign(result, args[a](theme));
|
|
1614
|
-
}
|
|
1615
|
-
else if (args[a] && typeof args[a] === 'object') {
|
|
1616
|
-
Object.keys(args[a]).forEach(attr => {
|
|
1617
|
-
if (typeof args[a][attr] === 'function') {
|
|
1618
|
-
result[attr] = args[a][attr](theme);
|
|
1619
|
-
}
|
|
1620
|
-
else if (typeof args[a][attr] === 'object') {
|
|
1621
|
-
const obj = args[a][attr];
|
|
1622
|
-
result[attr] = result[attr] || {};
|
|
1623
|
-
Object.keys(obj).forEach(attr1 => {
|
|
1624
|
-
if (typeof obj[attr1] === 'function') {
|
|
1625
|
-
result[attr][attr1] = obj(theme);
|
|
1626
|
-
}
|
|
1627
|
-
else if (obj[attr1] || obj[attr1] === 0) {
|
|
1628
|
-
result[attr][attr1] = obj[attr1];
|
|
1629
|
-
}
|
|
1630
|
-
});
|
|
1631
|
-
}
|
|
1632
|
-
else if (args[a][attr] || args[a][attr] === 0) {
|
|
1633
|
-
result[attr] = args[a][attr];
|
|
1634
|
-
}
|
|
1635
|
-
});
|
|
1636
|
-
}
|
|
1637
|
-
}
|
|
1638
|
-
return result;
|
|
1639
|
-
}
|
|
1640
|
-
}
|
|
1641
|
-
export default Utils;
|
|
1642
|
-
//# sourceMappingURL=Utils.js.map
|