Commit 538abeb14067c39e99bac100389ee403990d6baa

Authored by Igor Kulikov
1 parent bd142f86

Input widgets

... ... @@ -29,8 +29,10 @@ export class NotificationMessage {
29 29 type: NotificationType;
30 30 target?: string;
31 31 duration?: number;
  32 + forceDismiss?: boolean;
32 33 horizontalPosition?: NotificationHorizontalPosition;
33 34 verticalPosition?: NotificationVerticalPosition;
  35 + panelClass?: string | string[];
34 36 }
35 37
36 38 export class HideNotification {
... ...
... ... @@ -20,7 +20,7 @@
20 20 import { Inject, Injectable, NgZone } from '@angular/core';
21 21 import { WINDOW } from '@core/services/window.service';
22 22 import { ExceptionData } from '@app/shared/models/error.models';
23   -import { deepClone, deleteNullProperties, guid, isDefined, isUndefined } from '@core/utils';
  23 +import { deepClone, deleteNullProperties, guid, isDefined, isDefinedAndNotNull, isUndefined } from '@core/utils';
24 24 import { WindowMessage } from '@shared/models/window-message.model';
25 25 import { TranslateService } from '@ngx-translate/core';
26 26 import { customTranslationsPrefix } from '@app/shared/models/constants';
... ... @@ -441,4 +441,23 @@ export class UtilsService {
441 441 this.window.history.replaceState({}, '', baseUrl + params);
442 442 }
443 443
  444 + public deepClone<T>(target: T, ignoreFields?: string[]): T {
  445 + return deepClone(target, ignoreFields);
  446 + }
  447 +
  448 + public isUndefined(value: any): boolean {
  449 + return isUndefined(value);
  450 + }
  451 +
  452 + public isDefined(value: any): boolean {
  453 + return isDefined(value);
  454 + }
  455 +
  456 + public defaultValue(value: any, defaultValue: any): any {
  457 + if (isDefinedAndNotNull(value)) {
  458 + return value;
  459 + } else {
  460 + return defaultValue;
  461 + }
  462 + }
444 463 }
... ...
... ... @@ -15,14 +15,19 @@
15 15 ///
16 16
17 17 import { PageComponent } from '@shared/components/page.component';
18   -import { Inject, Injector, Input, OnDestroy, OnInit } from '@angular/core';
  18 +import { Inject, Injector, OnDestroy, OnInit } from '@angular/core';
19 19 import { Store } from '@ngrx/store';
20 20 import { AppState } from '@core/core.state';
21   -import { WidgetContext, IDynamicWidgetComponent } from '@home/models/widget-component.models';
22   -import { ExceptionData } from '@shared/models/error.models';
  21 +import { IDynamicWidgetComponent, WidgetContext } from '@home/models/widget-component.models';
23 22 import { HttpErrorResponse } from '@angular/common/http';
24 23 import { RafService } from '@core/services/raf.service';
25   -import { DeviceService } from '@core/http/device.service';
  24 +import { ActionNotificationShow } from '@core/notification/notification.actions';
  25 +import {
  26 + NotificationHorizontalPosition,
  27 + NotificationType,
  28 + NotificationVerticalPosition
  29 +} from '@core/notification/notification.models';
  30 +import { FormBuilder, Validators } from '@angular/forms';
