2019-09-05 21:15:40 +03:00
|
|
|
///
|
|
|
|
|
/// Copyright © 2016-2019 The Thingsboard Authors
|
|
|
|
|
///
|
|
|
|
|
/// 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.
|
|
|
|
|
///
|
|
|
|
|
|
2019-10-24 19:52:19 +03:00
|
|
|
import { Inject, Injectable, NgZone } from '@angular/core';
|
2019-09-05 21:15:40 +03:00
|
|
|
import { WINDOW } from '@core/services/window.service';
|
|
|
|
|
import { ExceptionData } from '@app/shared/models/error.models';
|
2019-10-31 10:06:57 +02:00
|
|
|
import { deepClone, deleteNullProperties, guid, isDefined, isUndefined } from '@core/utils';
|
2019-09-05 21:15:40 +03:00
|
|
|
import { WindowMessage } from '@shared/models/window-message.model';
|
|
|
|
|
import { TranslateService } from '@ngx-translate/core';
|
|
|
|
|
import { customTranslationsPrefix } from '@app/shared/models/constants';
|
2019-10-10 13:00:29 +03:00
|
|
|
import { DataKey, Datasource, DatasourceType, KeyInfo } from '@shared/models/widget.models';
|
2019-09-10 15:12:10 +03:00
|
|
|
import { EntityType } from '@shared/models/entity-type.models';
|
|
|
|
|
import { DataKeyType } from '@app/shared/models/telemetry/telemetry.models';
|
|
|
|
|
import { alarmFields } from '@shared/models/alarm.models';
|
|
|
|
|
import { materialColors } from '@app/shared/models/material.models';
|
2019-09-19 20:10:52 +03:00
|
|
|
import { WidgetInfo } from '@home/models/widget-component.models';
|
2019-10-10 13:00:29 +03:00
|
|
|
import jsonSchemaDefaults from 'json-schema-defaults';
|
2019-12-12 19:55:17 +02:00
|
|
|
import materialIconsCodepoints from '!raw-loader!material-design-icons/iconfont/codepoints';
|
2019-10-24 19:52:19 +03:00
|
|
|
import { Observable, of, ReplaySubject } from 'rxjs';
|
2019-09-05 21:15:40 +03:00
|
|
|
|
2019-09-20 20:30:43 +03:00
|
|
|
const varsRegex = /\$\{([^}]*)\}/g;
|
|
|
|
|
|
2019-10-10 13:00:29 +03:00
|
|
|
const predefinedFunctions: {[func: string]: string} = {
|
|
|
|
|
Sin: 'return Math.round(1000*Math.sin(time/5000));',
|
|
|
|
|
Cos: 'return Math.round(1000*Math.cos(time/5000));',
|
|
|
|
|
Random: 'var value = prevValue + Math.random() * 100 - 50;\n' +
|
|
|
|
|
'var multiplier = Math.pow(10, 2 || 0);\n' +
|
|
|
|
|
'var value = Math.round(value * multiplier) / multiplier;\n' +
|
|
|
|
|
'if (value < -1000) {\n' +
|
|
|
|
|
' value = -1000;\n' +
|
|
|
|
|
'} else if (value > 1000) {\n' +
|
|
|
|
|
' value = 1000;\n' +
|
|
|
|
|
'}\n' +
|
|
|
|
|
'return value;'
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const predefinedFunctionsList: Array<string> = [];
|
|
|
|
|
for (const func of Object.keys(predefinedFunctions)) {
|
|
|
|
|
predefinedFunctionsList.push(func);
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-11 19:22:03 +03:00
|
|
|
const defaultAlarmFields: Array<string> = [
|
|
|
|
|
alarmFields.createdTime.keyName,
|
|
|
|
|
alarmFields.originator.keyName,
|
|
|
|
|
alarmFields.type.keyName,
|
|
|
|
|
alarmFields.severity.keyName,
|
|
|
|
|
alarmFields.status.keyName
|
|
|
|
|
];
|
|
|
|
|
|
2019-10-24 19:52:19 +03:00
|
|
|
const commonMaterialIcons: Array<string> = [ 'more_horiz', 'more_vert', 'open_in_new',
|
|
|
|
|
'visibility', 'play_arrow', 'arrow_back', 'arrow_downward',
|
|
|
|
|
'arrow_forward', 'arrow_upwards', 'close', 'refresh', 'menu', 'show_chart', 'multiline_chart', 'pie_chart', 'insert_chart', 'people',
|
|
|
|
|
'person', 'domain', 'devices_other', 'now_widgets', 'dashboards', 'map', 'pin_drop', 'my_location', 'extension', 'search',
|
|
|
|
|
'settings', 'notifications', 'notifications_active', 'info', 'info_outline', 'warning', 'list', 'file_download', 'import_export',
|
|
|
|
|
'share', 'add', 'edit', 'done' ];
|
|
|
|
|
|
2019-09-05 21:15:40 +03:00
|
|
|
@Injectable({
|
|
|
|
|
providedIn: 'root'
|
|
|
|
|
})
|
|
|
|
|
export class UtilsService {
|
|
|
|
|
|
|
|
|
|
iframeMode = false;
|
|
|
|
|
widgetEditMode = false;
|
2019-09-19 20:10:52 +03:00
|
|
|
editWidgetInfo: WidgetInfo = null;
|
2019-09-05 21:15:40 +03:00
|
|
|
|
2019-10-10 13:00:29 +03:00
|
|
|
defaultDataKey: DataKey = {
|
|
|
|
|
name: 'f(x)',
|
|
|
|
|
type: DataKeyType.function,
|
|
|
|
|
label: 'Sin',
|
|
|
|
|
color: this.getMaterialColor(0),
|
|
|
|
|
funcBody: this.getPredefinedFunctionBody('Sin'),
|
|
|
|
|
settings: {},
|
|
|
|
|
_hash: Math.random()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
defaultDatasource: Datasource = {
|
|
|
|
|
type: DatasourceType.function,
|
|
|
|
|
name: DatasourceType.function,
|
|
|
|
|
dataKeys: [deepClone(this.defaultDataKey)]
|
|
|
|
|
};
|
|
|
|
|
|
2019-10-11 19:22:03 +03:00
|
|
|
defaultAlarmDataKeys: Array<DataKey> = [];
|
|
|
|
|
|
2019-10-24 19:52:19 +03:00
|
|
|
materialIcons: Array<string> = [];
|
|
|
|
|
|
2019-09-05 21:15:40 +03:00
|
|
|
constructor(@Inject(WINDOW) private window: Window,
|
2019-10-24 19:52:19 +03:00
|
|
|
private zone: NgZone,
|
2019-09-05 21:15:40 +03:00
|
|
|
private translate: TranslateService) {
|
|
|
|
|
let frame: Element = null;
|
|
|
|
|
try {
|
|
|
|
|
frame = window.frameElement;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
// ie11 fix
|
|
|
|
|
}
|
|
|
|
|
if (frame) {
|
|
|
|
|
this.iframeMode = true;
|
|
|
|
|
const dataWidgetAttr = frame.getAttribute('data-widget');
|
|
|
|
|
if (dataWidgetAttr && dataWidgetAttr.length) {
|
|
|
|
|
this.editWidgetInfo = JSON.parse(dataWidgetAttr);
|
|
|
|
|
this.widgetEditMode = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-10 13:00:29 +03:00
|
|
|
public getPredefinedFunctionsList(): Array<string> {
|
|
|
|
|
return predefinedFunctionsList;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public getPredefinedFunctionBody(func: string): string {
|
|
|
|
|
return predefinedFunctions[func];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public getDefaultDatasource(dataKeySchema: any): Datasource {
|
|
|
|
|
const datasource = deepClone(this.defaultDatasource);
|
|
|
|
|
if (isDefined(dataKeySchema)) {
|
|
|
|
|
datasource.dataKeys[0].settings = this.generateObjectFromJsonSchema(dataKeySchema);
|
|
|
|
|
}
|
|
|
|
|
return datasource;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-11 19:22:03 +03:00
|
|
|
private initDefaultAlarmDataKeys() {
|
|
|
|
|
for (let i = 0; i < defaultAlarmFields.length; i++) {
|
|
|
|
|
const name = defaultAlarmFields[i];
|
|
|
|
|
const dataKey: DataKey = {
|
|
|
|
|
name,
|
|
|
|
|
type: DataKeyType.alarm,
|
|
|
|
|
label: this.translate.instant(alarmFields[name].name),
|
|
|
|
|
color: this.getMaterialColor(i),
|
|
|
|
|
settings: {},
|
|
|
|
|
_hash: Math.random()
|
|
|
|
|
};
|
|
|
|
|
this.defaultAlarmDataKeys.push(dataKey);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public getDefaultAlarmDataKeys(): Array<DataKey> {
|
|
|
|
|
if (!this.defaultAlarmDataKeys.length) {
|
|
|
|
|
this.initDefaultAlarmDataKeys();
|
|
|
|
|
}
|
|
|
|
|
return deepClone(this.defaultAlarmDataKeys);
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-10 13:00:29 +03:00
|
|
|
public generateObjectFromJsonSchema(schema: any): any {
|
|
|
|
|
const obj = jsonSchemaDefaults(schema);
|
|
|
|
|
deleteNullProperties(obj);
|
|
|
|
|
return obj;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-12 19:58:42 +03:00
|
|
|
public hashCode(str: string): number {
|
|
|
|
|
let hash = 0;
|
|
|
|
|
let i: number;
|
|
|
|
|
let char: number;
|
|
|
|
|
if (str.length === 0) {
|
|
|
|
|
return hash;
|
|
|
|
|
}
|
|
|
|
|
for (i = 0; i < str.length; i++) {
|
|
|
|
|
char = str.charCodeAt(i);
|
|
|
|
|
// tslint:disable-next-line:no-bitwise
|
|
|
|
|
hash = ((hash << 5) - hash) + char;
|
|
|
|
|
// tslint:disable-next-line:no-bitwise
|
|
|
|
|
hash = hash & hash; // Convert to 32bit integer
|
|
|
|
|
}
|
|
|
|
|
return hash;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public objectHashCode(obj: any): number {
|
|
|
|
|
let hash = 0;
|
|
|
|
|
if (obj) {
|
|
|
|
|
const str = JSON.stringify(obj);
|
|
|
|
|
hash = this.hashCode(str);
|
|
|
|
|
}
|
|
|
|
|
return hash;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-05 21:15:40 +03:00
|
|
|
public processWidgetException(exception: any): ExceptionData {
|
2019-09-23 20:35:31 +03:00
|
|
|
const data = this.parseException(exception, -6);
|
2019-09-05 21:15:40 +03:00
|
|
|
if (this.widgetEditMode) {
|
|
|
|
|
const message: WindowMessage = {
|
|
|
|
|
type: 'widgetException',
|
|
|
|
|
data
|
|
|
|
|
};
|
2019-09-19 20:10:52 +03:00
|
|
|
this.window.parent.postMessage(JSON.stringify(message), '*');
|
2019-09-05 21:15:40 +03:00
|
|
|
}
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public parseException(exception: any, lineOffset?: number): ExceptionData {
|
|
|
|
|
const data: ExceptionData = {};
|
|
|
|
|
if (exception) {
|
|
|
|
|
if (typeof exception === 'string') {
|
|
|
|
|
data.message = exception;
|
|
|
|
|
} else if (exception instanceof String) {
|
|
|
|
|
data.message = exception.toString();
|
|
|
|
|
} else {
|
|
|
|
|
if (exception.name) {
|
|
|
|
|
data.name = exception.name;
|
|
|
|
|
} else {
|
|
|
|
|
data.name = 'UnknownError';
|
|
|
|
|
}
|
|
|
|
|
if (exception.message) {
|
|
|
|
|
data.message = exception.message;
|
|
|
|
|
}
|
|
|
|
|
if (exception.lineNumber) {
|
|
|
|
|
data.lineNumber = exception.lineNumber;
|
|
|
|
|
if (exception.columnNumber) {
|
|
|
|
|
data.columnNumber = exception.columnNumber;
|
|
|
|
|
}
|
|
|
|
|
} else if (exception.stack) {
|
|
|
|
|
const lineInfoRegexp = /(.*<anonymous>):(\d*)(:)?(\d*)?/g;
|
|
|
|
|
const lineInfoGroups = lineInfoRegexp.exec(exception.stack);
|
|
|
|
|
if (lineInfoGroups != null && lineInfoGroups.length >= 3) {
|
|
|
|
|
if (isUndefined(lineOffset)) {
|
|
|
|
|
lineOffset = -2;
|
|
|
|
|
}
|
|
|
|
|
data.lineNumber = Number(lineInfoGroups[2]) + lineOffset;
|
|
|
|
|
if (lineInfoGroups.length >= 5) {
|
|
|
|
|
data.columnNumber = Number(lineInfoGroups[4]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public customTranslation(translationValue: string, defaultValue: string): string {
|
|
|
|
|
let result = '';
|
|
|
|
|
const translationId = customTranslationsPrefix + translationValue;
|
|
|
|
|
const translation = this.translate.instant(translationId);
|
|
|
|
|
if (translation !== translationId) {
|
|
|
|
|
result = translation + '';
|
|
|
|
|
} else {
|
|
|
|
|
result = defaultValue;
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-20 20:30:43 +03:00
|
|
|
public insertVariable(pattern: string, name: string, value: any): string {
|
|
|
|
|
let result = deepClone(pattern);
|
|
|
|
|
let match = varsRegex.exec(pattern);
|
|
|
|
|
while (match !== null) {
|
|
|
|
|
const variable = match[0];
|
|
|
|
|
const variableName = match[1];
|
|
|
|
|
if (variableName === name) {
|
|
|
|
|
result = result.split(variable).join(value);
|
|
|
|
|
}
|
|
|
|
|
match = varsRegex.exec(pattern);
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-10 15:12:10 +03:00
|
|
|
public guid(): string {
|
2019-10-31 10:06:57 +02:00
|
|
|
return guid();
|
2019-09-10 15:12:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public validateDatasources(datasources: Array<Datasource>): Array<Datasource> {
|
|
|
|
|
datasources.forEach((datasource) => {
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
if (datasource.type === 'device') {
|
|
|
|
|
datasource.type = DatasourceType.entity;
|
|
|
|
|
datasource.entityType = EntityType.DEVICE;
|
|
|
|
|
if (datasource.deviceId) {
|
|
|
|
|
datasource.entityId = datasource.deviceId;
|
|
|
|
|
} else if (datasource.deviceAliasId) {
|
|
|
|
|
datasource.entityAliasId = datasource.deviceAliasId;
|
|
|
|
|
}
|
|
|
|
|
if (datasource.deviceName) {
|
|
|
|
|
datasource.entityName = datasource.deviceName;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (datasource.type === DatasourceType.entity && datasource.entityId) {
|
|
|
|
|
datasource.name = datasource.entityName;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return datasources;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-24 19:52:19 +03:00
|
|
|
public getMaterialIcons(): Observable<Array<string>> {
|
|
|
|
|
if (this.materialIcons.length) {
|
|
|
|
|
return of(this.materialIcons);
|
|
|
|
|
} else {
|
|
|
|
|
const materialIconsSubject = new ReplaySubject<Array<string>>();
|
|
|
|
|
this.zone.runOutsideAngular(() => {
|
|
|
|
|
const codepointsArray = materialIconsCodepoints
|
|
|
|
|
.split('\n')
|
|
|
|
|
.filter((codepoint) => codepoint && codepoint.length);
|
|
|
|
|
codepointsArray.forEach((codepoint) => {
|
|
|
|
|
const values = codepoint.split(' ');
|
|
|
|
|
if (values && values.length === 2) {
|
|
|
|
|
this.materialIcons.push(values[0]);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
materialIconsSubject.next(this.materialIcons);
|
|
|
|
|
});
|
|
|
|
|
return materialIconsSubject.asObservable();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public getCommonMaterialIcons(): Array<string> {
|
|
|
|
|
return commonMaterialIcons;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-10 13:00:29 +03:00
|
|
|
public getMaterialColor(index: number) {
|
2019-09-10 15:12:10 +03:00
|
|
|
const colorIndex = index % materialColors.length;
|
|
|
|
|
return materialColors[colorIndex].value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public createKey(keyInfo: KeyInfo, type: DataKeyType, index: number = -1): DataKey {
|
|
|
|
|
let label;
|
|
|
|
|
if (type === DataKeyType.alarm && !keyInfo.label) {
|
|
|
|
|
const alarmField = alarmFields[keyInfo.name];
|
|
|
|
|
if (alarmField) {
|
|
|
|
|
label = this.translate.instant(alarmField.name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!label) {
|
|
|
|
|
label = keyInfo.label || keyInfo.name;
|
|
|
|
|
}
|
|
|
|
|
const dataKey: DataKey = {
|
|
|
|
|
name: keyInfo.name,
|
|
|
|
|
type,
|
|
|
|
|
label,
|
|
|
|
|
funcBody: keyInfo.funcBody,
|
|
|
|
|
settings: {},
|
|
|
|
|
_hash: Math.random()
|
|
|
|
|
};
|
|
|
|
|
if (keyInfo.units) {
|
|
|
|
|
dataKey.units = keyInfo.units;
|
|
|
|
|
}
|
|
|
|
|
if (isDefined(keyInfo.decimals)) {
|
|
|
|
|
dataKey.decimals = keyInfo.decimals;
|
|
|
|
|
}
|
|
|
|
|
if (keyInfo.color) {
|
|
|
|
|
dataKey.color = keyInfo.color;
|
|
|
|
|
} else if (index > -1) {
|
|
|
|
|
dataKey.color = this.getMaterialColor(index);
|
|
|
|
|
}
|
|
|
|
|
if (keyInfo.postFuncBody && keyInfo.postFuncBody.length) {
|
|
|
|
|
dataKey.usePostProcessing = true;
|
|
|
|
|
dataKey.postFuncBody = keyInfo.postFuncBody;
|
|
|
|
|
}
|
|
|
|
|
return dataKey;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-11 19:22:03 +03:00
|
|
|
public createLabelFromDatasource(datasource: Datasource, pattern: string) {
|
|
|
|
|
let label = deepClone(pattern);
|
|
|
|
|
let match = varsRegex.exec(pattern);
|
|
|
|
|
while (match !== null) {
|
|
|
|
|
const variable = match[0];
|
|
|
|
|
const variableName = match[1];
|
|
|
|
|
if (variableName === 'dsName') {
|
|
|
|
|
label = label.split(variable).join(datasource.name);
|
|
|
|
|
} else if (variableName === 'entityName') {
|
|
|
|
|
label = label.split(variable).join(datasource.entityName);
|
|
|
|
|
} else if (variableName === 'deviceName') {
|
|
|
|
|
label = label.split(variable).join(datasource.entityName);
|
|
|
|
|
} else if (variableName === 'entityLabel') {
|
|
|
|
|
label = label.split(variable).join(datasource.entityLabel);
|
|
|
|
|
} else if (variableName === 'aliasName') {
|
|
|
|
|
label = label.split(variable).join(datasource.aliasName);
|
|
|
|
|
} else if (variableName === 'entityDescription') {
|
|
|
|
|
label = label.split(variable).join(datasource.entityDescription);
|
|
|
|
|
}
|
|
|
|
|
match = varsRegex.exec(pattern);
|
|
|
|
|
}
|
|
|
|
|
return label;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-10 15:12:10 +03:00
|
|
|
public generateColors(datasources: Array<Datasource>) {
|
|
|
|
|
let index = 0;
|
|
|
|
|
datasources.forEach((datasource) => {
|
|
|
|
|
datasource.dataKeys.forEach((dataKey) => {
|
|
|
|
|
if (!dataKey.color) {
|
|
|
|
|
dataKey.color = this.getMaterialColor(index);
|
|
|
|
|
}
|
|
|
|
|
index++;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-12 19:58:42 +03:00
|
|
|
public currentPerfTime(): number {
|
|
|
|
|
return this.window.performance && this.window.performance.now ?
|
|
|
|
|
this.window.performance.now() : Date.now();
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-15 12:22:14 +02:00
|
|
|
public getQueryParam(name: string): string {
|
|
|
|
|
const url = this.window.location.href;
|
|
|
|
|
name = name.replace(/[\[\]]/g, '\\$&');
|
|
|
|
|
const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)');
|
|
|
|
|
const results = regex.exec(url);
|
|
|
|
|
if (!results) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
if (!results[2]) {
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
return decodeURIComponent(results[2].replace(/\+/g, ' '));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public updateQueryParam(name: string, value: string | null) {
|
|
|
|
|
const baseUrl = [this.window.location.protocol, '//', this.window.location.host, this.window.location.pathname].join('');
|
|
|
|
|
const urlQueryString = this.window.location.search;
|
|
|
|
|
let newParam = '';
|
|
|
|
|
let params = '';
|
|
|
|
|
if (value !== null) {
|
|
|
|
|
newParam = name + '=' + value;
|
|
|
|
|
}
|
|
|
|
|
if (urlQueryString) {
|
|
|
|
|
const keyRegex = new RegExp('([\?&])' + name + '[^&]*');
|
|
|
|
|
if (urlQueryString.match(keyRegex) !== null) {
|
|
|
|
|
if (newParam) {
|
|
|
|
|
newParam = '$1' + newParam;
|
|
|
|
|
}
|
|
|
|
|
params = urlQueryString.replace(keyRegex, newParam);
|
|
|
|
|
} else if (newParam) {
|
|
|
|
|
params = urlQueryString + '&' + newParam;
|
|
|
|
|
}
|
|
|
|
|
} else if (newParam) {
|
|
|
|
|
params = '?' + newParam;
|
|
|
|
|
}
|
|
|
|
|
this.window.history.replaceState({}, '', baseUrl + params);
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-05 21:15:40 +03:00
|
|
|
}
|