2019-08-09 19:13:18 +03:00
|
|
|
///
|
2022-01-17 14:07:46 +02:00
|
|
|
/// Copyright © 2016-2022 The Thingsboard Authors
|
2019-08-09 19:13:18 +03:00
|
|
|
///
|
|
|
|
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
/// you may not use this file except in compliance with the License.
|
|
|
|
|
/// You may obtain a copy of the License at
|
|
|
|
|
///
|
|
|
|
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
///
|
|
|
|
|
/// Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
/// See the License for the specific language governing permissions and
|
|
|
|
|
/// limitations under the License.
|
|
|
|
|
///
|
|
|
|
|
|
2020-02-26 18:15:04 +02:00
|
|
|
import _ from 'lodash';
|
2020-05-05 11:57:35 +03:00
|
|
|
import { Observable, Subject } from 'rxjs';
|
|
|
|
|
import { finalize, share } from 'rxjs/operators';
|
2022-03-28 13:39:26 +03:00
|
|
|
import { Datasource, DatasourceData, FormattedData, ReplaceInfo } from '@app/shared/models/widget.models';
|
2021-03-12 13:07:19 +02:00
|
|
|
import { EntityId } from '@shared/models/id/entity-id';
|
|
|
|
|
import { NULL_UUID } from '@shared/models/id/has-uuid';
|
2022-01-12 19:15:38 +02:00
|
|
|
import { EntityType, baseDetailsPageByEntityType } from '@shared/models/entity-type.models';
|
2019-08-09 19:13:18 +03:00
|
|
|
|
2020-05-05 12:19:21 +03:00
|
|
|
const varsRegex = /\${([^}]*)}/g;
|
|
|
|
|
|
2019-08-09 19:13:18 +03:00
|
|
|
export function onParentScrollOrWindowResize(el: Node): Observable<Event> {
|
|
|
|
|
const scrollSubject = new Subject<Event>();
|
|
|
|
|
const scrollParentNodes = scrollParents(el);
|
|
|
|
|
const eventListenerObject: EventListenerObject = {
|
|
|
|
|
handleEvent(evt: Event) {
|
|
|
|
|
scrollSubject.next(evt);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
scrollParentNodes.forEach((scrollParentNode) => {
|
|
|
|
|
scrollParentNode.addEventListener('scroll', eventListenerObject);
|
|
|
|
|
});
|
|
|
|
|
window.addEventListener('resize', eventListenerObject);
|
2020-05-05 11:57:35 +03:00
|
|
|
return scrollSubject.pipe(
|
2019-08-09 19:13:18 +03:00
|
|
|
finalize(() => {
|
|
|
|
|
scrollParentNodes.forEach((scrollParentNode) => {
|
|
|
|
|
scrollParentNode.removeEventListener('scroll', eventListenerObject);
|
|
|
|
|
});
|
|
|
|
|
window.removeEventListener('resize', eventListenerObject);
|
|
|
|
|
}),
|
|
|
|
|
share()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-20 20:42:48 +03:00
|
|
|
export function isLocalUrl(url: string): boolean {
|
|
|
|
|
const parser = document.createElement('a');
|
|
|
|
|
parser.href = url;
|
|
|
|
|
const host = parser.hostname;
|
2020-05-05 11:57:35 +03:00
|
|
|
return host === 'localhost' || host === '127.0.0.1';
|
2019-08-20 20:42:48 +03:00
|
|
|
}
|
|
|
|
|
|
2019-09-03 19:31:16 +03:00
|
|
|
export function animatedScroll(element: HTMLElement, scrollTop: number, delay?: number) {
|
|
|
|
|
let currentTime = 0;
|
|
|
|
|
const increment = 20;
|
|
|
|
|
const start = element.scrollTop;
|
|
|
|
|
const to = scrollTop;
|
|
|
|
|
const duration = delay ? delay : 0;
|
|
|
|
|
const remaining = to - start;
|
|
|
|
|
const animateScroll = () => {
|
2019-09-25 19:37:29 +03:00
|
|
|
if (duration === 0) {
|
|
|
|
|
element.scrollTop = to;
|
|
|
|
|
} else {
|
|
|
|
|
currentTime += increment;
|
2020-05-05 11:57:35 +03:00
|
|
|
element.scrollTop = easeInOut(currentTime, start, remaining, duration);
|
2019-09-25 19:37:29 +03:00
|
|
|
if (currentTime < duration) {
|
|
|
|
|
setTimeout(animateScroll, increment);
|
|
|
|
|
}
|
2019-09-03 19:31:16 +03:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
animateScroll();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function isUndefined(value: any): boolean {
|
|
|
|
|
return typeof value === 'undefined';
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-07 19:41:04 +03:00
|
|
|
export function isUndefinedOrNull(value: any): boolean {
|
|
|
|
|
return typeof value === 'undefined' || value === null;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-03 19:31:16 +03:00
|
|
|
export function isDefined(value: any): boolean {
|
|
|
|
|
return typeof value !== 'undefined';
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-12 19:58:42 +03:00
|
|
|
export function isDefinedAndNotNull(value: any): boolean {
|
|
|
|
|
return typeof value !== 'undefined' && value !== null;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-12 13:07:09 +03:00
|
|
|
export function isEmptyStr(value: any): boolean {
|
|
|
|
|
return value === '';
|
2020-08-10 00:15:09 +03:00
|
|
|
}
|
|
|
|
|
|
2020-11-20 18:19:53 +02:00
|
|
|
export function isNotEmptyStr(value: any): boolean {
|
|
|
|
|
return value !== null && typeof value === 'string' && value.trim().length > 0;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-05 21:15:40 +03:00
|
|
|
export function isFunction(value: any): boolean {
|
|
|
|
|
return typeof value === 'function';
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-12 19:58:42 +03:00
|
|
|
export function isObject(value: any): boolean {
|
|
|
|
|
return value !== null && typeof value === 'object';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function isNumber(value: any): boolean {
|
|
|
|
|
return typeof value === 'number';
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-04 15:47:36 +02:00
|
|
|
export function isNumeric(value: any): boolean {
|
2020-03-03 19:24:58 +02:00
|
|
|
return (value - parseFloat(value) + 1) >= 0;
|
2019-11-04 15:47:36 +02:00
|
|
|
}
|
|
|
|
|
|
2021-12-21 11:50:05 +02:00
|
|
|
export function isBoolean(value: any): boolean {
|
|
|
|
|
return typeof value === 'boolean';
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-19 20:10:52 +03:00
|
|
|
export function isString(value: any): boolean {
|
|
|
|
|
return typeof value === 'string';
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-05 12:19:21 +03:00
|
|
|
export function isEmpty(obj: any): boolean {
|
|
|
|
|
for (const key of Object.keys(obj)) {
|
|
|
|
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-05 20:56:15 +03:00
|
|
|
export function isLiteralObject(value: any) {
|
|
|
|
|
return (!!value) && (value.constructor === Object);
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-04 15:47:36 +02:00
|
|
|
export function formatValue(value: any, dec?: number, units?: string, showZeroDecimals?: boolean): string | undefined {
|
2020-08-21 15:10:17 +03:00
|
|
|
if (isDefinedAndNotNull(value) && isNumeric(value) &&
|
|
|
|
|
(isDefinedAndNotNull(dec) || isDefinedAndNotNull(units) || Number(value).toString() === value)) {
|
2019-11-04 15:47:36 +02:00
|
|
|
let formatted: string | number = Number(value);
|
2020-08-14 12:07:28 +03:00
|
|
|
if (isDefinedAndNotNull(dec)) {
|
2019-11-04 15:47:36 +02:00
|
|
|
formatted = formatted.toFixed(dec);
|
|
|
|
|
}
|
|
|
|
|
if (!showZeroDecimals) {
|
2020-05-05 11:57:35 +03:00
|
|
|
formatted = (Number(formatted));
|
2019-11-04 15:47:36 +02:00
|
|
|
}
|
|
|
|
|
formatted = formatted.toString();
|
2020-08-14 12:07:28 +03:00
|
|
|
if (isDefinedAndNotNull(units) && units.length > 0) {
|
2019-11-04 15:47:36 +02:00
|
|
|
formatted += ' ' + units;
|
|
|
|
|
}
|
|
|
|
|
return formatted;
|
|
|
|
|
} else {
|
2020-02-05 17:52:18 +02:00
|
|
|
return value !== null ? value : '';
|
2019-11-04 15:47:36 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-05 17:52:18 +02:00
|
|
|
export function objectValues(obj: any): any[] {
|
|
|
|
|
return Object.keys(obj).map(e => obj[e]);
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-10 13:00:29 +03:00
|
|
|
export function deleteNullProperties(obj: any) {
|
|
|
|
|
if (isUndefined(obj) || obj == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
Object.keys(obj).forEach((propName) => {
|
|
|
|
|
if (obj[propName] === null || isUndefined(obj[propName])) {
|
|
|
|
|
delete obj[propName];
|
|
|
|
|
} else if (isObject(obj[propName])) {
|
|
|
|
|
deleteNullProperties(obj[propName]);
|
|
|
|
|
} else if (obj[propName] instanceof Array) {
|
|
|
|
|
(obj[propName] as any[]).forEach((elem) => {
|
|
|
|
|
deleteNullProperties(elem);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-05 21:15:40 +03:00
|
|
|
export function objToBase64(obj: any): string {
|
|
|
|
|
const json = JSON.stringify(obj);
|
2020-08-21 15:10:17 +03:00
|
|
|
return btoa(encodeURIComponent(json).replace(/%([0-9A-F]{2})/g,
|
|
|
|
|
function toSolidBytes(match, p1) {
|
|
|
|
|
return String.fromCharCode(Number('0x' + p1));
|
|
|
|
|
}));
|
2019-09-05 21:15:40 +03:00
|
|
|
}
|
|
|
|
|
|
2022-02-11 15:38:12 +02:00
|
|
|
export function base64toString(b64Encoded: string): string {
|
|
|
|
|
return decodeURIComponent(atob(b64Encoded).split('').map((c) => {
|
|
|
|
|
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
|
|
|
|
}).join(''));
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-21 15:10:17 +03:00
|
|
|
export function objToBase64URI(obj: any): string {
|
|
|
|
|
return encodeURIComponent(objToBase64(obj));
|
2019-09-05 21:15:40 +03:00
|
|
|
}
|
|
|
|
|
|
2020-08-21 15:10:17 +03:00
|
|
|
export function base64toObj(b64Encoded: string): any {
|
|
|
|
|
const json = decodeURIComponent(atob(b64Encoded).split('').map((c) => {
|
|
|
|
|
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
|
|
|
|
}).join(''));
|
|
|
|
|
return JSON.parse(json);
|
2019-09-05 21:15:40 +03:00
|
|
|
}
|
|
|
|
|
|
2019-08-09 19:13:18 +03:00
|
|
|
const scrollRegex = /(auto|scroll)/;
|
|
|
|
|
|
|
|
|
|
function parentNodes(node: Node, nodes: Node[]): Node[] {
|
|
|
|
|
if (node.parentNode === null) {
|
|
|
|
|
return nodes;
|
|
|
|
|
}
|
|
|
|
|
return parentNodes(node.parentNode, nodes.concat([node]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function style(el: Element, prop: string): string {
|
|
|
|
|
return getComputedStyle(el, null).getPropertyValue(prop);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function overflow(el: Element): string {
|
|
|
|
|
return style(el, 'overflow') + style(el, 'overflow-y') + style(el, 'overflow-x');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isScrollNode(node: Node): boolean {
|
|
|
|
|
if (node instanceof Element) {
|
|
|
|
|
return scrollRegex.test(overflow(node));
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function scrollParents(node: Node): Node[] {
|
|
|
|
|
if (!(node instanceof HTMLElement || node instanceof SVGElement)) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
const scrollParentNodes = [];
|
|
|
|
|
const nodeParents = parentNodes(node, []);
|
|
|
|
|
nodeParents.forEach((nodeParent) => {
|
|
|
|
|
if (isScrollNode(nodeParent)) {
|
|
|
|
|
scrollParentNodes.push(nodeParent);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
if (document.scrollingElement) {
|
|
|
|
|
scrollParentNodes.push(document.scrollingElement);
|
|
|
|
|
} else if (document.documentElement) {
|
|
|
|
|
scrollParentNodes.push(document.documentElement);
|
|
|
|
|
}
|
|
|
|
|
return scrollParentNodes;
|
|
|
|
|
}
|
2019-09-03 19:31:16 +03:00
|
|
|
|
2020-05-05 11:57:35 +03:00
|
|
|
export function hashCode(str: string): number {
|
2020-03-18 13:21:46 +02:00
|
|
|
let hash = 0;
|
2020-05-05 11:57:35 +03:00
|
|
|
let i: number;
|
|
|
|
|
let char: number;
|
|
|
|
|
if (str.length === 0) {
|
|
|
|
|
return hash;
|
|
|
|
|
}
|
2020-03-02 12:15:14 +02:00
|
|
|
for (i = 0; i < str.length; i++) {
|
2020-03-03 19:24:58 +02:00
|
|
|
char = str.charCodeAt(i);
|
2020-05-04 11:41:34 +03:00
|
|
|
// tslint:disable-next-line:no-bitwise
|
2020-03-03 19:24:58 +02:00
|
|
|
hash = ((hash << 5) - hash) + char;
|
2020-05-04 11:41:34 +03:00
|
|
|
// tslint:disable-next-line:no-bitwise
|
2020-03-03 19:24:58 +02:00
|
|
|
hash = hash & hash; // Convert to 32bit integer
|
2020-03-02 12:15:14 +02:00
|
|
|
}
|
|
|
|
|
return hash;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-05 11:57:35 +03:00
|
|
|
export function objectHashCode(obj: any): number {
|
|
|
|
|
let hash = 0;
|
|
|
|
|
if (obj) {
|
|
|
|
|
const str = JSON.stringify(obj);
|
|
|
|
|
hash = hashCode(str);
|
|
|
|
|
}
|
|
|
|
|
return hash;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-03 19:31:16 +03:00
|
|
|
function easeInOut(
|
|
|
|
|
currentTime: number,
|
|
|
|
|
startTime: number,
|
|
|
|
|
remainingTime: number,
|
|
|
|
|
duration: number) {
|
|
|
|
|
currentTime /= duration / 2;
|
|
|
|
|
|
|
|
|
|
if (currentTime < 1) {
|
|
|
|
|
return (remainingTime / 2) * currentTime * currentTime + startTime;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
currentTime--;
|
|
|
|
|
return (
|
|
|
|
|
(-remainingTime / 2) * (currentTime * (currentTime - 2) - 1) + startTime
|
|
|
|
|
);
|
|
|
|
|
}
|
2019-09-05 21:15:40 +03:00
|
|
|
|
2020-01-16 12:50:40 +02:00
|
|
|
export function deepClone<T>(target: T, ignoreFields?: string[]): T {
|
2019-09-12 19:58:42 +03:00
|
|
|
if (target === null) {
|
|
|
|
|
return target;
|
|
|
|
|
}
|
|
|
|
|
if (target instanceof Date) {
|
|
|
|
|
return new Date(target.getTime()) as any;
|
|
|
|
|
}
|
|
|
|
|
if (target instanceof Array) {
|
|
|
|
|
const cp = [] as any[];
|
|
|
|
|
(target as any[]).forEach((v) => { cp.push(v); });
|
|
|
|
|
return cp.map((n: any) => deepClone<any>(n)) as any;
|
|
|
|
|
}
|
|
|
|
|
if (typeof target === 'object' && target !== {}) {
|
2021-04-15 11:53:45 +03:00
|
|
|
const cp = {...(target as { [key: string]: any })} as { [key: string]: any };
|
2019-09-12 19:58:42 +03:00
|
|
|
Object.keys(cp).forEach(k => {
|
2020-01-16 12:50:40 +02:00
|
|
|
if (!ignoreFields || ignoreFields.indexOf(k) === -1) {
|
|
|
|
|
cp[k] = deepClone<any>(cp[k]);
|
|
|
|
|
}
|
2019-09-12 19:58:42 +03:00
|
|
|
});
|
|
|
|
|
return cp as T;
|
2019-09-10 15:12:10 +03:00
|
|
|
}
|
2019-09-12 19:58:42 +03:00
|
|
|
return target;
|
2019-09-10 15:12:10 +03:00
|
|
|
}
|
2019-10-31 10:06:57 +02:00
|
|
|
|
2020-02-26 18:15:04 +02:00
|
|
|
export function isEqual(a: any, b: any): boolean {
|
|
|
|
|
return _.isEqual(a, b);
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-23 16:05:28 +02:00
|
|
|
export function mergeDeep<T>(target: T, ...sources: T[]): T {
|
|
|
|
|
return _.merge(target, ...sources);
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-31 10:06:57 +02:00
|
|
|
export function guid(): string {
|
|
|
|
|
function s4(): string {
|
|
|
|
|
return Math.floor((1 + Math.random()) * 0x10000)
|
|
|
|
|
.toString(16)
|
|
|
|
|
.substring(1);
|
|
|
|
|
}
|
|
|
|
|
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
|
|
|
|
|
s4() + '-' + s4() + s4() + s4();
|
|
|
|
|
}
|
2019-12-23 14:36:44 +02:00
|
|
|
|
|
|
|
|
const SNAKE_CASE_REGEXP = /[A-Z]/g;
|
|
|
|
|
|
|
|
|
|
export function snakeCase(name: string, separator: string): string {
|
|
|
|
|
separator = separator || '_';
|
|
|
|
|
return name.replace(SNAKE_CASE_REGEXP, (letter, pos) => {
|
|
|
|
|
return (pos ? separator : '') + letter.toLowerCase();
|
|
|
|
|
});
|
|
|
|
|
}
|
2020-01-24 19:05:41 +02:00
|
|
|
|
|
|
|
|
export function getDescendantProp(obj: any, path: string): any {
|
2021-02-26 17:34:42 +02:00
|
|
|
if (obj.hasOwnProperty(path)) {
|
|
|
|
|
return obj[path];
|
|
|
|
|
}
|
2020-02-04 15:14:17 +02:00
|
|
|
return path.split('.').reduce((acc, part) => acc && acc[part], obj);
|
2020-01-24 19:05:41 +02:00
|
|
|
}
|
2020-03-02 12:15:14 +02:00
|
|
|
|
2020-05-05 12:19:21 +03:00
|
|
|
export function insertVariable(pattern: string, name: string, value: any): string {
|
|
|
|
|
let result = pattern;
|
|
|
|
|
let match = varsRegex.exec(pattern);
|
|
|
|
|
while (match !== null) {
|
|
|
|
|
const variable = match[0];
|
|
|
|
|
const variableName = match[1];
|
|
|
|
|
if (variableName === name) {
|
2020-08-07 19:41:04 +03:00
|
|
|
result = result.replace(variable, value);
|
2020-05-05 12:19:21 +03:00
|
|
|
}
|
|
|
|
|
match = varsRegex.exec(pattern);
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-05 13:53:20 +03:00
|
|
|
export function createLabelFromDatasource(datasource: Datasource, pattern: string): string {
|
2020-04-30 18:52:57 +03:00
|
|
|
let label = pattern;
|
|
|
|
|
if (!datasource) {
|
|
|
|
|
return label;
|
|
|
|
|
}
|
|
|
|
|
let match = varsRegex.exec(pattern);
|
|
|
|
|
while (match !== null) {
|
|
|
|
|
const variable = match[0];
|
|
|
|
|
const variableName = match[1];
|
|
|
|
|
if (variableName === 'dsName') {
|
2020-08-07 19:41:04 +03:00
|
|
|
label = label.replace(variable, datasource.name);
|
2020-04-30 18:52:57 +03:00
|
|
|
} else if (variableName === 'entityName') {
|
2020-08-07 19:41:04 +03:00
|
|
|
label = label.replace(variable, datasource.entityName);
|
2020-04-30 18:52:57 +03:00
|
|
|
} else if (variableName === 'deviceName') {
|
2020-08-07 19:41:04 +03:00
|
|
|
label = label.replace(variable, datasource.entityName);
|
2020-04-30 18:52:57 +03:00
|
|
|
} else if (variableName === 'entityLabel') {
|
2020-08-07 19:41:04 +03:00
|
|
|
label = label.replace(variable, datasource.entityLabel || datasource.entityName);
|
2020-04-30 18:52:57 +03:00
|
|
|
} else if (variableName === 'aliasName') {
|
2020-08-07 19:41:04 +03:00
|
|
|
label = label.replace(variable, datasource.aliasName);
|
2020-04-30 18:52:57 +03:00
|
|
|
} else if (variableName === 'entityDescription') {
|
2020-08-07 19:41:04 +03:00
|
|
|
label = label.replace(variable, datasource.entityDescription);
|
2020-04-30 18:52:57 +03:00
|
|
|
}
|
|
|
|
|
match = varsRegex.exec(pattern);
|
|
|
|
|
}
|
|
|
|
|
return label;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-28 13:39:26 +03:00
|
|
|
export function formattedDataFormDatasourceData(input: DatasourceData[], dataIndex?: number): FormattedData[] {
|
|
|
|
|
return _(input).groupBy(el => el.datasource.entityName + el.datasource.entityType)
|
|
|
|
|
.values().value().map((entityArray, i) => {
|
|
|
|
|
const datasource = entityArray[0].datasource;
|
|
|
|
|
const obj = formattedDataFromDatasource(datasource, i);
|
|
|
|
|
entityArray.filter(el => el.data.length).forEach(el => {
|
|
|
|
|
const index = isDefined(dataIndex) ? dataIndex : el.data.length - 1;
|
|
|
|
|
if (!obj.hasOwnProperty(el.dataKey.label) || el.data[index][1] !== '') {
|
|
|
|
|
obj[el.dataKey.label] = el.data[index][1];
|
|
|
|
|
obj[el.dataKey.label + '|ts'] = el.data[index][0];
|
|
|
|
|
if (el.dataKey.label.toLowerCase() === 'type') {
|
|
|
|
|
obj.deviceType = el.data[index][1];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return obj;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function formattedDataArrayFromDatasourceData(input: DatasourceData[]): FormattedData[][] {
|
|
|
|
|
return _(input).groupBy(el => el.datasource.entityName)
|
|
|
|
|
.values().value().map((entityArray, dsIndex) => {
|
|
|
|
|
const timeDataMap: {[time: number]: FormattedData} = {};
|
|
|
|
|
entityArray.filter(e => e.data.length).forEach(entity => {
|
|
|
|
|
entity.data.forEach(tsData => {
|
|
|
|
|
const time = tsData[0];
|
|
|
|
|
const value = tsData[1];
|
|
|
|
|
let data = timeDataMap[time];
|
|
|
|
|
if (!data) {
|
|
|
|
|
const datasource = entity.datasource;
|
|
|
|
|
data = formattedDataFromDatasource(datasource, dsIndex);
|
|
|
|
|
data.time = time;
|
|
|
|
|
timeDataMap[time] = data;
|
|
|
|
|
}
|
|
|
|
|
data[entity.dataKey.label] = value;
|
|
|
|
|
data[entity.dataKey.label + '|ts'] = time;
|
|
|
|
|
if (entity.dataKey.label.toLowerCase() === 'type') {
|
|
|
|
|
data.deviceType = value;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
return _.values(timeDataMap);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function formattedDataFromDatasource(datasource: Datasource, dsIndex: number): FormattedData {
|
|
|
|
|
return {
|
|
|
|
|
entityName: datasource.entityName,
|
|
|
|
|
deviceName: datasource.entityName,
|
|
|
|
|
entityId: datasource.entityId,
|
|
|
|
|
entityType: datasource.entityType,
|
|
|
|
|
entityLabel: datasource.entityLabel || datasource.entityName,
|
|
|
|
|
entityDescription: datasource.entityDescription,
|
|
|
|
|
aliasName: datasource.aliasName,
|
|
|
|
|
$datasource: datasource,
|
|
|
|
|
dsIndex,
|
|
|
|
|
dsName: datasource.name,
|
|
|
|
|
deviceType: null
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function flatFormattedData(input: FormattedData[]): FormattedData {
|
|
|
|
|
let result: FormattedData = {} as FormattedData;
|
|
|
|
|
if (input.length) {
|
|
|
|
|
for (const toMerge of input) {
|
|
|
|
|
result = {...result, ...toMerge};
|
|
|
|
|
}
|
|
|
|
|
const sourceData = input[0];
|
|
|
|
|
result.entityName = sourceData.entityName;
|
|
|
|
|
result.deviceName = sourceData.deviceName;
|
|
|
|
|
result.entityId = sourceData.entityId;
|
|
|
|
|
result.entityType = sourceData.entityType;
|
|
|
|
|
result.entityLabel = sourceData.entityLabel;
|
|
|
|
|
result.entityDescription = sourceData.entityDescription;
|
|
|
|
|
result.aliasName = sourceData.aliasName;
|
|
|
|
|
result.$datasource = sourceData.$datasource;
|
|
|
|
|
result.dsIndex = sourceData.dsIndex;
|
|
|
|
|
result.dsName = sourceData.dsName;
|
|
|
|
|
result.deviceType = sourceData.deviceType;
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-28 16:25:07 +03:00
|
|
|
export function mergeFormattedData(first: FormattedData[], second: FormattedData[]): FormattedData[] {
|
|
|
|
|
const merged = first.concat(second);
|
|
|
|
|
return _(merged).groupBy(el => el.$datasource)
|
|
|
|
|
.values().value().map((formattedDataArray, i) => {
|
|
|
|
|
let res = formattedDataArray[0];
|
|
|
|
|
if (formattedDataArray.length > 1) {
|
|
|
|
|
const toMerge = formattedDataArray[1];
|
|
|
|
|
res = {...res, ...toMerge};
|
|
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-28 13:39:26 +03:00
|
|
|
export function processDataPattern(pattern: string, data: FormattedData): Array<ReplaceInfo> {
|
|
|
|
|
const replaceInfo: Array<ReplaceInfo> = [];
|
|
|
|
|
try {
|
|
|
|
|
const reg = /\${([^}]*)}/g;
|
|
|
|
|
let match = reg.exec(pattern);
|
|
|
|
|
while (match !== null) {
|
|
|
|
|
const variableInfo: ReplaceInfo = {
|
|
|
|
|
dataKeyName: '',
|
|
|
|
|
valDec: 2,
|
|
|
|
|
variable: ''
|
|
|
|
|
};
|
|
|
|
|
const variable = match[0];
|
|
|
|
|
let label = match[1];
|
|
|
|
|
let valDec = 2;
|
|
|
|
|
const splitValues = label.split(':');
|
|
|
|
|
if (splitValues.length > 1) {
|
|
|
|
|
label = splitValues[0];
|
|
|
|
|
valDec = parseFloat(splitValues[1]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
variableInfo.variable = variable;
|
|
|
|
|
variableInfo.valDec = valDec;
|
|
|
|
|
|
|
|
|
|
if (label.startsWith('#')) {
|
|
|
|
|
const keyIndexStr = label.substring(1);
|
|
|
|
|
const n = Math.floor(Number(keyIndexStr));
|
|
|
|
|
if (String(n) === keyIndexStr && n >= 0) {
|
|
|
|
|
variableInfo.dataKeyName = data.$datasource.dataKeys[n].label;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
variableInfo.dataKeyName = label;
|
|
|
|
|
}
|
|
|
|
|
replaceInfo.push(variableInfo);
|
|
|
|
|
|
|
|
|
|
match = reg.exec(pattern);
|
|
|
|
|
}
|
|
|
|
|
} catch (ex) {
|
|
|
|
|
console.log(ex, pattern);
|
|
|
|
|
}
|
|
|
|
|
return replaceInfo;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function fillDataPattern(pattern: string, replaceInfo: Array<ReplaceInfo>, data: FormattedData) {
|
|
|
|
|
let text = createLabelFromDatasource(data.$datasource, pattern);
|
|
|
|
|
if (replaceInfo) {
|
|
|
|
|
for (const variableInfo of replaceInfo) {
|
|
|
|
|
let txtVal = '';
|
|
|
|
|
if (variableInfo.dataKeyName && isDefinedAndNotNull(data[variableInfo.dataKeyName])) {
|
|
|
|
|
const varData = data[variableInfo.dataKeyName];
|
|
|
|
|
if (isNumber(varData)) {
|
|
|
|
|
txtVal = padValue(varData, variableInfo.valDec);
|
|
|
|
|
} else {
|
|
|
|
|
txtVal = varData;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
text = text.replace(variableInfo.variable, txtVal);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return text;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function createLabelFromPattern(pattern: string, data: FormattedData): string {
|
|
|
|
|
const replaceInfo = processDataPattern(pattern, data);
|
|
|
|
|
return fillDataPattern(pattern, replaceInfo, data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function parseFunction(source: any, params: string[] = ['def']): (...args: any[]) => any {
|
|
|
|
|
let res = null;
|
|
|
|
|
if (source?.length) {
|
|
|
|
|
try {
|
|
|
|
|
res = new Function(...params, source);
|
|
|
|
|
}
|
|
|
|
|
catch (err) {
|
|
|
|
|
res = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function safeExecute(func: (...args: any[]) => any, params = []) {
|
|
|
|
|
let res = null;
|
|
|
|
|
if (func && typeof (func) === 'function') {
|
|
|
|
|
try {
|
|
|
|
|
res = func(...params);
|
|
|
|
|
}
|
|
|
|
|
catch (err) {
|
|
|
|
|
console.log('error in external function:', err);
|
|
|
|
|
res = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-13 14:24:56 +02:00
|
|
|
export function padValue(val: any, dec: number): string {
|
|
|
|
|
let strVal;
|
|
|
|
|
let n;
|
|
|
|
|
|
|
|
|
|
val = parseFloat(val);
|
|
|
|
|
n = (val < 0);
|
|
|
|
|
val = Math.abs(val);
|
|
|
|
|
|
|
|
|
|
if (dec > 0) {
|
2020-05-12 17:24:36 +03:00
|
|
|
strVal = val.toFixed(dec);
|
2020-03-13 14:24:56 +02:00
|
|
|
} else {
|
|
|
|
|
strVal = Math.round(val).toString();
|
|
|
|
|
}
|
|
|
|
|
strVal = (n ? '-' : '') + strVal;
|
|
|
|
|
return strVal;
|
2020-03-23 16:05:28 +02:00
|
|
|
}
|
2020-08-11 13:43:37 +03:00
|
|
|
|
2020-11-27 18:21:12 +02:00
|
|
|
export function baseUrl(): string {
|
|
|
|
|
let url = window.location.protocol + '//' + window.location.hostname;
|
|
|
|
|
const port = window.location.port;
|
|
|
|
|
if (port && port.length > 0 && port !== '80' && port !== '443') {
|
|
|
|
|
url += ':' + port;
|
|
|
|
|
}
|
|
|
|
|
return url;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-11 17:43:19 +03:00
|
|
|
export function sortObjectKeys<T>(obj: T): T {
|
|
|
|
|
return Object.keys(obj).sort().reduce((acc, key) => {
|
|
|
|
|
acc[key] = obj[key];
|
2020-08-11 13:43:37 +03:00
|
|
|
return acc;
|
2020-08-11 17:43:19 +03:00
|
|
|
}, {} as T);
|
2020-08-11 13:43:37 +03:00
|
|
|
}
|
2020-10-12 09:45:34 +03:00
|
|
|
|
|
|
|
|
export function deepTrim<T>(obj: T): T {
|
2021-04-12 16:56:00 +03:00
|
|
|
if (isNumber(obj) || isUndefined(obj) || isString(obj) || obj === null || obj instanceof File) {
|
2020-10-12 09:45:34 +03:00
|
|
|
return obj;
|
|
|
|
|
}
|
|
|
|
|
return Object.keys(obj).reduce((acc, curr) => {
|
|
|
|
|
if (isString(obj[curr])) {
|
|
|
|
|
acc[curr] = obj[curr].trim();
|
|
|
|
|
} else if (isObject(obj[curr])) {
|
|
|
|
|
acc[curr] = deepTrim(obj[curr]);
|
|
|
|
|
} else {
|
|
|
|
|
acc[curr] = obj[curr];
|
|
|
|
|
}
|
|
|
|
|
return acc;
|
|
|
|
|
}, (Array.isArray(obj) ? [] : {}) as T);
|
|
|
|
|
}
|
2020-10-13 11:08:13 +03:00
|
|
|
|
|
|
|
|
export function generateSecret(length?: number): string {
|
|
|
|
|
if (isUndefined(length) || length == null) {
|
|
|
|
|
length = 1;
|
|
|
|
|
}
|
|
|
|
|
const l = length > 10 ? 10 : length;
|
|
|
|
|
const str = Math.random().toString(36).substr(2, l);
|
|
|
|
|
if (str.length >= length) {
|
|
|
|
|
return str;
|
|
|
|
|
}
|
|
|
|
|
return str.concat(generateSecret(length - str.length));
|
|
|
|
|
}
|
2021-03-12 13:07:19 +02:00
|
|
|
|
2021-05-21 18:14:19 +03:00
|
|
|
export function validateEntityId(entityId: EntityId | null): boolean {
|
|
|
|
|
return isDefinedAndNotNull(entityId?.id) && entityId.id !== NULL_UUID && isDefinedAndNotNull(entityId?.entityType);
|
2021-03-12 13:07:19 +02:00
|
|
|
}
|
2021-06-03 18:54:09 +03:00
|
|
|
|
|
|
|
|
export function isMobileApp(): boolean {
|
|
|
|
|
return isDefined((window as any).flutter_inappwebview);
|
|
|
|
|
}
|
2021-06-11 13:37:23 +03:00
|
|
|
|
|
|
|
|
const alphanumericCharacters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
|
|
|
const alphanumericCharactersLength = alphanumericCharacters.length;
|
|
|
|
|
|
|
|
|
|
export function randomAlphanumeric(length: number): string {
|
|
|
|
|
let result = '';
|
|
|
|
|
for ( let i = 0; i < length; i++ ) {
|
|
|
|
|
result += alphanumericCharacters.charAt(Math.floor(Math.random() * alphanumericCharactersLength));
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
2021-12-28 12:01:26 +02:00
|
|
|
|
|
|
|
|
export function getEntityDetailsPageURL(id: string, entityType: EntityType): string {
|
|
|
|
|
return `${baseDetailsPageByEntityType.get(entityType)}/${id}`;
|
|
|
|
|
}
|