26 31
27 32 export class DynamicWidgetComponent extends PageComponent implements IDynamicWidgetComponent, OnInit, OnDestroy {
28 33
... ... @@ -33,8 +38,11 @@ export class DynamicWidgetComponent extends PageComponent implements IDynamicWid
33 38
34 39 [key: string]: any;
35 40
  41 + validators = Validators;
  42 +
36 43 constructor(@Inject(RafService) public raf: RafService,
37 44 @Inject(Store) protected store: Store<AppState>,
  45 + @Inject(FormBuilder) public fb: FormBuilder,
38 46 @Inject(Injector) private $injector: Injector,
39 47 @Inject('widgetContext') public readonly ctx: WidgetContext,
40 48 @Inject('errorMessages') public readonly errorMessages: string[]) {
... ... @@ -63,4 +71,35 @@ export class DynamicWidgetComponent extends PageComponent implements IDynamicWid
63 71 }
64 72 }
65 73
  74 + showSuccessToast(message: string, duration: number = 1000,
  75 + verticalPosition: NotificationVerticalPosition = 'bottom',
  76 + horizontalPosition: NotificationHorizontalPosition = 'left',
  77 + target?: string) {
  78 + this.showToast('success', message, duration, verticalPosition, horizontalPosition, target);
  79 + }
  80 +
  81 + showErrorToast(message: string,
  82 + verticalPosition: NotificationVerticalPosition = 'bottom',
  83 + horizontalPosition: NotificationHorizontalPosition = 'left',
  84 + target?: string) {
  85 + this.showToast('error', message, undefined, verticalPosition, horizontalPosition, target);
  86 + }
  87 +
  88 + showToast(type: NotificationType, message: string, duration: number = 1000,
  89 + verticalPosition: NotificationVerticalPosition = 'bottom',
  90 + horizontalPosition: NotificationHorizontalPosition = 'left',
  91 + target?: string) {
  92 + this.store.dispatch(new ActionNotificationShow(
  93 + {
  94 + message,
  95 + type,
  96 + duration,
  97 + verticalPosition,
  98 + horizontalPosition,
  99 + target,
  100 + panelClass: this.ctx.widgetNamespace,
  101 + forceDismiss: true
  102 + }));
  103 + }
  104 +
66 105 }
... ...
... ... @@ -95,6 +95,8 @@ import { DialogService } from '@core/services/dialog.service';
95 95 import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service';
96 96 import { DatePipe } from '@angular/common';
97 97 import { AttributeService } from '@core/http/attribute.service';
  98 +import { TranslateService } from '@ngx-translate/core';
  99 +import { HttpClient } from '@angular/common/http';
98 100
99 101 const ServicesMap = new Map<string, Type<any>>();
100 102 ServicesMap.set('deviceService', DeviceService);
... ... @@ -104,6 +106,8 @@ ServicesMap.set('dialogs', DialogService);
104 106 ServicesMap.set('customDialog', CustomDialogService);
105 107 ServicesMap.set('date', DatePipe);
106 108 ServicesMap.set('utils', UtilsService);
  109 +ServicesMap.set('translate', TranslateService);
  110 +ServicesMap.set('http', HttpClient);
107 111
108 112 @Component({
109 113 selector: 'tb-widget',
... ... @@ -390,10 +394,10 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
390 394 }
391 395
392 396 private loadFromWidgetInfo() {
393   - const widgetNamespace = `widget-type-${(this.widget.isSystemType ? 'sys-' : '')}${this.widget.bundleAlias}-${this.widget.typeAlias}`;
  397 + this.widgetContext.widgetNamespace = `widget-type-${(this.widget.isSystemType ? 'sys-' : '')}${this.widget.bundleAlias}-${this.widget.typeAlias}`;
394 398 const elem = this.elementRef.nativeElement;
395 399 elem.classList.add('tb-widget');
396   - elem.classList.add(widgetNamespace);
  400 + elem.classList.add(this.widgetContext.widgetNamespace);
397 401 this.widgetType = this.widgetInfo.widgetTypeFunction;
398 402 this.typeParameters = this.widgetInfo.typeParameters;
399 403
... ...
... ... @@ -161,6 +161,7 @@ export class WidgetContext {
161 161 isEdit: boolean;
162 162 isMobile: boolean;
163 163
  164 + widgetNamespace?: string;
164 165 subscriptionApi?: WidgetSubscriptionApi;
165 166
166 167 actionsApi?: WidgetActionsApi;
... ...
... ... @@ -22,5 +22,5 @@
22 22 'info-toast': notification.type === 'info'
23 23 }">
24 24 <div class="toast-text" [innerHTML]="notification.message"></div>
25   - <button mat-button (click)="action()">{{ 'action.close' | translate }}</button>
  25 + <button #actionButton mat-button (click)="action()">{{ 'action.close' | translate }}</button>
26 26 </div>
... ...
... ... @@ -15,12 +15,12 @@
15 15 ///
16 16
17 17 import {
18   - AfterViewInit,
  18 + AfterViewInit, ApplicationRef, ChangeDetectorRef,
19 19 Component,
20 20 Directive,
21 21 ElementRef,
22 22 Inject, Input, NgZone,
23   - OnDestroy,
  23 + OnDestroy, ViewChild,
24 24 ViewContainerRef
25 25 } from '@angular/core';
26 26 import {
... ... @@ -35,6 +35,8 @@ import { Subscription } from 'rxjs';
35 35 import { NotificationService } from '@app/core/services/notification.service';
36 36 import { BreakpointObserver } from '@angular/cdk/layout';
37 37 import { MediaBreakpoints } from '@shared/models/constants';
  38 +import Timeout = NodeJS.Timeout;
  39 +import { MatButton } from '@angular/material/button';
38 40
39 41 @Directive({
40 42 selector: '[tb-toast]'
... ... @@ -50,6 +52,8 @@ export class ToastDirective implements AfterViewInit, OnDestroy {
50 52 private snackBarRef: MatSnackBarRef<TbSnackBarComponent> = null;
51 53 private currentMessage: NotificationMessage = null;
52 54
  55 + private dismissTimeout: Timeout = null;
  56 +
53 57 constructor(public elementRef: ElementRef,
54 58 public viewContainerRef: ViewContainerRef,
55 59 private notificationService: NotificationService,
... ... @@ -73,6 +77,7 @@ export class ToastDirective implements AfterViewInit, OnDestroy {
73 77 verticalPosition: !isGtSm ? 'bottom' : (notificationMessage.verticalPosition || 'top'),
74 78 viewContainerRef: this.viewContainerRef,
75 79 duration: notificationMessage.duration,
  80 + panelClass: notificationMessage.panelClass,
76 81 data
77 82 };
78 83 this.ngZone.run(() => {
... ... @@ -80,7 +85,23 @@ export class ToastDirective implements AfterViewInit, OnDestroy {
80 85 this.snackBarRef.dismiss();
81 86 }
82 87 this.snackBarRef = this.snackBar.openFromComponent(TbSnackBarComponent, config);
  88 + if (notificationMessage.duration && notificationMessage.duration > 0 && notificationMessage.forceDismiss) {
  89 + if (this.dismissTimeout !== null) {
  90 + clearTimeout(this.dismissTimeout);
  91 + this.dismissTimeout = null;
  92 + }
  93 + this.dismissTimeout = setTimeout(() => {
  94 + if (this.snackBarRef) {
  95 + this.snackBarRef.instance.actionButton._elementRef.nativeElement.click();
  96 + }
  97 + this.dismissTimeout = null;
  98 + }, notificationMessage.duration);
  99 + }
83 100 this.snackBarRef.afterDismissed().subscribe(() => {
  101 + if (this.dismissTimeout !== null) {
  102 + clearTimeout(this.dismissTimeout);
  103 + this.dismissTimeout = null;
  104 + }
84 105 this.snackBarRef = null;
85 106 this.currentMessage = null;
86 107 });
... ... @@ -134,11 +155,15 @@ export class ToastDirective implements AfterViewInit, OnDestroy {
134 155 styleUrls: ['snack-bar-component.scss']
135 156 })
136 157 export class TbSnackBarComponent implements AfterViewInit, OnDestroy {
  158 +
  159 + @ViewChild('actionButton', {static: true}) actionButton: MatButton;
  160 +
137 161 private parentEl: HTMLElement;
138   - private snackBarContainerEl: HTMLElement;
  162 + public snackBarContainerEl: HTMLElement;
139 163 private parentScrollSubscription: Subscription = null;
140 164 public notification: NotificationMessage;
141 165 constructor(@Inject(MAT_SNACK_BAR_DATA) public data: any, private elementRef: ElementRef,
  166 + public cd: ChangeDetectorRef,
142 167 public snackBarRef: MatSnackBarRef<TbSnackBarComponent>) {
143 168 this.notification = data.notification;
144 169 }
... ...
... ... @@ -44,7 +44,6 @@ export class KeyboardShortcutPipe implements PipeTransform {
44 44 const last = index === keys.length - 1;
45 45 return last ? key : abbreviations[key];
46 46 }).join(seperator);
47   - return (!value) ? '' : value.replace(/ /g, '');
48 47 }
49 48
50 49 }
... ...
... ... @@ -1607,6 +1607,31 @@
1607 1607 "Step size": "Schrittlänge",
1608 1608 "Ok": "Ok"
1609 1609 }
  1610 + },
  1611 + "input-widgets": {
  1612 + "attribute-not-allowed": "Attributparameter können in diesem Widget nicht verwendet werden",
  1613 + "date": "Datum",
  1614 + "discard-changes": "Änderungen verwerfen",
  1615 + "entity-attribute-required": "Entitätsattribut ist erforderlich",
  1616 + "entity-timeseries-required": "Zeitreihen für Entitäten sind erforderlich",
  1617 + "not-allowed-entity": "Die ausgewählte Entität kann keine gemeinsamen Attribute haben",
  1618 + "no-attribute-selected": "Es ist kein Attribut ausgewählt",
  1619 + "no-datakey-selected": "Es ist kein Datenschlüssel ausgewählt",
  1620 + "no-entity-selected": "Keine Entität ausgewählt",
  1621 + "no-image": "Kein Bild",
  1622 + "no-support-web-camera": "Keine unterstützte Webcam",
  1623 + "no-timeseries-selected": "Keine Zeitreihen ausgewählt",
  1624 + "switch-attribute-value": "Entitätsattributwert wechseln",
  1625 + "switch-camera": "Kamera wechseln",
  1626 + "switch-timeseries-value": "Wert für Zeitreihen von Entitäten wechseln",
  1627 + "take-photo": "Foto machen",
  1628 + "time": "Zeit",
  1629 + "timeseries-not-allowed": "Der Timeseries-Parameter kann in diesem Widget nicht verwendet werden",
  1630 + "update-failed": "Aktualisierung fehlgeschlagen",
  1631 + "update-successful": "Aktualisierung erfolgreich",
  1632 + "update-attribute": "Attribut aktualisieren",
  1633 + "update-timeseries": "Zeitreihen aktualisieren",
  1634 + "value": "Wert"
1610 1635 }
1611 1636 },
1612 1637 "icon": {
... ... @@ -1641,7 +1666,7 @@
1641 1666 "tr_TR": "Türkisch",
1642 1667 "fa_IR": "Persisch",
1643 1668 "uk_UA": "Ukrainisch",
1644   - "cs_CZ": "Tschechisch"
  1669 + "cs_CZ": "Tschechisch"
1645 1670 }
1646 1671 }
1647 1672 }
... ...
... ... @@ -1728,6 +1728,46 @@
1728 1728 "Step size": "Step size",
1729 1729 "Ok": "Ok"
1730 1730 }
  1731 + },
  1732 + "input-widgets": {
  1733 + "attribute-not-allowed": "Attribute parameter cannot be used in this widget",
  1734 + "blocked-location": "Geolocation is blocked in your browser",
  1735 + "claim-device": "Claim device",
  1736 + "claim-failed": "Failed to claim the device!",
  1737 + "claim-not-found": "Device not found!",
  1738 + "claim-successful": "Device was successfully claimed!",
  1739 + "date": "Date",
  1740 + "device-name": "Device name",
  1741 + "device-name-required": "Device name is required",
  1742 + "discard-changes": "Discard changes",
  1743 + "entity-attribute-required": "Entity attribute is required",
  1744 + "entity-coordinate-required": "Both fields, latitude and longitude, are required",
  1745 + "entity-timeseries-required": "Entity timeseries is required",
  1746 + "get-location": "Get current location",
  1747 + "latitude": "Latitude",
  1748 + "longitude": "Longitude",
  1749 + "not-allowed-entity": "Selected entity cannot have shared attributes",
  1750 + "no-attribute-selected": "No attribute is selected",
  1751 + "no-datakey-selected": "No datakey is selected",
  1752 + "no-coordinate-specified": "Datakey for latitude/longitude doesn't specified",
  1753 + "no-entity-selected": "No entity selected",
  1754 + "no-image": "No image",
  1755 + "no-support-geolocation": "Your browser doesn't support geolocation",
  1756 + "no-support-web-camera": "No supported web camera",
  1757 + "no-timeseries-selected": "No timeseries selected",
  1758 + "secret-key": "Secret key",
  1759 + "secret-key-required": "Secret key is required",
  1760 + "switch-attribute-value": "Switch entity attribute value",
  1761 + "switch-camera": "Switch camera",
  1762 + "switch-timeseries-value": "Switch entity timeseries value",
  1763 + "take-photo": "Take photo",
  1764 + "time": "Time",
  1765 + "timeseries-not-allowed": "Timeseries parameter cannot be used in this widget",
  1766 + "update-failed": "Update failed",
  1767 + "update-successful": "Update successful",
  1768 + "update-attribute": "Update attribute",
  1769 + "update-timeseries": "Update timeseries",
  1770 + "value": "Value"
1731 1771 }
1732 1772 },
1733 1773 "icon": {
... ...
... ... @@ -1680,6 +1680,31 @@
1680 1680 "Step size": "Numero de pie",
1681 1681 "Ok": "De acuerdo"
1682 1682 }
  1683 + },
  1684 + "input-widgets": {
  1685 + "attribute-not-allowed": "El parámetro de atributo no se puede usar en este widget",
  1686 + "date": "Fecha",
  1687 + "discard-changes": "Descartar los cambios",
  1688 + "entity-attribute-required": "Se requiere atributo de entidad",
  1689 + "entity-timeseries-required": "Se requiere la serie de tiempo de la entidad",
  1690 + "not-allowed-entity": "La entidad seleccionada no puede tener atributos compartidos",
  1691 + "no-attribute-selected": "No se seleccionó ningún atributo",
  1692 + "no-datakey-selected": "No se seleccionó ninguna clave de datos",
  1693 + "no-entity-selected": "Ninguna entidad seleccionada",
  1694 + "no-image": "Sin imágen",
  1695 + "no-support-web-camera": "No hay cámara web compatible",
  1696 + "no-timeseries-selected": "No hay series de tiempo seleccionadas",
  1697 + "switch-attribute-value": "Cambiar el valor del atributo de entidad",
  1698 + "switch-camera": "Cambiar de cámara",
  1699 + "switch-timeseries-value": "Cambiar el valor de la serie de tiempo de la entidad",
  1700 + "take-photo": "Tomar foto",
  1701 + "time": "Tiempo",
  1702 + "timeseries-not-allowed": "El parámetro Timeseries no se puede usar en este widget",
  1703 + "update-failed": "Actualización fallida",
  1704 + "update-successful": "Actualización exitosa",
  1705 + "update-attribute": "Actualizar atributo",
  1706 + "update-timeseries": "Actualizar series de tiempo",
  1707 + "value": "Valor"
1683 1708 }
1684 1709 },
1685 1710 "icon": {
... ...
... ... @@ -1018,7 +1018,7 @@
1018 1018 "tr_TR": "Turc",
1019 1019 "fa_IR": "Persane",
1020 1020 "uk_UA": "Ukrainien",
1021   - "cs_CZ": "Tchèque"
  1021 + "cs_CZ": "Tchèque"
1022 1022 }
1023 1023 },
1024 1024 "layout": {
... ... @@ -1501,6 +1501,31 @@
1501 1501 "Step size": "Taille de pas",
1502 1502 "Ok": "Ok"
1503 1503 }
  1504 + },
  1505 + "input-widgets": {
  1506 + "attribute-not-allowed": "Le paramètre d'attribut ne peut pas être utilisé dans ce widget",
  1507 + "date": "Date",
  1508 + "discard-changes": "Annuler les modifications",
  1509 + "entity-attribute-required": "L'attribut d'entité est requis",
  1510 + "entity-timeseries-required": "Entité timeseries est requis",
  1511 + "not-allowed-entity": "L'entité sélectionnée ne peut pas avoir d'attributs partagés",
  1512 + "no-attribute-selected": "Aucun attribut n'est sélectionné",
  1513 + "no-datakey-selected": "Aucune date n'est sélectionnée",
  1514 + "no-entity-selected": "Aucune entité sélectionnée",
  1515 + "no-image": "Pas d'image",
  1516 + "no-support-web-camera": "Pas de webcam supportée",
  1517 + "no-timeseries-selected": "Aucune série temporelle sélectionnée",
  1518 + "switch-attribute-value": "Changer la valeur de l'attribut d'entité",
  1519 + "switch-camera": "Changer de caméra",
  1520 + "switch-timeseries-value": "Changer la valeur de l'entité série temporelle",
  1521 + "take-photo": "Prendre une photo",
  1522 + "time": "Temps",
  1523 + "timeseries-not-allowed": "Le paramètre série temporelle ne peut pas être utilisé dans ce widget",
  1524 + "update-failed": "Mise à jour a échoué",
  1525 + "update-successful": "Mise à jour réussie",
  1526 + "update-attribute": "Attribut de mise à jour",
  1527 + "update-timeseries": "Mise à jour de la série temporelle",
  1528 + "value": "Valeur"
1504 1529 }
1505 1530 },
1506 1531 "widgets-bundle": {
... ...
... ... @@ -1615,6 +1615,31 @@
1615 1615 "Step size": "Dimensione del passo",
1616 1616 "Ok": "Ok"
1617 1617 }
  1618 + },
  1619 + "input-widgets": {
  1620 + "attribute-not-allowed": "Questo widget non può usare un parametro di tipo attributo",
  1621 + "date": "Data",
  1622 + "discard-changes": "Annulla modifiche",
  1623 + "entity-attribute-required": "E' richiesta un'entità di tipo attributo",
  1624 + "entity-timeseries-required": "E' richiesta un'entità di tipo serie temporale",
  1625 + "not-allowed-entity": "L'entità selezionata non può avere attributi condivisi",
  1626 + "no-attribute-selected": "Nessun attributo selezionato",
  1627 + "no-datakey-selected": "Nessuna datakey selezionata",
  1628 + "no-entity-selected": "Nessuna entità selezionata",
  1629 + "no-image": "Nessuna immagine",
  1630 + "no-support-web-camera": "Web camera non supportata",
  1631 + "no-timeseries-selected": "Nessuna serie temporale selezionata",
  1632 + "switch-attribute-value": "Cambia il valore dell'attributo",
  1633 + "switch-camera": "Cambia camera",
  1634 + "switch-timeseries-value": "Cambia il valore della serie temporale",
  1635 + "take-photo": "Fai una foto",
  1636 + "time": "Tempo",
  1637 + "timeseries-not-allowed": "Questo widget non può usare un parametro di tipo serie temporale",
  1638 + "update-failed": "Aggiornamento fallito",
  1639 + "update-successful": "Aggiornamento eseguito con successo",
  1640 + "update-attribute": "Aggiorna attributo",
  1641 + "update-timeseries": "Aggiorna serie temporale",
  1642 + "value": "Valore"
1618 1643 }
1619 1644 },
1620 1645 "icon": {
... ... @@ -1649,7 +1674,7 @@
1649 1674 "tr_TR": "Turco",
1650 1675 "fa_IR": "Persiana",
1651 1676 "uk_UA": "Ucraino",
1652   - "cs_CZ": "Ceco"
  1677 + "cs_CZ": "Ceco"
1653 1678 }
1654 1679 }
1655 1680 }
... ...
... ... @@ -1611,6 +1611,46 @@
1611 1611 "Step size": "Размер шага",
1612 1612 "Ok": "Ok"
1613 1613 }
  1614 + },
  1615 + "input-widgets": {
  1616 + "attribute-not-allowed": "Атрибут не может быть выбран в этом виджете",
  1617 + "date": "Дата",
  1618 + "blocked-location": "Геолокация заблокирована в вашем браузере",
  1619 + "claim-device": "Подтвердить устройство",
  1620 + "claim-failed": "Не удалось подтвердить устройство!",
  1621 + "claim-not-found": "Устройство не найдено!",
  1622 + "claim-successful": "Устройство успешно подтверждено!",
  1623 + "discard-changes": "Отменить изменения",
  1624 + "device-name": "Название устройства",
  1625 + "device-name-required": "Необходимо указать название устройства",
  1626 + "entity-attribute-required": "Значение атрибута обязателено",
  1627 + "entity-coordinate-required": "Необходимо указать широту и долготу",
  1628 + "entity-timeseries-required": "Значение телеметрии обязательно",
  1629 + "get-location": "Получить текущее местоположение",
  1630 + "latitude": "Широта",
  1631 + "longitude": "Долгота",
  1632 + "not-allowed-entity": "Выбраный объект не имеет общих атрибутов",
  1633 + "no-attribute-selected": "Атрибут не выбран",
  1634 + "no-datakey-selected": "Ни один datakey не выбран",
  1635 + "no-entity-selected": "Объект не выбран",
  1636 + "no-coordinate-specified": "Ключ для широты/долготы не указан",
  1637 + "no-support-geolocation": "Ваш браузер не поддерживает геолокацию",
  1638 + "no-image": "Нет изображения",
  1639 + "no-support-web-camera": "Нет поддерживаемой веб-камеры",
  1640 + "no-timeseries-selected": "Параметр телеметрии не выбран",
  1641 + "secret-key": "Секретный ключ",
  1642 + "secret-key-required": "Необходимо указать секретный ключ",
  1643 + "switch-attribute-value": "Изменить значение атрибута",
  1644 + "switch-camera": "Изменить камеру",
  1645 + "switch-timeseries-value": "Изменить значение телеметрии",
  1646 + "take-photo": "Сделать фото",
  1647 + "time": "Время",
  1648 + "timeseries-not-allowed": "Телеметрия не может быть выбрана в этом виджете",
  1649 + "update-failed": "Не удалось обновить",
  1650 + "update-successful": "Успешно обновлено",
  1651 + "update-attribute": "Обновить атрибут",
  1652 + "update-timeseries": "Обновить телеметрию",
  1653 + "value": "Значение"
1614 1654 }
1615 1655 },
1616 1656 "icon": {
... ...
1 1 {
2 2 "access": {
3 3 "unauthorized": "Неавторизований",
4   - "unauthorized-access": "Неавторизований доступ",
  4 + "unauthorized-access": "Неавторизований доступ",
5 5 "unauthorized-access-text": "Щоб отримати доступ до цього ресурсу, потрібно ввійти в систему!",
6 6 "access-forbidden": "Доступ заборонено",
7 7 "access-forbidden-text": "Недостатньо прав для доступу! <br/> Спробуйте увійти як інший користувач, якщо ви все ще хочете отримати доступ до цього ресурсу.",
... ... @@ -246,7 +246,7 @@
246 246 "assign-assets": "Надати активи",
247 247 "assign-assets-text": "Надати { count, plural, 1 {1 актив} other {# активи} } клієнту",
248 248 "delete-assets": "Видалити активи",
249   - "unassign-assets": "Позбавити активів",
  249 + "unassign-assets": "Позбавити активів",
250 250 "unassign-assets-action-title": "Позбавити { count, plural, 1 {1 актив} other {# активи} } клієнта",
251 251 "assign-new-asset": "Надати новий актив",
252 252 "delete-asset-title": "Ви впевнені, що хочете видалити актив '{{assetName}}'?",
... ... @@ -2123,6 +2123,105 @@
2123 2123 "widget-type-file": "Файл типу віджета",
2124 2124 "invalid-widget-type-file-error": "Неможливо імпортувати тип віджету: неправильна структура даних типу віджета."
2125 2125 },
  2126 + "widgets": {
  2127 + "date-range-navigator": {
  2128 + "localizationMap": {
  2129 + "Sun": "Нд",
  2130 + "Mon": "Пн",
  2131 + "Tue": "Вт",
  2132 + "Wed": "Ср",
  2133 + "Thu": "Чт",
  2134 + "Fri": "Пт",
  2135 + "Sat": "Сб",
  2136 + "Jan": "Січ.",
  2137 + "Feb": "Лют.",
  2138 + "Mar": "Берез.",
  2139 + "Apr": "Квіт.",
  2140 + "May": "Трав.",
  2141 + "Jun": "Черв.",
  2142 + "Jul": "Лип.",
  2143 + "Aug": "Серп.",
  2144 + "Sep": "Верес.",
  2145 + "Oct": "Жовт.",
  2146 + "Nov": "Листоп.",
  2147 + "Dec": "Груд.",
  2148 + "January": "Січень",
  2149 + "February": "Лютий",
  2150 + "March": "Березень",
  2151 + "April": "Квітень",
  2152 + "June": "Червень",
  2153 + "July": "Липень",
  2154 + "August": "Серпень",
  2155 + "September": "Вересень",
  2156 + "October": "Жовтень",
  2157 + "November": "Листопад",
  2158 + "December": "Грудень",
  2159 + "Custom Date Range": "Користувацький діапазон дат",
  2160 + "Date Range Template": "Шаблон діапазону дат",
  2161 + "Today": "Сьогодні",
  2162 + "Yesterday": "Вчора",
  2163 + "This Week": "Цього тижня",
  2164 + "Last Week": "Минулий тиждень",
  2165 + "This Month": "Цей місяць",
  2166 + "Last Month": "Минулий місяць",
  2167 + "Year": "Рік",
  2168 + "This Year": "Цього року",
  2169 + "Last Year": "Минулий рік",
  2170 + "Date picker": "Вибір дати",
  2171 + "Hour": "Година",
  2172 + "Day": "День",
  2173 + "Week": "Тиждень",
  2174 + "2 weeks": "2 Тижні",
  2175 + "Month": "Місяць",
  2176 + "3 months": "3 Місяці",
  2177 + "6 months": "6 Місяців",
  2178 + "Custom interval": "Користувацький інтервал",
  2179 + "Interval": "Інтервал",
  2180 + "Step size": "Розмір кроку",
  2181 + "Ok": "Ok"
  2182 + }
  2183 + },
  2184 + "input-widgets": {
  2185 + "attribute-not-allowed": "Атрибут не може бути вибраний в цьому віджеті",
  2186 + "date": "Дата",
  2187 + "blocked-location": "Геолокація заблокована у вашому браузері",
  2188 + "claim-device": "Підтвердити пристрій",
  2189 + "claim-failed": "Не вдалося підтвердити пристрій!",
  2190 + "claim-not-found": "Пристрій не знайдено!",
  2191 + "claim-successful": "Пристрій успішно підтверджено!",
  2192 + "discard-changes": "Скасувати зміни",
  2193 + "device-name": "Назва пристрою",
  2194 + "device-name-required": "Необхідно вказати назву пристрою",
  2195 + "entity-attribute-required": "Значення атрибута обов'язкове",
  2196 + "entity-coordinate-required": "Необхідно вказати широту та довготу",
  2197 + "entity-timeseries-required": "Значення телеметрії обов'язкове",
  2198 + "get-location": "Отримати поточне місцезнаходження",
  2199 + "latitude": "Широта",
  2200 + "longitude": "Довгота",
  2201 + "not-allowed-entity": "Обрана сутність не має спільних атрибутів",
  2202 + "no-attribute-selected": "Атрибут не вибрано",
  2203 + "no-datakey-selected": "Ні один datakey не обраний",
  2204 + "no-entity-selected": "Сутність не вибрано",
  2205 + "no-coordinate-specified": "Ключ для широти/довготи не вказаний",
  2206 + "no-support-geolocation": "Ваш браузер не підтримує геолокацію",
  2207 + "no-image": "Немає зображення",
  2208 + "no-support-web-camera": "Нет поддерживаемой веб-камеры",
  2209 + "no-timeseries-selected": "Параметр телеметрії не вибрано",
  2210 + "secret-key": "Секретний ключ",
  2211 + "secret-key-required": "Необхідно вказати секретний ключ",
  2212 + "switch-attribute-value": "Змінити значення атрибута",
  2213 + "switch-camera": "Змінити камеру",
  2214 + "switch-timeseries-value": "Змінити значення телеметрії",
  2215 + "take-photo": "Зробити фото",
  2216 + "time": "Час",
  2217 + "timeseries-not-allowed": "Телеметрія не може бути вибрана в цьому віджеті",
  2218 + "update-failed": "Не вдалося оновити",
  2219 + "update-successful": "Успішно оновлено",
  2220 + "update-attribute": "Оновити атрибут",
  2221 + "update-timeseries": "Оновити телеметрію",
  2222 + "value": "Значення"
  2223 + }
  2224 + },
2126 2225 "white-labeling": {
2127 2226 "white-labeling": "Білий маркування",
2128 2227 "login-white-labeling": "Login White Labeling",
... ...