From f6ceae1d1e958263215b730f7c208dc691d49c80 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Thu, 25 Sep 2025 10:37:21 +0300 Subject: [PATCH 01/38] added show all/hide all button to table columns --- .../lib/display-columns-panel.component.html | 7 +++++ .../lib/display-columns-panel.component.ts | 26 +++++++++++++++++++ .../assets/locale/locale.constant-en_US.json | 1 + 3 files changed, 34 insertions(+) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.html index 65d647993f..5bcd9f0711 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.html @@ -17,6 +17,13 @@ -->
+ + {{'entity.show-all-columns' | translate}} + + {{ column.title }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.ts index 8c5f13de69..deb46408e8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.ts @@ -37,6 +37,32 @@ export class DisplayColumnsPanelComponent { this.columns = this.data.columns; } + get selectableColumns(): DisplayColumn[] { + return this.columns.filter(column => column.selectable); + } + + get allColumnsVisible(): boolean { + const selectableColumns = this.selectableColumns; + return selectableColumns.length > 0 && selectableColumns.every(column => column.display); + } + + get someColumnsVisible(): boolean { + const selectableColumns = this.selectableColumns; + const visibleCount = selectableColumns.filter(column => column.display).length; + return visibleCount > 0 && visibleCount < selectableColumns.length; + } + + public toggleAllColumns(event: any): void { + const isChecked = event.checked; + const selectableColumns = this.selectableColumns; + + selectableColumns.forEach(column => { + column.display = isChecked; + }); + + this.update(); + } + public update() { this.data.columnsUpdated(this.columns); } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 9ee555a838..0e3c815ebd 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2680,6 +2680,7 @@ "details": "Entity details", "no-entities-prompt": "No entities found", "no-data": "No data to display", + "show-all-columns":"Show All", "columns-to-display": "Columns to Display", "type-api-usage-state": "API Usage State", "type-edge": "Edge", From e4c807bad4cf72a5b0d5624a9d064a71eac31f31 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Thu, 25 Sep 2025 15:54:22 +0300 Subject: [PATCH 02/38] did some reworks --- .../lib/display-columns-panel.component.html | 9 +++++---- .../lib/display-columns-panel.component.ts | 20 ++++++++----------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.html index 5bcd9f0711..d1142d08ed 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.html @@ -24,8 +24,9 @@ {{'entity.show-all-columns' | translate}} - - {{ column.title }} - + @for (column of columns; track column.def) { + + {{ column.title }} + + }
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.ts index deb46408e8..d898cdb5a9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.ts @@ -15,6 +15,8 @@ /// import { Component, Inject, InjectionToken } from '@angular/core'; +import { isDefinedAndNotNull } from '@app/core/utils'; +import { SelectableColumnsPipe } from '@app/shared/pipe/selectable-columns.pipe'; import { DisplayColumn } from '@home/components/widget/lib/table-widget.models'; export const DISPLAY_COLUMNS_PANEL_DATA = new InjectionToken('DisplayColumnsPanelData'); @@ -34,29 +36,23 @@ export class DisplayColumnsPanelComponent { columns: DisplayColumn[]; constructor(@Inject(DISPLAY_COLUMNS_PANEL_DATA) public data: DisplayColumnsPanelData) { - this.columns = this.data.columns; - } - - get selectableColumns(): DisplayColumn[] { - return this.columns.filter(column => column.selectable); + const selectableColumnsPipe = new SelectableColumnsPipe(); + this.columns = selectableColumnsPipe.transform(this.data.columns); } get allColumnsVisible(): boolean { - const selectableColumns = this.selectableColumns; - return selectableColumns.length > 0 && selectableColumns.every(column => column.display); + return isDefinedAndNotNull(this.columns) && this.columns.every(column => column.display); } get someColumnsVisible(): boolean { - const selectableColumns = this.selectableColumns; - const visibleCount = selectableColumns.filter(column => column.display).length; - return visibleCount > 0 && visibleCount < selectableColumns.length; + const filtredColumns = this.columns.filter(item => item.display); + return filtredColumns.length !== 0 && this.columns.length !== filtredColumns.length; } public toggleAllColumns(event: any): void { const isChecked = event.checked; - const selectableColumns = this.selectableColumns; - selectableColumns.forEach(column => { + this.columns.forEach(column => { column.display = isChecked; }); From df1dc91b7eb474be6cedc27706d9cdc901cfd7a2 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Mon, 29 Sep 2025 18:20:10 +0300 Subject: [PATCH 03/38] Add language and unit system selection to a user form. --- .../home/pages/user/user.component.html | 18 ++++++++++++++++++ .../modules/home/pages/user/user.component.ts | 10 ++++++++++ 2 files changed, 28 insertions(+) diff --git a/ui-ngx/src/app/modules/home/pages/user/user.component.html b/ui-ngx/src/app/modules/home/pages/user/user.component.html index 658b361255..6b0a507e51 100644 --- a/ui-ngx/src/app/modules/home/pages/user/user.component.html +++ b/ui-ngx/src/app/modules/home/pages/user/user.component.html @@ -98,6 +98,24 @@ formControlName="phone">
+ + language.language + + + {{ lang ? ('language.locales.' + lang | translate) : ''}} + + + + + unit.unit-system + + {{ 'unit.unit-system-type.AUTO' | translate }} + @for(unit of UnitSystems; track unit) { + {{ 'unit.unit-system-type.' + unit | translate }} + } + + user.description diff --git a/ui-ngx/src/app/modules/home/pages/user/user.component.ts b/ui-ngx/src/app/modules/home/pages/user/user.component.ts index da0a001b5d..c5cb91502e 100644 --- a/ui-ngx/src/app/modules/home/pages/user/user.component.ts +++ b/ui-ngx/src/app/modules/home/pages/user/user.component.ts @@ -27,6 +27,8 @@ import { isDefinedAndNotNull } from '@core/utils'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; import { ActionNotificationShow } from '@app/core/notification/notification.actions'; import { TranslateService } from '@ngx-translate/core'; +import { environment as env } from '@env/environment'; +import { UnitSystems } from '@shared/models/unit.models'; @Component({ selector: 'tb-user', @@ -36,6 +38,8 @@ import { TranslateService } from '@ngx-translate/core'; export class UserComponent extends EntityComponent { authority = Authority; + languageList = env.supportedLangs; + UnitSystems = UnitSystems; loginAsUserEnabled$ = this.store.pipe( select(selectAuth), @@ -77,6 +81,8 @@ export class UserComponent extends EntityComponent { additionalInfo: this.fb.group( { description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], + lang: [entity && entity.additionalInfo ? entity.additionalInfo.lang : null], + unitSystem: [entity && entity.additionalInfo ? entity.additionalInfo.unitSystem : null], defaultDashboardId: [entity && entity.additionalInfo ? entity.additionalInfo.defaultDashboardId : null], defaultDashboardFullscreen: [entity && entity.additionalInfo ? entity.additionalInfo.defaultDashboardFullscreen : false], homeDashboardId: [entity && entity.additionalInfo ? entity.additionalInfo.homeDashboardId : null], @@ -94,6 +100,10 @@ export class UserComponent extends EntityComponent { this.entityForm.patchValue({lastName: entity.lastName}); this.entityForm.patchValue({phone: entity.phone}); this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); + this.entityForm.patchValue({additionalInfo: + {lang: entity.additionalInfo ? entity.additionalInfo.lang : env.defaultLang}}); + this.entityForm.patchValue({additionalInfo: + {unitSystem: entity.additionalInfo ? entity.additionalInfo.unitSystem : null}}); this.entityForm.patchValue({additionalInfo: {defaultDashboardId: entity.additionalInfo ? entity.additionalInfo.defaultDashboardId : null}}); this.entityForm.patchValue({additionalInfo: From b77274baefe02c659d88e714a38e8dafd9475848 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Tue, 30 Sep 2025 13:17:58 +0300 Subject: [PATCH 04/38] add missing provider --- .../widget/lib/display-columns-panel.component.ts | 10 +++++----- ui-ngx/src/app/shared/shared.module.ts | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.ts index d898cdb5a9..87ca4ee4fc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.ts @@ -15,8 +15,8 @@ /// import { Component, Inject, InjectionToken } from '@angular/core'; -import { isDefinedAndNotNull } from '@app/core/utils'; -import { SelectableColumnsPipe } from '@app/shared/pipe/selectable-columns.pipe'; +import { isDefinedAndNotNull } from '@core/utils'; +import { SelectableColumnsPipe } from '@shared/public-api'; import { DisplayColumn } from '@home/components/widget/lib/table-widget.models'; export const DISPLAY_COLUMNS_PANEL_DATA = new InjectionToken('DisplayColumnsPanelData'); @@ -35,9 +35,9 @@ export class DisplayColumnsPanelComponent { columns: DisplayColumn[]; - constructor(@Inject(DISPLAY_COLUMNS_PANEL_DATA) public data: DisplayColumnsPanelData) { - const selectableColumnsPipe = new SelectableColumnsPipe(); - this.columns = selectableColumnsPipe.transform(this.data.columns); + constructor(@Inject(DISPLAY_COLUMNS_PANEL_DATA) public data: DisplayColumnsPanelData, + private selectableColumnsPipe:SelectableColumnsPipe ) { + this.columns = this.selectableColumnsPipe.transform(this.data.columns); } get allColumnsVisible(): boolean { diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 9fd6e4a71e..619c64cfb1 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -236,6 +236,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) @NgModule({ providers: [ DatePipe, + SelectableColumnsPipe, MillisecondsToTimeStringPipe, EnumToArrayPipe, HighlightPipe, From 43d5ea9db10c046ca543cdb920bda17683b75157 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Thu, 2 Oct 2025 15:18:09 +0300 Subject: [PATCH 05/38] Update User form: apply outline appearance. --- .../pages/user/add-user-dialog.component.html | 2 +- .../home/pages/user/user.component.html | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html b/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html index 8f4a8a4a6a..c9a83c1531 100644 --- a/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html @@ -31,7 +31,7 @@
- + user.activation-method diff --git a/ui-ngx/src/app/modules/home/pages/user/user.component.html b/ui-ngx/src/app/modules/home/pages/user/user.component.html index 6b0a507e51..8cd6152b83 100644 --- a/ui-ngx/src/app/modules/home/pages/user/user.component.html +++ b/ui-ngx/src/app/modules/home/pages/user/user.component.html @@ -74,7 +74,7 @@
- + user.email @@ -84,21 +84,22 @@ {{ 'user.email-required' | translate }} - + user.first-name - + user.last-name
- + language.language @@ -106,7 +107,7 @@ - + unit.unit-system @@ -116,16 +117,17 @@ } - + user.description
-
+
-
+
Date: Fri, 3 Oct 2025 14:03:48 +0300 Subject: [PATCH 06/38] add color adjustment for table pagination icons --- .../modules/home/components/widget/lib/table-widget.models.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts index 7e91a2f043..b6069d4214 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts @@ -549,6 +549,9 @@ export function constructTableCssString(widgetConfig: WidgetConfig): string { '.mat-mdc-paginator button.mat-mdc-icon-button[disabled][disabled] {\n' + 'color: ' + mdDarkDisabled + ';\n' + '}\n' + + '.mat-mdc-paginator svg.mat-mdc-paginator-icon {\n' + + 'fill: currentColor;\n' + + '}\n' + '.mat-mdc-paginator .mat-mdc-select-value {\n' + 'color: ' + mdDarkSecondary + ';\n' + '}'; From b63c2e3214a52f62f949eda17f6aa34b29035103 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Mon, 6 Oct 2025 16:07:15 +0300 Subject: [PATCH 07/38] add spaces --- .../components/widget/lib/display-columns-panel.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.ts index 87ca4ee4fc..4ef00bd575 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/display-columns-panel.component.ts @@ -36,7 +36,7 @@ export class DisplayColumnsPanelComponent { columns: DisplayColumn[]; constructor(@Inject(DISPLAY_COLUMNS_PANEL_DATA) public data: DisplayColumnsPanelData, - private selectableColumnsPipe:SelectableColumnsPipe ) { + private selectableColumnsPipe: SelectableColumnsPipe ) { this.columns = this.selectableColumnsPipe.transform(this.data.columns); } From c78f3b7ef255ccb697edccc9f5fa6c9174ef91eb Mon Sep 17 00:00:00 2001 From: deaflynx Date: Tue, 7 Oct 2025 12:18:46 +0300 Subject: [PATCH 08/38] User default lang/unitSystem improvements: add Auto lang (null), on save Profile do not overwrite lang if not set explicitly; delete props if null. --- .../home/pages/profile/profile.component.html | 12 +++++++----- .../home/pages/profile/profile.component.ts | 15 +++++++-------- .../home/pages/user/add-user-dialog.component.ts | 6 ++++++ .../modules/home/pages/user/user.component.html | 12 +++++++----- .../app/modules/home/pages/user/user.component.ts | 2 +- .../pages/user/users-table-config.resolver.ts | 6 ++++++ .../src/assets/locale/locale.constant-en_US.json | 1 + 7 files changed, 35 insertions(+), 19 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/profile/profile.component.html b/ui-ngx/src/app/modules/home/pages/profile/profile.component.html index 44eea5865e..bcbbd5e7d4 100644 --- a/ui-ngx/src/app/modules/home/pages/profile/profile.component.html +++ b/ui-ngx/src/app/modules/home/pages/profile/profile.component.html @@ -58,12 +58,14 @@ [enableFlagsSelect]="true" formControlName="phone"> - + language.language - - - {{ lang ? ('language.locales.' + lang | translate) : ''}} - + + {{ 'language.auto' | translate }} + @for(lang of languageList; track lang) { + {{ 'language.locales.' + lang | translate }} + } diff --git a/ui-ngx/src/app/modules/home/pages/profile/profile.component.ts b/ui-ngx/src/app/modules/home/pages/profile/profile.component.ts index 7d1aa2aa4e..f66dd68b89 100644 --- a/ui-ngx/src/app/modules/home/pages/profile/profile.component.ts +++ b/ui-ngx/src/app/modules/home/pages/profile/profile.component.ts @@ -25,7 +25,6 @@ import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; import { ActionAuthUpdateUserDetails } from '@core/auth/auth.actions'; import { environment as env } from '@env/environment'; -import { TranslateService } from '@ngx-translate/core'; import { ActionSettingsChangeLanguage } from '@core/settings/settings.actions'; import { ActivatedRoute } from '@angular/router'; import { isDefinedAndNotNull, isNotEmptyStr } from '@core/utils'; @@ -52,7 +51,6 @@ export class ProfileComponent extends PageComponent implements OnInit, HasConfir private route: ActivatedRoute, private userService: UserService, private authService: AuthService, - private translate: TranslateService, private unitService: UnitService, private fb: UntypedFormBuilder) { super(store); @@ -82,9 +80,13 @@ export class ProfileComponent extends PageComponent implements OnInit, HasConfir if (!this.user.additionalInfo) { this.user.additionalInfo = {}; } - this.user.additionalInfo.lang = this.profile.get('language').value; this.user.additionalInfo.homeDashboardId = this.profile.get('homeDashboardId').value; this.user.additionalInfo.homeDashboardHideToolbar = this.profile.get('homeDashboardHideToolbar').value; + if (isNotEmptyStr(this.profile.get('language').value)) { + this.user.additionalInfo.lang = this.profile.get('language').value; + } else { + delete this.user.additionalInfo.lang; + } if (isNotEmptyStr(this.profile.get('unitSystem').value)) { this.user.additionalInfo.unitSystem = this.profile.get('unitSystem').value; } else { @@ -105,7 +107,7 @@ export class ProfileComponent extends PageComponent implements OnInit, HasConfir id: user.id, lastName: user.lastName, } })); - this.store.dispatch(new ActionSettingsChangeLanguage({ userLang: user.additionalInfo.lang })); + this.store.dispatch(new ActionSettingsChangeLanguage({ userLang: user.additionalInfo.lang || env.defaultLang })); this.unitService.setUnitSystem(this.user.additionalInfo.unitSystem); this.authService.refreshJwtToken(false); } @@ -115,7 +117,7 @@ export class ProfileComponent extends PageComponent implements OnInit, HasConfir private userLoaded(user: User) { this.user = user; this.profile.reset(user); - let lang; + let lang: string = null; let homeDashboardId; let homeDashboardHideToolbar = true; let unitSystem: UnitSystem = null; @@ -131,9 +133,6 @@ export class ProfileComponent extends PageComponent implements OnInit, HasConfir unitSystem = user.additionalInfo.unitSystem; } } - if (!lang) { - lang = this.translate.currentLang; - } this.profile.get('language').setValue(lang); this.profile.get('unitSystem').setValue(unitSystem); this.profile.get('homeDashboardId').setValue(homeDashboardId); diff --git a/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.ts index 7470deb3d6..11468f64fb 100644 --- a/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.ts @@ -84,6 +84,12 @@ export class AddUserDialogComponent extends DialogComponent { diff --git a/ui-ngx/src/app/modules/home/pages/user/user.component.html b/ui-ngx/src/app/modules/home/pages/user/user.component.html index 8cd6152b83..30e23a4a73 100644 --- a/ui-ngx/src/app/modules/home/pages/user/user.component.html +++ b/ui-ngx/src/app/modules/home/pages/user/user.component.html @@ -99,12 +99,14 @@ formControlName="phone">
- + language.language - - - {{ lang ? ('language.locales.' + lang | translate) : ''}} - + + {{ 'language.auto' | translate }} + @for(lang of languageList; track lang) { + {{ 'language.locales.' + lang | translate }} + } diff --git a/ui-ngx/src/app/modules/home/pages/user/user.component.ts b/ui-ngx/src/app/modules/home/pages/user/user.component.ts index c5cb91502e..df99729eda 100644 --- a/ui-ngx/src/app/modules/home/pages/user/user.component.ts +++ b/ui-ngx/src/app/modules/home/pages/user/user.component.ts @@ -101,7 +101,7 @@ export class UserComponent extends EntityComponent { this.entityForm.patchValue({phone: entity.phone}); this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); this.entityForm.patchValue({additionalInfo: - {lang: entity.additionalInfo ? entity.additionalInfo.lang : env.defaultLang}}); + {lang: entity.additionalInfo ? entity.additionalInfo.lang : null}}); this.entityForm.patchValue({additionalInfo: {unitSystem: entity.additionalInfo ? entity.additionalInfo.unitSystem : null}}); this.entityForm.patchValue({additionalInfo: diff --git a/ui-ngx/src/app/modules/home/pages/user/users-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/user/users-table-config.resolver.ts index 2e056119d7..651da8d99f 100644 --- a/ui-ngx/src/app/modules/home/pages/user/users-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/user/users-table-config.resolver.ts @@ -158,6 +158,12 @@ export class UsersTableConfigResolver { user.tenantId = new TenantId(this.tenantId); user.customerId = new CustomerId(this.customerId); user.authority = this.authority; + if (!user.additionalInfo.lang) { + delete user.additionalInfo.lang; + } + if (!user.additionalInfo.unitSystem) { + delete user.additionalInfo.unitSystem; + } return this.userService.saveUser(user); } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index f78e95e56d..f926a8545c 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -9682,6 +9682,7 @@ "items-per-page-separator": "of" }, "language": { + "auto": "Auto", "language": "Language", "locales": { "ar_AE": "العربية (الإمارات العربية المتحدة)", From c3c33100fd6f92499be5298493bac5ab0121040b Mon Sep 17 00:00:00 2001 From: LeoMorgan113 Date: Wed, 8 Oct 2025 16:11:21 +0300 Subject: [PATCH 09/38] Added polylines to map: init --- .../maps/data-layer/polygons-data-layer.ts | 1 + .../maps/data-layer/polylines-data-layer.ts | 458 ++++++++++++++++++ .../components/widget/lib/maps/geo-map.ts | 32 +- .../components/widget/lib/maps/image-map.ts | 40 +- .../home/components/widget/lib/maps/map.ts | 24 +- .../map/map-data-layer-row.component.html | 18 + .../map/map-data-layer-row.component.ts | 24 +- .../common/map/map-data-layers.component.ts | 4 + .../common/map/map-settings.component.html | 7 + .../common/map/map-settings.component.ts | 8 +- ui-ngx/src/app/shared/models/widget.models.ts | 4 +- .../shared/models/widget/maps/map.models.ts | 78 ++- 12 files changed, 687 insertions(+), 11 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polylines-data-layer.ts diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polygons-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polygons-data-layer.ts index cfe564e7fd..65249f2498 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polygons-data-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polygons-data-layer.ts @@ -1,4 +1,5 @@ /// +/// /// Copyright © 2016-2025 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polylines-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polylines-data-layer.ts new file mode 100644 index 0000000000..b9fa4a692c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polylines-data-layer.ts @@ -0,0 +1,458 @@ +/// +/// Copyright © 2016-2025 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. +/// + +import { + defaultBasePolygonsDataLayerSettings, + isCutPolygon, + isJSON, + MapDataLayerType, + PolygonsDataLayerSettings, + PolylinesDataLayerSettings, + TbMapDatasource, + TbPolyData, + TbPolygonCoordinates, + TbPolygonRawCoordinates, + TbPolylineCoordinates, TbPolylineData, + TbPolylineRawCoordinates +} from '@shared/models/widget/maps/map.models'; +import L from 'leaflet'; +import { DataKey, FormattedData } from '@shared/models/widget.models'; +import { ShapeStyleInfo, TbShapesDataLayer } from '@home/components/widget/lib/maps/data-layer/shapes-data-layer'; +import { TbMap } from '@home/components/widget/lib/maps/map'; +import { Observable } from 'rxjs'; +import { isNotEmptyStr, isString } from '@core/utils'; +import { + TbLatestDataLayerItem, + UnplacedMapDataItem +} from '@home/components/widget/lib/maps/data-layer/latest-map-data-layer'; +import { map } from 'rxjs/operators'; + +class TbPolylineDataLayerItem extends TbLatestDataLayerItem { + + private polylineContainer: L.FeatureGroup; + private polyline: L.Polyline; + private polylineStyleInfo: ShapeStyleInfo; + private editing = false; + + constructor(data: FormattedData, + dsData: FormattedData[], + protected settings: PolylinesDataLayerSettings, + protected dataLayer: TbPolylineDataLayer) { + super(data, dsData, settings, dataLayer); + } + + public isEditing() { + return this.editing; + } + + public updateBubblingMouseEvents() { + this.polyline.options.bubblingMouseEvents = !this.dataLayer.isEditMode(); + } + + public remove() { + super.remove(); + if (this.polylineStyleInfo?.patternId) { + this.dataLayer.getMap().unUseShapePattern(this.polylineStyleInfo.patternId); + } + } + + protected create(data: FormattedData, dsData: FormattedData[]): L.Layer { + const polyData = this.dataLayer.extractPolylineCoordinates(data); + const polyConstructor = L.polyline; + this.polyline = polyConstructor(polyData as (TbPolygonRawCoordinates & L.LatLngTuple[]), { + // noClip: true, + // snapIgnore: !this.dataLayer.isSnappable(), + bubblingMouseEvents: !this.dataLayer.isEditMode() + }); + + this.dataLayer.getShapeStyle(data, dsData, this.polylineStyleInfo?.patternId).subscribe((styleInfo) => { + this.polylineStyleInfo = styleInfo; + if (this.polyline) { + this.polyline.setStyle(this.polylineStyleInfo.style); + } + }); + + this.polylineContainer = L.featureGroup(); + this.polyline.addTo(this.polylineContainer); + + this.updateLabel(data, dsData); + return this.polylineContainer; + } + + protected unbindLabel() { + this.polylineContainer.unbindTooltip(); + } + + protected bindLabel(content: L.Content): void { + this.polylineContainer.bindTooltip(content, {className: 'tb-polyline-label', permanent: true, direction: 'center'}) + .openTooltip(this.polylineContainer.getBounds().getCenter()); + } + + protected doUpdate(data: FormattedData, dsData: FormattedData[]): void { + this.dataLayer.getShapeStyle(data, dsData, this.polylineStyleInfo?.patternId).subscribe((styleInfo) => { + this.polylineStyleInfo = styleInfo; + this.updatePolylineShape(data); + this.updateTooltip(data, dsData); + this.updateLabel(data, dsData); + if (!this.editing || !this.dataLayer.getMap().getMap().pm.globalCutModeEnabled()) { + this.polyline.setStyle(this.polylineStyleInfo.style); + } + }); + } + + protected doInvalidateCoordinates(data: FormattedData, _dsData: FormattedData[]): void { + this.updatePolylineShape(data); + } + + protected addItemClass(clazz: string): void { + if ((this.polyline as any)._path) { + L.DomUtil.addClass((this.polyline as any)._path, clazz); + } + } + + protected removeItemClass(clazz: string): void { + if ((this.polyline as any)._path) { + L.DomUtil.removeClass((this.polyline as any)._path, clazz); + } + } + + protected enableDrag(): void { + this.polyline.pm.setOptions({ + snappable: this.dataLayer.isSnappable() + }); + this.polyline.pm.enableLayerDrag(); + this.polyline.on('pm:dragstart', () => { + this.editing = true; + }); + this.polyline.on('pm:drag', () => { + if (this.tooltip?.isOpen()) { + this.tooltip.setLatLng(this.polyline.getBounds().getCenter()); + } + }); + this.polyline.on('pm:dragend', () => { + this.savePolygonCoordinates(); + this.editing = false; + }); + } + + protected disableDrag(): void { + this.polyline.pm.disableLayerDrag(); + this.polyline.off('pm:dragstart'); + this.polyline.off('pm:dragend'); + } + + protected onSelected(): L.TB.ToolbarButtonOptions[] { + const buttons: L.TB.ToolbarButtonOptions[] = []; + if (this.dataLayer.isEditEnabled()) { + // this.enablePolygonEditMode(); + buttons.push( + { + id: 'cut', + title: this.getDataLayer().getCtx().translate.instant('widgets.maps.data-layer.polygon.cut'), + iconClass: 'tb-cut', + click: (e, button) => { + const map = this.dataLayer.getMap().getMap(); + // if (!map.pm.globalCutModeEnabled()) { + // this.disablePolygonRotateMode(); + // this.disablePolygonEditMode(); + // this.enablePolygonCutMode(button); + // } else { + // this.disablePolygonCutMode(button); + // this.enablePolygonEditMode(); + // } + } + }, + { + id: 'rotate', + title: this.getDataLayer().getCtx().translate.instant('widgets.maps.data-layer.polygon.rotate'), + iconClass: 'tb-rotate', + click: (e, button) => { + if (!this.polyline.pm.rotateEnabled()) { + // this.disablePolygonCutMode(); + // this.disablePolygonEditMode(); + // this.enablePolygonRotateMode(button); + } else { + // this.disablePolygonRotateMode(button); + // this.enablePolygonEditMode(); + } + } + } + ); + } + return buttons; + } + + protected onDeselected(): void { + if (this.dataLayer.isEditEnabled()) { + // this.disablePolygonEditMode(); + // this.disablePolygonCutMode(); + // this.disablePolygonRotateMode(); + } + } + + protected canDeselect(cancel = false): boolean { + const map = this.dataLayer.getMap().getMap(); + if (map.pm.globalCutModeEnabled()) { + if (cancel) { + // this.disablePolygonCutMode(); + } + return false; + } else if (this.polyline.pm.rotateEnabled()) { + if (cancel) { + // this.disablePolygonRotateMode(); + } + return false; + } else if (this.editing) { + return false; + } + return true; + } + + protected removeDataItemTitle(): string { + return this.dataLayer.getCtx().translate.instant('widgets.maps.data-layer.polygon.remove-polygon-for', {entityName: this.data.entityName}); + } + + protected removeDataItem(): Observable { + return this.dataLayer.savePolylineCoordinates(this.data, null); + } + + // private enablePolygonEditMode() { + // this.polyline.on('pm:markerdragstart', () => this.editing = true); + // this.polyline.on('pm:markerdragend', () => setTimeout(() => { + // this.editing = false; + // }) ); + // this.polyline.on('pm:edit', () => this.savePolygonCoordinates()); + // this.polyline.pm.enable(); + // const map = this.dataLayer.getMap(); + // map.getEditToolbar().getButton('remove')?.setDisabled(false); + // } + + // private disablePolygonEditMode() { + // this.polyline.pm.disable(); + // this.polyline.off('pm:markerdragstart'); + // this.polyline.off('pm:markerdragend'); + // this.polyline.off('pm:edit'); + // const map = this.dataLayer.getMap(); + // map.getEditToolbar().getButton('remove')?.setDisabled(true); + // } + + // private enablePolygonCutMode(cutButton?: L.TB.ToolbarButton) { + // this.polylineContainer.closePopup(); + // this.editing = true; + // this.polyline.options.bubblingMouseEvents = true; + // this.polyline.setStyle({...this.polylineStyleInfo.style, dashArray: '5 5', weight: 3, + // color: '#3388ff', opacity: 1, fillColor: '#3388ff', fillOpacity: 0.2}); + // this.addItemClass('tb-cut-mode'); + // this.polyline.once('pm:cut', (e) => { + // if (e.layer instanceof L.Polygon) { + // if (this.polyline instanceof L.Rectangle) { + // this.polylineContainer.removeLayer(this.polyline); + // this.polyline = L.polyline(e.layer.getLatLngs(), { + // ...this.polylineStyleInfo.style, + // snapIgnore: !this.dataLayer.isSnappable(), + // bubblingMouseEvents: !this.dataLayer.isEditMode() + // }); + // this.polyline.addTo(this.polylineContainer); + // } else { + // this.polyline.setLatLngs(e.layer.getLatLngs()); + // } + // } + // // @ts-ignore + // e.layer._pmTempLayer = true; + // e.layer.remove(); + // this.polylineContainer.removeLayer(this.polyline); + // // @ts-ignore + // this.polyline._pmTempLayer = false; + // this.polyline.addTo(this.polylineContainer); + // this.updateSelectedState(); + // cutButton?.setActive(false); + // this.savePolygonCoordinates() + // }); + // const map = this.dataLayer.getMap().getMap(); + // map.pm.setLang('en', { + // tooltips: { + // firstVertex: this.getDataLayer().getCtx().translate.instant('widgets.maps.data-layer.polygon.polygon-place-first-point-cut-hint'), + // continueLine: this.getDataLayer().getCtx().translate.instant('widgets.maps.data-layer.polygon.continue-polygon-cut-hint'), + // finishPoly: this.getDataLayer().getCtx().translate.instant('widgets.maps.data-layer.polygon.finish-polygon-cut-hint') + // } + // }, 'en'); + // map.pm.enableGlobalCutMode({ + // // @ts-ignore + // layersToCut: [this.polyline] + // }); + // // @ts-ignore + // L.DomUtil.addClass(map.pm.Draw.Cut._hintMarker.getTooltip()._container, 'tb-place-item-label'); + // cutButton?.setActive(true); + // map.once('pm:globalcutmodetoggled', (e) => { + // // if (!e.enabled) { + // // this.disablePolygonCutMode(cutButton); + // // this.enablePolygonEditMode(); + // // } + // }); + // } + + // private disablePolygonCutMode(cutButton?: L.TB.ToolbarButton) { + // this.editing = false; + // this.polyline.options.bubblingMouseEvents = !this.dataLayer.isEditMode(); + // this.polyline.setStyle({...this.polylineStyleInfo.style, dashArray: null}); + // this.removeItemClass('tb-cut-mode'); + // this.polyline.off('pm:cut'); + // const map = this.dataLayer.getMap().getMap(); + // map.pm.disableGlobalCutMode(); + // cutButton?.setActive(false); + // } + + // private enablePolygonRotateMode(rotateButton?: L.TB.ToolbarButton) { + // this.polylineContainer.closePopup(); + // this.editing = true; + // this.polyline.on('pm:rotateend', () => { + // this.savePolygonCoordinates(); + // }); + // this.polyline.pm.enableRotate(); + // rotateButton?.setActive(true); + // this.polyline.on('pm:rotatedisable', () => { + // this.disablePolygonRotateMode(rotateButton); + // this.enablePolygonEditMode(); + // }); + // } + // + // private disablePolygonRotateMode(rotateButton?: L.TB.ToolbarButton) { + // this.editing = false; + // this.polyline.pm.disableRotate(); + // this.polyline.off('pm:rotateend'); + // this.polyline.off('pm:rotatedisable'); + // rotateButton?.setActive(false); + // } + + private savePolygonCoordinates() { + let coordinates: TbPolygonCoordinates = this.polyline.getLatLngs(); + if (coordinates.length === 1) { + coordinates = coordinates[0] as TbPolygonCoordinates; + } + if (this.polyline instanceof L.Rectangle && !isCutPolygon(coordinates)) { + const bounds = this.polyline.getBounds(); + const boundsArray = [bounds.getNorthWest(), bounds.getNorthEast(), bounds.getSouthWest(), bounds.getSouthEast()]; + if (coordinates.every(point => boundsArray.find(boundPoint => boundPoint.equals(point as L.LatLng)) !== undefined)) { + coordinates = [bounds.getNorthWest(), bounds.getSouthEast()]; + } + } + this.dataLayer.savePolylineCoordinates(this.data, coordinates).subscribe(); + } + + private updatePolylineShape(data: FormattedData) { + if (this.editing) { + return; + } + const polyData = this.dataLayer.extractPolylineCoordinates(data) as TbPolylineData; + if (isCutPolygon(polyData) || polyData.length !== 2) { + if (this.polyline instanceof L.Rectangle) { + this.polylineContainer.removeLayer(this.polyline); + this.polyline = L.polyline(polyData, { + ...this.polylineStyleInfo.style, + snapIgnore: !this.dataLayer.isSnappable(), + bubblingMouseEvents: !this.dataLayer.isEditMode(), + noClip: true + }); + this.polyline.addTo(this.polylineContainer); + this.editModeUpdated(); + } else { + this.polyline.setLatLngs(polyData); + } + } else if (polyData.length === 2) { + const bounds = new L.LatLngBounds(polyData as L.LatLngTuple[]); + // (this.polyline as L.Rectangle).setBounds(bounds); + } + } + +} + +export class TbPolylineDataLayer extends TbShapesDataLayer { + + constructor(protected map: TbMap, + inputSettings: PolylinesDataLayerSettings) { + super(map, inputSettings); + } + + public dataLayerType(): MapDataLayerType { + return 'polylines'; + } + + public placeItem(item: UnplacedMapDataItem, layer: L.Layer): void { + if (layer instanceof L.Polygon) { + let coordinates: TbPolylineCoordinates; + if (layer instanceof L.Rectangle) { + const bounds = layer.getBounds(); + coordinates = [bounds.getNorthWest(), bounds.getSouthEast()]; + } else { + coordinates = layer.getLatLngs(); + if (coordinates.length === 1) { + coordinates = coordinates[0] as TbPolygonCoordinates; + } + } + this.savePolylineCoordinates(item.entity, coordinates).subscribe( + (converted) => { + item.entity[this.settings.polylineKey.label] = JSON.stringify(converted); + this.createItemFromUnplaced(item); + } + ); + } else { + console.warn('Unable to place item, layer is not a polygon.'); + } + } + + public extractPolylineCoordinates(data: FormattedData): TbPolygonRawCoordinates { + let rawPolyData = data[this.settings.polylineKey.label]; + if (isString(rawPolyData)) { + rawPolyData = JSON.parse(rawPolyData); + } + return this.map.polylineDataToCoordinates(rawPolyData); + } + + public savePolylineCoordinates(data: FormattedData, coordinates: TbPolylineCoordinates): Observable { + const converted = coordinates ? this.map.coordinatesToPolygonData(coordinates) : null; + const polylineData = [ + { + dataKey: this.settings.polylineKey, + value: converted + } + ]; + return this.map.saveItemData(data.$datasource, polylineData, this.settings.edit?.attributeScope).pipe( + map(() => converted) + ); + } + + protected getDataKeys(): DataKey[] { + return [this.settings.polylineKey]; + } + + protected defaultBaseSettings(map: TbMap): Partial { + return defaultBasePolygonsDataLayerSettings(map.type()); + } + + protected doSetup(): Observable { + return super.doSetup(); + } + + protected isValidLayerData(layerData: FormattedData): boolean { + return layerData && ((isNotEmptyStr(layerData[this.settings.polylineKey.label]) && !isJSON(layerData[this.settings.polylineKey.label]) + || Array.isArray(layerData[this.settings.polylineKey.label]))); + } + + protected createLayerItem(data: FormattedData, dsData: FormattedData[]): TbPolylineDataLayerItem { + return new TbPolylineDataLayerItem(data, dsData, this.settings, this); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/geo-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/geo-map.ts index fd03990d26..d28c0359f2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/geo-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/geo-map.ts @@ -24,7 +24,11 @@ import { TbPolygonCoordinate, TbPolygonCoordinates, TbPolygonRawCoordinate, - TbPolygonRawCoordinates + TbPolygonRawCoordinates, + TbPolylineCoordinate, + TbPolylineCoordinates, + TbPolylineRawCoordinate, + TbPolylineRawCoordinates } from '@shared/models/widget/maps/map.models'; import { WidgetContext } from '@home/models/widget-component.models'; import { DeepPartial } from '@shared/models/common'; @@ -179,5 +183,31 @@ export class TbGeoMap extends TbMap { return circleData; } + public polylineDataToCoordinates(expression: TbPolylineRawCoordinates): TbPolylineRawCoordinates { + return (expression).map((el: TbPolylineRawCoordinate) => { + if (!Array.isArray(el[0]) && !Array.isArray(el[1]) && el.length === 2) { + return el; + } + // else if (Array.isArray(el) && el.length) { + // return this.polylineDataToCoordinates(el as TbPolylineRawCoordinates) as TbPolylineRawCoordinates; + // } + else { + return null; + } + }).filter(el => !!el); + } + public coordinatesToPolylineData(coordinates: TbPolylineCoordinates): TbPolylineRawCoordinates { + if (coordinates.length) { + return coordinates.map((point: TbPolylineCoordinate) => { + if (Array.isArray(point)) { + return this.coordinatesToPolylineData(point) as TbPolylineRawCoordinate; + } else { + const convertPoint = latLngPointToBounds(point, this.southWest, this.northEast); + return [convertPoint.lat, convertPoint.lng]; + } + }); + } + return []; + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/image-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/image-map.ts index 219f5d63ff..df5f9e34f6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/image-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/image-map.ts @@ -23,7 +23,12 @@ import { ImageSourceType, loadImageWithAspect, MapZoomAction, - TbCircleData, TbPolygonCoordinate, TbPolygonCoordinates, TbPolygonRawCoordinate, TbPolygonRawCoordinates + TbCircleData, + TbPolygonCoordinate, + TbPolygonCoordinates, + TbPolygonRawCoordinate, + TbPolygonRawCoordinates, TbPolylineCoordinate, TbPolylineCoordinates, TbPolylineRawCoordinate, + TbPolylineRawCoordinates } from '@shared/models/widget/maps/map.models'; import { WidgetContext } from '@home/models/widget-component.models'; import { DeepPartial } from '@shared/models/common'; @@ -339,4 +344,37 @@ export class TbImageMap extends TbMap { ); } + public polylineDataToCoordinates(expression: TbPolylineRawCoordinates): TbPolylineRawCoordinates{ + return expression.map((el: TbPolylineRawCoordinate) => { + if (!Array.isArray(el[0]) && !Array.isArray(el[1]) && el.length === 2) { + const latLng = this.pointToLatLng( + el[0] * this.width, + el[1] * this.height + ); + return [latLng.lat, latLng.lng] as TbPolylineRawCoordinate; + } + else if (Array.isArray(el) && el.length) { + return this.polylineDataToCoordinates(el as TbPolylineRawCoordinates) as TbPolylineRawCoordinate; + } + else { + return null; + } + }).filter(el => !!el); + } + + public coordinatesToPolylineData(coordinates: TbPolylineCoordinates): TbPolylineRawCoordinates{ + if (coordinates.length) { + return coordinates.map((point: TbPolylineCoordinate) => { + if (Array.isArray(point)) { + return this.coordinatesToPolylineData(point) as TbPolylineRawCoordinate; + } else { + const pos = this.latLngToPoint(point); + return [calculateNewPointCoordinate(pos.x, this.width), calculateNewPointCoordinate(pos.y, this.height)]; + } + }); + } else { + return []; + } + } + } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts index aa8dd67494..faf328d203 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts @@ -27,7 +27,7 @@ import { TbCircleData, TbMapDatasource, TbPolygonCoordinates, - TbPolygonRawCoordinates + TbPolygonRawCoordinates, TbPolylineCoordinates, TbPolylineRawCoordinates } from '@shared/models/widget/maps/map.models'; import { WidgetContext } from '@home/models/widget-component.models'; import { @@ -83,6 +83,7 @@ import { EntityType } from '@shared/models/entity-type.models'; import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance; import TooltipPositioningSide = JQueryTooltipster.TooltipPositioningSide; import { ShapePatternStorage } from '@home/components/widget/lib/maps/data-layer/shapes-data-layer'; +import { TbPolylineDataLayer } from '@home/components/widget/lib/maps/data-layer/polylines-data-layer'; type TooltipInstancesData = {root: HTMLElement, instances: ITooltipsterInstance[]}; @@ -274,6 +275,11 @@ export abstract class TbMap { this.dataLayers.push(...circlesDataLayers); this.latestDataLayers.push(...circlesDataLayers); } + if (this.settings.polylines) { + const polylinesDataLayers = this.settings.polylines.map(settings => new TbPolylineDataLayer(this, settings)); + this.dataLayers.push(...polylinesDataLayers); + this.latestDataLayers.push(...polylinesDataLayers); + } if (this.settings.trips) { const tripsDataLayers = this.settings.trips.map(settings => new TbTripsDataLayer(this, settings)); this.dataLayers.push(...tripsDataLayers); @@ -792,6 +798,15 @@ export abstract class TbMap { return this.coordinatesToCircleData(layer.getLatLng(), layer.getRadius()); } return null; + case MapItemType.polyline: + if (layer instanceof L.Polyline) { + let coordinates: any = layer.getLatLngs(); + if (coordinates.length === 1) { + coordinates = coordinates[0]; + } + return this.coordinatesToPolylineData(coordinates); + } + return null; } } } @@ -813,7 +828,7 @@ export abstract class TbMap { this.editToolbar.close(); } - private prepareDrawMode(shape: 'Marker' | 'Rectangle' | 'Polygon' | 'Circle', tooltipsTranslation: Record) { + private prepareDrawMode(shape: 'Marker' | 'Rectangle' | 'Polygon' | 'Circle' | 'Polyline', tooltipsTranslation: Record) { this.map.pm.setLang('en', { tooltips: tooltipsTranslation }, 'en'); this.map.pm.enableDraw(shape); // @ts-ignore @@ -1040,6 +1055,7 @@ export abstract class TbMap { if (this.addCircleButton) { this.addCircleButton.setDisabled(!this.addCircleDataLayers.some(dl => dl.isEnabled() && dl.hasUnplacedItems())); } + // TODO this.customActionsToolbar?.setDisabled(false); } } @@ -1337,6 +1353,10 @@ export abstract class TbMap { public abstract coordinatesToPolygonData(coordinates: TbPolygonCoordinates): TbPolygonRawCoordinates; + public abstract polylineDataToCoordinates(coordinates: TbPolylineRawCoordinates): TbPolylineRawCoordinates; + + public abstract coordinatesToPolylineData(coordinates: TbPolylineCoordinates): TbPolylineRawCoordinates; + public abstract circleDataToCoordinates(circle: TbCircleData): TbCircleData; public abstract coordinatesToCircleData(center: L.LatLng, radius: number): TbCircleData; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-row.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-row.component.html index b03c9ae500..f7da0cf82b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-row.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-row.component.html @@ -116,6 +116,24 @@ (keyEdit)="editKey('circleKey')" formControlName="circleKey"> + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.ts index 5b949bae75..ab32155420 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.ts @@ -145,6 +145,7 @@ export class MapSettingsComponent implements OnInit, ControlValueAccessor, Valid markers: [null, []], polygons: [null, []], circles: [null, []], + polylines: [null, []], additionalDataSources: [null, []], controlsPosition: [null, []], zoomActions: [null, []], @@ -180,7 +181,8 @@ export class MapSettingsComponent implements OnInit, ControlValueAccessor, Valid }); merge(this.mapSettingsFormGroup.get('markers').valueChanges, this.mapSettingsFormGroup.get('polygons').valueChanges, - this.mapSettingsFormGroup.get('circles').valueChanges + this.mapSettingsFormGroup.get('circles').valueChanges, + this.mapSettingsFormGroup.get('polylines').valueChanges ).pipe( takeUntilDestroyed(this.destroyRef) ).subscribe(() => { @@ -281,6 +283,10 @@ export class MapSettingsComponent implements OnInit, ControlValueAccessor, Valid const polygons: MapDataLayerSettings[] = this.mapSettingsFormGroup.get('polygons').value; dragModeButtonSettingsEnabled = polygons.some(d => d.edit && d.edit.enabledActions && d.edit.enabledActions.includes(DataLayerEditAction.move)); } + if (!dragModeButtonSettingsEnabled) { + const polylines: MapDataLayerSettings[] = this.mapSettingsFormGroup.get('polylines').value; + dragModeButtonSettingsEnabled = polylines.some(d => d.edit && d.edit.enabledActions && d.edit.enabledActions.includes(DataLayerEditAction.move)); + } if (!dragModeButtonSettingsEnabled) { const circles: MapDataLayerSettings[] = this.mapSettingsFormGroup.get('circles').value; dragModeButtonSettingsEnabled = circles.some(d => d.edit && d.edit.enabledActions && d.edit.enabledActions.includes(DataLayerEditAction.move)); diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 01ac1b2aaa..c27639e45c 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -659,7 +659,8 @@ export enum MapItemType { marker = 'marker', polygon = 'polygon', rectangle = 'rectangle', - circle = 'circle' + circle = 'circle', + polyline = 'polyline' } export const widgetActionTypes = Object.keys(WidgetActionType) @@ -1145,5 +1146,4 @@ export abstract class WidgetSettingsComponent extends PageComponent implements protected onWidgetConfigSet(widgetConfig: WidgetConfigComponentData) { } - } diff --git a/ui-ngx/src/app/shared/models/widget/maps/map.models.ts b/ui-ngx/src/app/shared/models/widget/maps/map.models.ts index 8df70edae3..6d47a52478 100644 --- a/ui-ngx/src/app/shared/models/widget/maps/map.models.ts +++ b/ui-ngx/src/app/shared/models/widget/maps/map.models.ts @@ -96,6 +96,9 @@ const mapDataLayerDatasourceDataKeys = (settings: MapDataLayerSettings, case 'circles': dataKeys.push((settings as CirclesDataLayerSettings).circleKey); break; + case 'polylines': + dataKeys.push((settings as PolylinesDataLayerSettings).polylineKey); + break; } return dataKeys; }; @@ -196,9 +199,9 @@ export const defaultBaseDataLayerSettings = (mapType: MapType): Partial { if (!dataLayer.dsType || ![DatasourceType.function, DatasourceType.device, DatasourceType.entity].includes(dataLayer.dsType)) { @@ -232,6 +235,12 @@ export const mapDataLayerValid = (dataLayer: MapDataLayerSettings, type: MapData return false; } break; + case 'polylines': + const polylinesDataLayer = dataLayer as PolylinesDataLayerSettings; + if (!polylinesDataLayer.polylineKey?.type || !polylinesDataLayer.polylineKey?.name) { + return false; + } + break; case 'circles': const circlesDataLayer = dataLayer as CirclesDataLayerSettings; if (!circlesDataLayer.circleKey?.type || !circlesDataLayer.circleKey?.name) { @@ -654,6 +663,58 @@ export const defaultBaseCirclesDataLayerSettings = (mapType: MapType): Partial, defaultBaseDataLayerSettings(mapType), {label: {show: false}, tooltip: {show: false, pattern: '${entityName}

TimeStamp: ${ts:7}'}} as Partial) +export interface PolylinesDataLayerSettings extends ShapeDataLayerSettings { + polylineKey: DataKey; +} + +export const defaultPolylinesDataLayerSettings = (mapType: MapType, functionsOnly = false): PolylinesDataLayerSettings => mergeDeep({ + dsType: functionsOnly ? DatasourceType.function : DatasourceType.entity, + dsLabel: functionsOnly ? 'First polyline' : '', + polylineKey: { + name: functionsOnly ? 'f(x)' : 'perimeter', + label: 'perimeter', + type: functionsOnly ? DataKeyType.function : DataKeyType.attribute, + settings: {}, + color: materialColors[0].value + } +} as PolylinesDataLayerSettings, defaultBasePolylinesDataLayerSettings(mapType) as PolylinesDataLayerSettings); + +export const defaultBasePolylinesDataLayerSettings = (mapType: MapType): Partial => mergeDeep({ + fillType: ShapeFillType.color, + // fillColor: { + // type: DataLayerColorType.constant, + // color: 'rgba(51,136,255,0.2)', + // }, + // fillImage: { + // type: ShapeFillImageType.image, + // image: '/assets/widget-preview-empty.svg', + // preserveAspectRatio: true, + // opacity: 1, + // angle: 0, + // scale: 1 + // }, + // fillStripe: { + // weight: 3, + // color: { + // type: DataLayerColorType.constant, + // color: '#8f8f8f' + // }, + // spaceWeight: 9, + // spaceColor: { + // type: DataLayerColorType.constant, + // color: 'rgba(143,143,143,0)', + // }, + // angle: 45 + // }, + strokeColor: { + type: DataLayerColorType.constant, + color: '#3388ff', + }, + strokeWeight: 3 + } as Partial, defaultBaseDataLayerSettings(mapType), + {label: {show: false}, tooltip: {show: false, pattern: '${entityName}

TimeStamp: ${ts:7}'}} as Partial) + + export const defaultMapDataLayerSettings = (mapType: MapType, dataLayerType: MapDataLayerType, functionsOnly = false): MapDataLayerSettings => { switch (dataLayerType) { case 'trips': @@ -664,6 +725,8 @@ export const defaultMapDataLayerSettings = (mapType: MapType, dataLayerType: Map return defaultPolygonsDataLayerSettings(mapType, functionsOnly); case 'circles': return defaultCirclesDataLayerSettings(mapType, functionsOnly); + case 'polylines': + return defaultPolylinesDataLayerSettings(mapType, functionsOnly); } }; @@ -677,6 +740,8 @@ export const defaultBaseMapDataLayerSettings = ( return defaultBasePolygonsDataLayerSettings(mapType) as T; case 'circles': return defaultBaseCirclesDataLayerSettings(mapType) as T; + case 'polylines': + return defaultBasePolylinesDataLayerSettings(mapType) as T; } } @@ -815,6 +880,7 @@ export interface BaseMapSettings { markers: MarkersDataLayerSettings[]; polygons: PolygonsDataLayerSettings[]; circles: CirclesDataLayerSettings[]; + polylines: PolylinesDataLayerSettings[]; additionalDataSources: AdditionalMapDataSourceSettings[]; controlsPosition: MapControlsPosition; zoomActions: MapZoomAction[]; @@ -839,6 +905,7 @@ export const defaultBaseMapSettings: BaseMapSettings = { markers: [], polygons: [], circles: [], + polylines: [], additionalDataSources: [], controlsPosition: MapControlsPosition.topleft, zoomActions: [MapZoomAction.scroll, MapZoomAction.doubleClick, MapZoomAction.controlButtons], @@ -1245,6 +1312,13 @@ export type TbPolyData = L.LatLngTuple[] | L.LatLngTuple[][] | L.LatLngTuple[][] export type TbPolygonCoordinate = L.LatLng | L.LatLng[] | L.LatLng[][]; export type TbPolygonCoordinates = TbPolygonCoordinate[]; + +export type TbPolylineRawCoordinate = L.LatLngTuple | L.LatLngTuple[] | L.LatLngTuple[][]; +export type TbPolylineRawCoordinates = TbPolylineRawCoordinate[]; +export type TbPolylineData = L.LatLngTuple[] | L.LatLngTuple[][]; +export type TbPolylineCoordinate = L.LatLng | L.LatLng[] | L.LatLng[][]; +export type TbPolylineCoordinates = TbPolylineCoordinate[]; + export interface TbCircleData { latitude: number; longitude: number; From 9d19ffc951d5502f4ac9f441d2144fc429471a67 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Wed, 8 Oct 2025 17:28:58 +0300 Subject: [PATCH 10/38] move widget style generator from css rules to css variables --- .../widget/lib/table-widget.models.ts | 135 ++++-------------- .../components/widget/lib/table-widget.scss | 63 +++++++- 2 files changed, 93 insertions(+), 105 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts index b6069d4214..64e5e9c1f7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts @@ -451,110 +451,37 @@ export function constructTableCssString(widgetConfig: WidgetConfig): string { const mdDarkDisabled = defaultColor.setAlpha(0.26).toRgbString(); const mdDarkDisabled2 = defaultColor.setAlpha(0.38).toRgbString(); const mdDarkDivider = defaultColor.setAlpha(0.12).toRgbString(); - - const cssString = - '.mat-mdc-input-element::placeholder {\n' + - ' color: ' + mdDarkSecondary + ';\n' + - '}\n' + - '.mat-mdc-input-element::-moz-placeholder {\n' + - ' color: ' + mdDarkSecondary + ';\n' + - '}\n' + - '.mat-mdc-input-element::-webkit-input-placeholder {\n' + - ' color: ' + mdDarkSecondary + ';\n' + - '}\n' + - '.mat-mdc-input-element:-ms-input-placeholder {\n' + - ' color: ' + mdDarkSecondary + ';\n' + - '}\n' + - 'mat-toolbar.mat-mdc-table-toolbar {\n' + - 'color: ' + mdDark + ';\n' + - '}\n' + - 'mat-toolbar.mat-mdc-table-toolbar:not([color="primary"]) button.mat-mdc-icon-button mat-icon {\n' + - 'color: ' + mdDarkSecondary + ';\n' + - '}\n' + - '.mat-mdc-tab .mdc-tab__text-label {\n' + - 'color: ' + mdDark + ';\n' + - '}\n' + - '.mat-mdc-tab-header-pagination-chevron {\n' + - 'border-color: ' + mdDark + ';\n' + - '}\n' + - '.mat-mdc-tab-header-pagination-disabled .mat-mdc-tab-header-pagination-chevron {\n' + - 'border-color: ' + mdDarkDisabled2 + ';\n' + - '}\n' + - '.mat-mdc-table .mat-mdc-header-row {\n' + - 'background-color: ' + origBackgroundColor + ';\n' + - '}\n' + - '.mat-mdc-table .mat-mdc-header-cell {\n' + - 'color: ' + mdDarkSecondary + ';\n' + - '}\n' + - '.mat-mdc-table .mat-mdc-cell, .mat-mdc-table .mat-mdc-header-cell {\n' + - 'border-bottom-color: ' + mdDarkDivider + ';\n' + - '}\n' + - '.mat-mdc-table .mat-mdc-cell .mat-mdc-checkbox ' + - '.mdc-checkbox__native-control:focus:enabled:not(:checked):not(:indeterminate):not([data-indeterminate=true])'+ - '~.mdc-checkbox__background, ' + - '.mat-table .mat-header-cell .mat-mdc-checkbox ' + - '.mdc-checkbox__native-control:focus:enabled:not(:checked):not(:indeterminate):not([data-indeterminate=true])'+ - '~.mdc-checkbox__background {\n' + - 'border-color: ' + mdDarkSecondary + ';\n' + - '}\n' + - '.mat-mdc-table .mat-mdc-row .mat-mdc-cell.mat-mdc-table-sticky {\n' + - 'transition: background-color .2s;\n' + - '}\n' + - '.mat-mdc-table .mat-mdc-row.tb-current-entity {\n' + - 'background-color: ' + currentEntityColor + ';\n' + - '}\n' + - '.mat-mdc-table .mat-mdc-row.tb-current-entity .mat-mdc-cell.mat-mdc-table-sticky {\n' + - 'background-color: ' + currentEntityStickyColor + ';\n' + - '}\n' + - '.mat-mdc-table .mat-mdc-row:hover:not(.tb-current-entity) {\n' + - 'background-color: ' + hoverColor + ';\n' + - '}\n' + - '.mat-mdc-table .mat-mdc-row:hover:not(.tb-current-entity) .mat-mdc-cell.mat-mdc-table-sticky {\n' + - 'background-color: ' + hoverStickyColor + ';\n' + - '}\n' + - '.mat-mdc-table .mat-mdc-row.mat-row-select.mat-selected:not(.tb-current-entity) {\n' + - 'background-color: ' + selectedColor + ';\n' + - '}\n' + - '.mat-mdc-table .mat-mdc-row.mat-row-select.mat-selected:not(.tb-current-entity) .mat-mdc-cell.mat-mdc-table-sticky {\n' + - 'background-color: ' + selectedStickyColor + ';\n' + - '}\n' + - '.mat-mdc-table .mat-mdc-row .mat-mdc-cell.mat-mdc-table-sticky, .mat-mdc-table .mat-mdc-header-cell.mat-mdc-table-sticky {\n' + - 'background-color: ' + origBackgroundColor + ';\n' + - '}\n' + - '.mat-mdc-table .mat-mdc-row {\n' + - 'color: ' + mdDark + ';\n' + - 'background-color: rgba(0, 0, 0, 0);\n' + - '}\n' + - '.mat-mdc-table .mat-mdc-cell button.mat-mdc-icon-button mat-icon {\n' + - 'color: ' + mdDarkSecondary + ';\n' + - '}\n' + - '.mat-mdc-table .mat-mdc-cell button.mat-mdc-icon-button[disabled][disabled] mat-icon {\n' + - 'color: ' + mdDarkDisabled + ';\n' + - '}\n' + - '.mat-mdc-table .mat-mdc-cell button.mat-mdc-icon-button tb-icon {\n' + - 'color: ' + mdDarkSecondary + ';\n' + - '}\n' + - '.mat-mdc-table .mat-mdc-cell button.mat-mdc-icon-button[disabled][disabled] tb-icon {\n' + - 'color: ' + mdDarkDisabled + ';\n' + - '}\n' + - '.mat-divider {\n' + - 'border-top-color: ' + mdDarkDivider + ';\n' + - '}\n' + - '.mat-mdc-paginator {\n' + - 'color: ' + mdDarkSecondary + ';\n' + - '}\n' + - '.mat-mdc-paginator button.mat-mdc-icon-button {\n' + - 'color: ' + mdDarkSecondary + ';\n' + - '}\n' + - '.mat-mdc-paginator button.mat-mdc-icon-button[disabled][disabled] {\n' + - 'color: ' + mdDarkDisabled + ';\n' + - '}\n' + - '.mat-mdc-paginator svg.mat-mdc-paginator-icon {\n' + - 'fill: currentColor;\n' + - '}\n' + - '.mat-mdc-paginator .mat-mdc-select-value {\n' + - 'color: ' + mdDarkSecondary + ';\n' + - '}'; + + const cssString = ` { + --mat-toolbar-container-text-color: ${mdDark}; + --mat-tab-header-inactive-label-text-color: ${mdDarkSecondary}; + --mat-tab-header-pagination-icon-color: ${mdDark}; + --mat-tab-header-pagination-disabled-icon-color: ${mdDarkDisabled2}; + --mat-table-header-headline-color: ${mdDarkSecondary}; + --mat-table-row-item-label-text-color: ${mdDark}; + --mat-icon-color: ${mdDarkSecondary}; + --mdc-icon-button-disabled-icon-color: ${mdDarkDisabled}; + --mat-divider-color: ${mdDarkDivider}; + --mat-paginator-container-text-color: ${mdDarkSecondary}; + --mdc-icon-button-icon-color: ${mdDarkSecondary}; + --mat-paginator-enabled-icon-color: ${mdDarkSecondary}; + --mat-paginator-disabled-icon-color: ${mdDarkDisabled}; + --mat-select-enabled-trigger-text-color: ${mdDarkSecondary}; + --mat-select-disabled-trigger-text-color: ${mdDarkDisabled}; + --mat-table-header-headline-color: ${mdDarkSecondary}; + + --tb-orig-background-color: ${origBackgroundColor}; + --tb-secondary-text-color: ${mdDarkSecondary}; + --tb-second-disabled-color: ${mdDarkDisabled2}; + --tb-divider-color: ${mdDarkDivider}; + --tb-current-entity-color: ${currentEntityColor}; + --tb-current-entity-sticky-color: ${currentEntityStickyColor}; + --tb-hover-color: ${hoverColor}; + --tb-hover-sticky-color: ${hoverStickyColor}; + --tb-selected-color: ${selectedColor}; + --tb-selected-sticky-color: ${selectedStickyColor}; + } + `; return cssString; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.scss b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.scss index 3df2c7d0a2..7c14750ba8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.scss @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -:host { +:host ::ng-deep{ .tb-table-widget { .mat-mdc-table, .mat-mdc-paginator, mat-toolbar.mat-mdc-table-toolbar:not([color="primary"]) { background: transparent; @@ -25,6 +25,9 @@ height: 39px; max-height: 39px; } + .mat-mdc-table-toolbar:not([color="primary"]) .mat-toolbar-tools button.mat-mdc-icon-button mat-icon { + color:var(--tb-secondary-text-color) + } } .table-container { overflow: auto; @@ -44,7 +47,65 @@ padding: 0 5px; } } + + .mat-mdc-tab-header-pagination-disabled .mat-mdc-tab-header-pagination-chevron { + border-color: var(--tb-second-disabled-color); + } } + + .mat-mdc-input-element { + &::placeholder, + &::-moz-placeholder, + &::-webkit-input-placeholder, + &::-ms-input-placeholder { + color: var(--tb-secondary-text-color); + } + } + + .mat-mdc-table { + .mat-mdc-header-row { + background-color: var(--tb-orig-background-color); + } + + .mat-mdc-cell, .mat-mdc-header-cell { + border-bottom-color: var(--tb-divider-color); + &.mat-mdc-table-sticky{ + background-color:var(--tb-orig-background-color) + } + .mat-mdc-checkbox { + .mdc-checkbox__native-control:focus:enabled:not(:checked):not(:indeterminate):not([data-indeterminate='true']) ~ .mdc-checkbox__background { + border-color: var(--tb-secondary-text-color); + } + } + } + + .mat-mdc-row { + background-color: rgba(0,0,0,0); + &.tb-current-entity { + background-color: var(--tb-current-entity-color); + + .mat-mdc-cell.mat-mdc-table-sticky { + background-color: var(--tb-current-entity-sticky-color); + } + } + &:hover:not(.tb-current-entity){ + background-color: var(--tb-hover-color); + .mat-mdc-cell.mat-mdc-table-sticky{ + background-color: var(--tb-hover-sticky-color); + } + } + &.mat-row-select.mat-selected:not(.tb-current-entity){ + background-color: var(--tb-selected-color); + .mat-mdc-cell.mat-mdc-table-sticky{ + background-color: var(--tb-selected-sticky-color); + } + } + .mat-mdc-cell.mat-mdc-table-sticky { + transition: background-color .2s; + background-color:var(--tb-orig-background-color) + } + } + } } :host-context(.tb-has-timewindow) { From e7086616ecdd386b06e1b5eae6d18145471e4a4d Mon Sep 17 00:00:00 2001 From: LeoMorgan113 Date: Fri, 10 Oct 2025 16:33:44 +0300 Subject: [PATCH 11/38] Updated translation, added helper functions, updated settings --- .../maps/data-layer/polylines-data-layer.ts | 36 +++--- .../map/map-data-layer-dialog.component.ts | 110 +++++++++++++++--- .../map/map-data-layer-row.component.ts | 4 +- .../common/map/map-data-layers.component.ts | 6 +- .../shared/models/widget/maps/map.models.ts | 70 +++++------ .../en_US/widget/lib/map/polyline_label_fn.md | 22 ++++ .../widget/lib/map/polyline_tooltip_fn.md | 22 ++++ .../assets/locale/locale.constant-en_US.json | 15 ++- 8 files changed, 209 insertions(+), 76 deletions(-) create mode 100644 ui-ngx/src/assets/help/en_US/widget/lib/map/polyline_label_fn.md create mode 100644 ui-ngx/src/assets/help/en_US/widget/lib/map/polyline_tooltip_fn.md diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polylines-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polylines-data-layer.ts index b9fa4a692c..a6823b40d0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polylines-data-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polylines-data-layer.ts @@ -72,7 +72,7 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem, dsData: FormattedData[]): L.Layer { const polyData = this.dataLayer.extractPolylineCoordinates(data); const polyConstructor = L.polyline; - this.polyline = polyConstructor(polyData as (TbPolygonRawCoordinates & L.LatLngTuple[]), { + this.polyline = polyConstructor(polyData as (TbPolylineRawCoordinates & L.LatLngTuple[]), { // noClip: true, // snapIgnore: !this.dataLayer.isSnappable(), bubblingMouseEvents: !this.dataLayer.isEditMode() @@ -80,6 +80,7 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem { this.polylineStyleInfo = styleInfo; + if (this.polyline) { this.polyline.setStyle(this.polylineStyleInfo.style); } @@ -107,6 +108,7 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem { - this.savePolygonCoordinates(); + this.savePolylineCoordinates(); this.editing = false; }); } @@ -229,16 +231,16 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem this.editing = true); - // this.polyline.on('pm:markerdragend', () => setTimeout(() => { - // this.editing = false; - // }) ); - // this.polyline.on('pm:edit', () => this.savePolygonCoordinates()); - // this.polyline.pm.enable(); - // const map = this.dataLayer.getMap(); - // map.getEditToolbar().getButton('remove')?.setDisabled(false); - // } + private enablePolylineEditMode() { + this.polyline.on('pm:markerdragstart', () => this.editing = true); + this.polyline.on('pm:markerdragend', () => setTimeout(() => { + this.editing = false; + }) ); + this.polyline.on('pm:edit', () => this.savePolylineCoordinates()); + this.polyline.pm.enable(); + const map = this.dataLayer.getMap(); + map.getEditToolbar().getButton('remove')?.setDisabled(false); + } // private disablePolygonEditMode() { // this.polyline.pm.disable(); @@ -337,10 +339,10 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem this.updateValidators() @@ -291,6 +298,32 @@ export class MapDataLayerDialogComponent extends DialogComponent + this.updateValidators() + ); + break; } this.dataLayerFormGroup.get('dsType').valueChanges.pipe( takeUntilDestroyed(this.destroyRef) @@ -325,6 +358,12 @@ export class MapDataLayerDialogComponent extends DialogComponent mergeDeep( defaultMarkersDataSourceSettings(mapType, true, functionsOnly) as TripsDataLayerSettings, defaultBaseTripsDataLayerSettings(mapType) as TripsDataLayerSettings); @@ -663,7 +666,7 @@ export const defaultBaseCirclesDataLayerSettings = (mapType: MapType): Partial, defaultBaseDataLayerSettings(mapType), {label: {show: false}, tooltip: {show: false, pattern: '${entityName}

TimeStamp: ${ts:7}'}} as Partial) -export interface PolylinesDataLayerSettings extends ShapeDataLayerSettings { +export interface PolylinesDataLayerSettings extends ShapeDataLayerSettings, PathDataLayerSettings { polylineKey: DataKey; } @@ -680,37 +683,34 @@ export const defaultPolylinesDataLayerSettings = (mapType: MapType, functionsOnl } as PolylinesDataLayerSettings, defaultBasePolylinesDataLayerSettings(mapType) as PolylinesDataLayerSettings); export const defaultBasePolylinesDataLayerSettings = (mapType: MapType): Partial => mergeDeep({ - fillType: ShapeFillType.color, - // fillColor: { - // type: DataLayerColorType.constant, - // color: 'rgba(51,136,255,0.2)', - // }, - // fillImage: { - // type: ShapeFillImageType.image, - // image: '/assets/widget-preview-empty.svg', - // preserveAspectRatio: true, - // opacity: 1, - // angle: 0, - // scale: 1 - // }, - // fillStripe: { - // weight: 3, - // color: { - // type: DataLayerColorType.constant, - // color: '#8f8f8f' - // }, - // spaceWeight: 9, - // spaceColor: { - // type: DataLayerColorType.constant, - // color: 'rgba(143,143,143,0)', - // }, - // angle: 45 - // }, strokeColor: { type: DataLayerColorType.constant, color: '#3388ff', }, - strokeWeight: 3 + usePathDecorator: false, + pathDecoratorSymbol: PathDecoratorSymbol.arrowHead, + pathDecoratorSymbolSize: 10, + pathDecoratorSymbolColor: '#307FE5', + pathDecoratorOffset: 20, + pathEndDecoratorOffset: 20, + pathDecoratorRepeat: 20, + showPoints: false, + pointSize: 10, + pointColor: { + type: DataLayerColorType.constant, + color: '#307FE5', + }, + pointTooltip: { + show: true, + trigger: DataLayerTooltipTrigger.click, + autoclose: true, + type: DataLayerPatternType.pattern, + pattern: mapType === MapType.geoMap ? + '${entityName}

Latitude: ${latitude:7}
Longitude: ${longitude:7}
End Time: ${maxTime}
Start Time: ${minTime}' + : '${entityName}

X Pos: ${xPos:2}
Y Pos: ${yPos:2}
End Time: ${maxTime}
Start Time: ${minTime}', + offsetX: 0, + offsetY: -1 + }, } as Partial, defaultBaseDataLayerSettings(mapType), {label: {show: false}, tooltip: {show: false, pattern: '${entityName}

TimeStamp: ${ts:7}'}} as Partial) diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/map/polyline_label_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/map/polyline_label_fn.md new file mode 100644 index 0000000000..b2eafef8cb --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/widget/lib/map/polyline_label_fn.md @@ -0,0 +1,22 @@ +#### Polyline label function + +
+
+ +*function (data, dsData): string* + +A JavaScript function used to compute text or HTML code of the polyline label. + +**Parameters:** + +
    + {% include widget/lib/map/map_fn_args %} +
+ +**Returns:** + +Should return string value presenting text or HTML of the polyline label. + +
+ +{% include widget/lib/map/label_fn_examples %} diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/map/polyline_tooltip_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/map/polyline_tooltip_fn.md new file mode 100644 index 0000000000..0de9b8ee7a --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/widget/lib/map/polyline_tooltip_fn.md @@ -0,0 +1,22 @@ +#### Polyline tooltip function + +
+
+ +*function (data, dsData): string* + +A JavaScript function used to compute text or HTML code to be displayed in the polyline tooltip. + +**Parameters:** + +
    + {% include widget/lib/map/map_fn_args %} +
+ +**Returns:** + +Should return string value presenting text or HTML for the polyline tooltip. + +
+ +{% include widget/lib/map/tooltip_fn_examples %} diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 4a347ab6cd..40302a0eae 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -8432,7 +8432,8 @@ "trips": "Trips", "markers": "Markers", "polygons": "Polygons", - "circles": "Circles" + "circles": "Circles", + "polylines": "Polylines" }, "data-layer": { "source": "Source", @@ -8629,6 +8630,18 @@ "finish-circle-hint-with-entity": "Circle for '{{entityName}}': click to finish and save circle", "finish-circle-hint": "Circle: click to finish drawing" }, + "polyline": { + "polyline-key": "Polyline key", + "polyline-key-required": "Polyline key required", + "no-polylines": "No polylines configured", + "add-polylines": "Add polyline", + "polyline-configuration": "Polyline configuration", + "remove-polyline": "Remove polyline", + "edit": "Edit polyline", + "remove-polyline-for": "Remove polyline for '{{entityName}}'", + "draw-polyline": "Draw polyline", + "finish-polyline-hint": "Polyline: click to finish drawing" + }, "select-entity": "Select entity", "select-entity-hint": "Hint: after selection click at the map to set position" }, From 02bc6fd8b2629412bf1bdecdaf8bac8924ae7b1f Mon Sep 17 00:00:00 2001 From: LeoMorgan113 Date: Fri, 10 Oct 2025 16:49:34 +0300 Subject: [PATCH 12/38] Updated translation, added helper functions, updated settings --- .../map/map-data-layer-dialog.component.html | 22 +++++++++++++++++++ .../map/map-data-layer-dialog.component.ts | 17 +++++++------- .../shared/models/widget/maps/map.models.ts | 14 ++++++------ 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.html index 0ca912146c..1369deefcb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.html @@ -163,6 +163,26 @@ (keyEdit)="editKey('circleKey')" formControlName="circleKey"> + +
+ +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.ts index 3563a9aaca..27a06f9a32 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.ts @@ -306,18 +306,19 @@ export class MapDataLayerDialogComponent extends DialogComponent diff --git a/ui-ngx/src/app/shared/models/widget/maps/map.models.ts b/ui-ngx/src/app/shared/models/widget/maps/map.models.ts index 62f9e5ab0a..e3e9de6839 100644 --- a/ui-ngx/src/app/shared/models/widget/maps/map.models.ts +++ b/ui-ngx/src/app/shared/models/widget/maps/map.models.ts @@ -687,13 +687,13 @@ export const defaultBasePolylinesDataLayerSettings = (mapType: MapType): Partial type: DataLayerColorType.constant, color: '#3388ff', }, - usePathDecorator: false, - pathDecoratorSymbol: PathDecoratorSymbol.arrowHead, - pathDecoratorSymbolSize: 10, - pathDecoratorSymbolColor: '#307FE5', - pathDecoratorOffset: 20, - pathEndDecoratorOffset: 20, - pathDecoratorRepeat: 20, + // usePathDecorator: false, + // pathDecoratorSymbol: PathDecoratorSymbol.arrowHead, + // pathDecoratorSymbolSize: 10, + // pathDecoratorSymbolColor: '#307FE5', + // pathDecoratorOffset: 20, + // pathEndDecoratorOffset: 20, + // pathDecoratorRepeat: 20, showPoints: false, pointSize: 10, pointColor: { From 344f219062ad376c04cbd98f17336f1773c4ecd8 Mon Sep 17 00:00:00 2001 From: LeoMorgan113 Date: Fri, 10 Oct 2025 18:00:52 +0300 Subject: [PATCH 13/38] Updated translations, added settings, added control for polyline --- .../maps/data-layer/polylines-data-layer.ts | 218 ++++++------------ .../lib/maps/data-layer/shapes-data-layer.ts | 17 ++ .../home/components/widget/lib/maps/map.scss | 3 + .../home/components/widget/lib/maps/map.ts | 24 ++ .../map/map-data-layer-dialog.component.html | 6 +- .../map/map-data-layer-dialog.component.ts | 32 +-- .../common/map/map-data-layers.component.html | 1 + .../shared/models/widget/maps/map.models.ts | 3 +- .../widget/lib/map/path_point_color_fn.md | 4 +- .../widget/lib/map/path_point_tooltip_fn.md | 2 +- .../lib/map/polyline_stroke_color_fn.md | 24 ++ .../assets/locale/locale.constant-en_US.json | 2 + 12 files changed, 167 insertions(+), 169 deletions(-) create mode 100644 ui-ngx/src/assets/help/en_US/widget/lib/map/polyline_stroke_color_fn.md diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polylines-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polylines-data-layer.ts index a6823b40d0..2e1b22f157 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polylines-data-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polylines-data-layer.ts @@ -73,14 +73,13 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem { + this.dataLayer.getStrokeStyle(data, dsData, this.polylineStyleInfo?.patternId).subscribe((styleInfo) => { this.polylineStyleInfo = styleInfo; - if (this.polyline) { this.polyline.setStyle(this.polylineStyleInfo.style); } @@ -103,7 +102,7 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem, dsData: FormattedData[]): void { - this.dataLayer.getShapeStyle(data, dsData, this.polylineStyleInfo?.patternId).subscribe((styleInfo) => { + this.dataLayer.getStrokeStyle(data, dsData, this.polylineStyleInfo?.patternId).subscribe((styleInfo) => { this.polylineStyleInfo = styleInfo; this.updatePolylineShape(data); this.updateTooltip(data, dsData); @@ -159,36 +158,33 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem { - const map = this.dataLayer.getMap().getMap(); - // if (!map.pm.globalCutModeEnabled()) { - // this.disablePolygonRotateMode(); - // this.disablePolygonEditMode(); - // this.enablePolygonCutMode(button); - // } else { - // this.disablePolygonCutMode(button); - // this.enablePolygonEditMode(); - // } - } - }, + // { + // id: 'cut', + // title: this.getDataLayer().getCtx().translate.instant('widgets.maps.data-layer.polygon.cut'), + // iconClass: 'tb-cut', + // click: (e, button) => { + // const map = this.dataLayer.getMap().getMap(); + // if (!map.pm.globalCutModeEnabled()) { + // this.disablePolylineRotateMode(); + // this.disablePolylineEditMode(); + // } else { + // this.enablePolylineEditMode(); + // } + // } + // }, { id: 'rotate', title: this.getDataLayer().getCtx().translate.instant('widgets.maps.data-layer.polygon.rotate'), iconClass: 'tb-rotate', click: (e, button) => { if (!this.polyline.pm.rotateEnabled()) { - // this.disablePolygonCutMode(); - // this.disablePolygonEditMode(); - // this.enablePolygonRotateMode(button); + this.disablePolylineEditMode(); + this.enablePolylineRotateMode(button); } else { - // this.disablePolygonRotateMode(button); - // this.enablePolygonEditMode(); + this.disablePolylineRotateMode(button); + this.enablePolylineEditMode(); } } } @@ -199,22 +195,15 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem { - // if (e.layer instanceof L.Polygon) { - // if (this.polyline instanceof L.Rectangle) { - // this.polylineContainer.removeLayer(this.polyline); - // this.polyline = L.polyline(e.layer.getLatLngs(), { - // ...this.polylineStyleInfo.style, - // snapIgnore: !this.dataLayer.isSnappable(), - // bubblingMouseEvents: !this.dataLayer.isEditMode() - // }); - // this.polyline.addTo(this.polylineContainer); - // } else { - // this.polyline.setLatLngs(e.layer.getLatLngs()); - // } - // } - // // @ts-ignore - // e.layer._pmTempLayer = true; - // e.layer.remove(); - // this.polylineContainer.removeLayer(this.polyline); - // // @ts-ignore - // this.polyline._pmTempLayer = false; - // this.polyline.addTo(this.polylineContainer); - // this.updateSelectedState(); - // cutButton?.setActive(false); - // this.savePolygonCoordinates() - // }); - // const map = this.dataLayer.getMap().getMap(); - // map.pm.setLang('en', { - // tooltips: { - // firstVertex: this.getDataLayer().getCtx().translate.instant('widgets.maps.data-layer.polygon.polygon-place-first-point-cut-hint'), - // continueLine: this.getDataLayer().getCtx().translate.instant('widgets.maps.data-layer.polygon.continue-polygon-cut-hint'), - // finishPoly: this.getDataLayer().getCtx().translate.instant('widgets.maps.data-layer.polygon.finish-polygon-cut-hint') - // } - // }, 'en'); - // map.pm.enableGlobalCutMode({ - // // @ts-ignore - // layersToCut: [this.polyline] - // }); - // // @ts-ignore - // L.DomUtil.addClass(map.pm.Draw.Cut._hintMarker.getTooltip()._container, 'tb-place-item-label'); - // cutButton?.setActive(true); - // map.once('pm:globalcutmodetoggled', (e) => { - // // if (!e.enabled) { - // // this.disablePolygonCutMode(cutButton); - // // this.enablePolygonEditMode(); - // // } - // }); - // } - - // private disablePolygonCutMode(cutButton?: L.TB.ToolbarButton) { - // this.editing = false; - // this.polyline.options.bubblingMouseEvents = !this.dataLayer.isEditMode(); - // this.polyline.setStyle({...this.polylineStyleInfo.style, dashArray: null}); - // this.removeItemClass('tb-cut-mode'); - // this.polyline.off('pm:cut'); - // const map = this.dataLayer.getMap().getMap(); - // map.pm.disableGlobalCutMode(); - // cutButton?.setActive(false); - // } - - // private enablePolygonRotateMode(rotateButton?: L.TB.ToolbarButton) { - // this.polylineContainer.closePopup(); - // this.editing = true; - // this.polyline.on('pm:rotateend', () => { - // this.savePolygonCoordinates(); - // }); - // this.polyline.pm.enableRotate(); - // rotateButton?.setActive(true); - // this.polyline.on('pm:rotatedisable', () => { - // this.disablePolygonRotateMode(rotateButton); - // this.enablePolygonEditMode(); - // }); - // } - // - // private disablePolygonRotateMode(rotateButton?: L.TB.ToolbarButton) { - // this.editing = false; - // this.polyline.pm.disableRotate(); - // this.polyline.off('pm:rotateend'); - // this.polyline.off('pm:rotatedisable'); - // rotateButton?.setActive(false); - // } + private disablePolylineEditMode() { + this.polyline.pm.disable(); + this.polyline.off('pm:markerdragstart'); + this.polyline.off('pm:markerdragend'); + this.polyline.off('pm:edit'); + const map = this.dataLayer.getMap(); + map.getEditToolbar().getButton('remove')?.setDisabled(true); + } + + private enablePolylineRotateMode(rotateButton?: L.TB.ToolbarButton) { + this.polylineContainer.closePopup(); + this.editing = true; + this.polyline.on('pm:rotateend', () => { + this.savePolylineCoordinates(); + }); + this.polyline.pm.enableRotate(); + rotateButton?.setActive(true); + this.polyline.on('pm:rotatedisable', () => { + this.disablePolylineRotateMode(rotateButton); + this.enablePolylineEditMode(); + }); + } + + private disablePolylineRotateMode(rotateButton?: L.TB.ToolbarButton) { + this.editing = false; + this.polyline.pm.disableRotate(); + this.polyline.off('pm:rotateend'); + this.polyline.off('pm:rotatedisable'); + rotateButton?.setActive(false); + } private savePolylineCoordinates() { let coordinates: TbPolylineCoordinates = this.polyline.getLatLngs(); @@ -361,20 +284,20 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem, dsData: FormattedData[]): TbPolylineDataLayerItem { return new TbPolylineDataLayerItem(data, dsData, this.settings, this); } - } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/shapes-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/shapes-data-layer.ts index 93502588b1..c24d40c5bf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/shapes-data-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/shapes-data-layer.ts @@ -311,6 +311,23 @@ export abstract class TbShapesDataLayer, dsData: FormattedData[], fillPatternId: string): Observable { + return this.shapePatternProcessor.processPattern(data, dsData, fillPatternId).pipe( + map((patternWithId) => { + const stroke = this.strokeColorProcessor.processColor(data, dsData); + const style: L.PathOptions = { + color: stroke, + weight: this.settings.strokeWeight, + opacity: 1 + }; + return { + patternId: patternWithId.patternId, + style + } + }) + ); + } + public getShapeStyle(data: FormattedData, dsData: FormattedData[], fillPatternId: string): Observable { return this.shapePatternProcessor.processPattern(data, dsData, fillPatternId).pipe( map((patternWithId) => { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.scss b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.scss index 985bdc17f5..5d00f9add6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.scss @@ -169,6 +169,9 @@ div.tb-widget .tb-widget-content.tb-no-interaction { &.tb-draw-circle { mask-image: url('data:image/svg+xml,'); } + &.tb-draw-polyline { + mask-image: url('data:image/svg+xml,'); + } &.tb-close { background: #D12730; mask-image: url('data:image/svg+xml,'); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts index faf328d203..24f88308ff 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts @@ -125,6 +125,7 @@ export abstract class TbMap { protected addRectangleButton: L.TB.ToolbarButton; protected addPolygonButton: L.TB.ToolbarButton; protected addCircleButton: L.TB.ToolbarButton; + protected addPolylineButton: L.TB.ToolbarButton; protected timeLineComponentRef: ComponentRef; protected timeLineComponent: MapTimelinePanelComponent; @@ -133,6 +134,7 @@ export abstract class TbMap { protected addMarkerDataLayers: TbLatestMapDataLayer[]; protected addPolygonDataLayers: TbLatestMapDataLayer[]; protected addCircleDataLayers: TbLatestMapDataLayer[]; + protected addPolylineDataLayers: TbLatestMapDataLayer[]; protected shapePatternStorage: ShapePatternStorage = {}; @@ -506,6 +508,18 @@ export abstract class TbMap { }); this.addCircleButton.setDisabled(true); } + this.addPolylineDataLayers = addSupportedDataLayers.filter(dl => dl.dataLayerType() === 'polylines'); + if (this.addPolylineDataLayers.length) { + this.addPolylineButton = drawToolbar.toolbarButton({ + id: 'addPolyline', + title: this.ctx.translate.instant('widgets.maps.data-layer.polyline.draw-polyline'), + iconClass: 'tb-draw-polyline', + click: (e, button) => { + this.drawPolyline(e, button); + } + }); + this.addPolylineButton.setDisabled(true); + } } } @@ -563,6 +577,13 @@ export abstract class TbMap { })); } + private drawPolyline(e: MouseEvent, button: L.TB.ToolbarButton): void { + this.placeItem(e, button, this.addCircleDataLayers, (entity) => this.prepareDrawMode('Polyline', { + firstVertex: this.ctx.translate.instant('widgets.maps.data-layer.polyline.polyline-place-first-point-hint-with-entity', {entityName: entity.entity.entityDisplayName}), + continueLine: this.ctx.translate.instant('widgets.maps.data-layer.polyline.continue-polyline-hint-with-entity', {entityName: entity.entity.entityDisplayName}), + })); + } + private placeItem(e: MouseEvent, button: L.TB.ToolbarButton, dataLayers: TbLatestMapDataLayer[], prepareDrawMode: (entity: UnplacedMapDataItem) => void): void { if (this.isPlacingItem) { @@ -684,6 +705,9 @@ export abstract class TbMap { case MapItemType.circle: this.createCircle(actionData); break; + case MapItemType.polyline: + // this.createPolyline(actionData); // TODO + break; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.html index 1369deefcb..df53c44be7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.html @@ -465,6 +465,8 @@
+ +
widgets.maps.data-layer.shape.stroke
@@ -477,7 +479,9 @@ [dsType]="dataLayerFormGroup.get('dsType').value" [dsEntityAliasId]="dataLayerFormGroup.get('dsEntityAliasId').value" [dsDeviceId]="dataLayerFormGroup.get('dsDeviceId').value" - helpId="{{ dataLayerType === 'polygons' ? 'widget/lib/map/polygon_stroke_color_fn' : 'widget/lib/map/circle_stroke_color_fn' }}" formControlName="strokeColor"> + helpId="{{ dataLayerType === 'polygons' ? 'widget/lib/map/polygon_stroke_color_fn' : + (dataLayerType === 'circles' ? 'widget/lib/map/polygon_stroke_color_fn' : 'widget/lib/map/polyline_stroke_color_fn') }}" + formControlName="strokeColor">
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.ts index 27a06f9a32..71b6a2b31a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.ts @@ -474,23 +474,23 @@ export class MapDataLayerDialogComponent extends DialogComponent
widgets.maps.data-layer.polygon.polygon-key
widgets.maps.data-layer.circle.circle-key
+
widgets.maps.data-layer.polyline.polyline-key
diff --git a/ui-ngx/src/app/shared/models/widget/maps/map.models.ts b/ui-ngx/src/app/shared/models/widget/maps/map.models.ts index e3e9de6839..99a3bc499b 100644 --- a/ui-ngx/src/app/shared/models/widget/maps/map.models.ts +++ b/ui-ngx/src/app/shared/models/widget/maps/map.models.ts @@ -687,6 +687,7 @@ export const defaultBasePolylinesDataLayerSettings = (mapType: MapType): Partial type: DataLayerColorType.constant, color: '#3388ff', }, + strokeWeight: 3, // usePathDecorator: false, // pathDecoratorSymbol: PathDecoratorSymbol.arrowHead, // pathDecoratorSymbolSize: 10, @@ -701,7 +702,7 @@ export const defaultBasePolylinesDataLayerSettings = (mapType: MapType): Partial color: '#307FE5', }, pointTooltip: { - show: true, + show: false, trigger: DataLayerTooltipTrigger.click, autoclose: true, type: DataLayerPatternType.pattern, diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/map/path_point_color_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/map/path_point_color_fn.md index be95f136ad..9af015d033 100644 --- a/ui-ngx/src/assets/help/en_US/widget/lib/map/path_point_color_fn.md +++ b/ui-ngx/src/assets/help/en_US/widget/lib/map/path_point_color_fn.md @@ -5,7 +5,7 @@ *function (data, dsData): string* -A JavaScript function used to compute color of the trip path point. +A JavaScript function used to compute color of the path point. **Parameters:** @@ -15,7 +15,7 @@ A JavaScript function used to compute color of the trip path point. **Returns:** -Should return string value presenting color of the trip path point. +Should return string value presenting color of the path point. In case no data is returned, color value from **Color** settings field will be used. diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/map/path_point_tooltip_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/map/path_point_tooltip_fn.md index 4ce996626a..e9e202ef85 100644 --- a/ui-ngx/src/assets/help/en_US/widget/lib/map/path_point_tooltip_fn.md +++ b/ui-ngx/src/assets/help/en_US/widget/lib/map/path_point_tooltip_fn.md @@ -5,7 +5,7 @@ *function (data, dsData): string* -A JavaScript function used to compute text or HTML code to be displayed in the trip path point tooltip. +A JavaScript function used to compute text or HTML code to be displayed in the path point tooltip. **Parameters:** diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/map/polyline_stroke_color_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/map/polyline_stroke_color_fn.md new file mode 100644 index 0000000000..6b49763393 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/widget/lib/map/polyline_stroke_color_fn.md @@ -0,0 +1,24 @@ +#### Polyline stroke color function + +
+
+ +*function (data, dsData): string* + +A JavaScript function used to compute stroke color of the polyline. + +**Parameters:** + +
    + {% include widget/lib/map/map_fn_args %} +
+ +**Returns:** + +Should return string value presenting stroke color of the polyline. + +In case no data is returned, color value from **Color** settings field will be used. + +
+ +{% include widget/lib/map/color_fn_examples %} diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 40302a0eae..0e13fda6be 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -8640,6 +8640,8 @@ "edit": "Edit polyline", "remove-polyline-for": "Remove polyline for '{{entityName}}'", "draw-polyline": "Draw polyline", + "polyline-place-first-point-hint": "Polyline: click to place first point", + "continue-polyline-hint-with-entity": "Polyline for '{{entityName}}': click to continue drawing", "finish-polyline-hint": "Polyline: click to finish drawing" }, "select-entity": "Select entity", From 9e70fba25a675fc3202ef13d79a0387ccd5f67a4 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 13 Oct 2025 13:11:15 +0300 Subject: [PATCH 14/38] UI: Add to mobile handler navigation action support queryParams --- ui-ngx/src/app/core/guards/auth.guard.ts | 2 +- ui-ngx/src/app/core/services/mobile.service.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/core/guards/auth.guard.ts b/ui-ngx/src/app/core/guards/auth.guard.ts index f6d8753882..6b333e878c 100644 --- a/ui-ngx/src/app/core/guards/auth.guard.ts +++ b/ui-ngx/src/app/core/guards/auth.guard.ts @@ -121,7 +121,7 @@ export class AuthGuard { } } if (this.mobileService.isMobileApp() && !path.startsWith('dashboard.')) { - this.mobileService.handleMobileNavigation(path, params); + this.mobileService.handleMobileNavigation(path, params, lastChild.queryParams); return of(false); } if (authState.authUser.authority === Authority.PRE_VERIFICATION_TOKEN) { diff --git a/ui-ngx/src/app/core/services/mobile.service.ts b/ui-ngx/src/app/core/services/mobile.service.ts index 6fcb7c3602..aceb564ec2 100644 --- a/ui-ngx/src/app/core/services/mobile.service.ts +++ b/ui-ngx/src/app/core/services/mobile.service.ts @@ -106,9 +106,9 @@ export class MobileService { } } - public handleMobileNavigation(path?: string, params?: Params) { + public handleMobileNavigation(path?: string, params?: Params, queryParams?: Params) { if (this.mobileApp) { - this.mobileChannel.callHandler(navigationHandler, path, params); + this.mobileChannel.callHandler(navigationHandler, path, params, queryParams); } } From e8b2066fb6f38bcc5775ea5b51a0755dfb763df8 Mon Sep 17 00:00:00 2001 From: LeoMorgan113 Date: Mon, 13 Oct 2025 17:12:24 +0300 Subject: [PATCH 15/38] Added logic to create and edit polylines --- .../lib/maps/data-layer/map-data-layer.ts | 22 +- .../maps/data-layer/polygons-data-layer.ts | 19 +- .../maps/data-layer/polylines-data-layer.ts | 97 ++---- .../lib/maps/data-layer/shapes-data-layer.ts | 27 +- .../components/widget/lib/maps/geo-map.ts | 31 +- .../components/widget/lib/maps/image-map.ts | 3 +- .../home/components/widget/lib/maps/map.ts | 322 +++++++++--------- ui-ngx/src/app/shared/models/widget.models.ts | 6 +- .../shared/models/widget/maps/map.models.ts | 61 ++-- .../assets/locale/locale.constant-en_US.json | 5 +- 10 files changed, 307 insertions(+), 286 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts index 4380f36186..f3c13167ae 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts @@ -28,7 +28,6 @@ import { } from '@shared/models/widget/maps/map.models'; import { createLabelFromPattern, - guid, isDefined, isDefinedAndNotNull, isNumber, @@ -53,7 +52,8 @@ export class DataLayerPatternProcessor { private pattern: string; constructor(private dataLayer: TbMapDataLayer, - private settings: DataLayerPatternSettings) {} + private settings: DataLayerPatternSettings) { + } public setup(): Observable { if (this.settings.type === DataLayerPatternType.function) { @@ -91,7 +91,8 @@ export class DataLayerColorProcessor { private range: ColorRange[]; constructor(private dataLayer: TbMapDataLayer, - private settings: DataLayerColorSettings) {} + private settings: DataLayerColorSettings) { + } public setup(): Observable { this.color = this.settings.color; @@ -150,7 +151,8 @@ export abstract class TbDataLayerItem(); - protected groupsState: {[group: string]: boolean} = {}; + protected groupsState: { [group: string]: boolean } = {}; protected enabled = true; @@ -197,7 +199,7 @@ export abstract class TbMapDataLayer settings.type === DataLayerColorType.range && settings.rangeKey) - .map(settings => settings.rangeKey); + .map(settings => settings.rangeKey); dataKeys.push(...colorRangeKeys); dataKeys.push(...this.getDataKeys()); return dataKeys; @@ -316,9 +318,11 @@ export abstract class TbMapDataLayer, dsData: FormattedData[]): void { @@ -148,7 +148,7 @@ class TbPolygonDataLayerItem extends TbLatestDataLayerItem this.editing = true); this.polygon.on('pm:markerdragend', () => setTimeout(() => { this.editing = false; - }) ); + })); this.polygon.on('pm:edit', () => this.savePolygonCoordinates()); this.polygon.pm.enable(); const map = this.dataLayer.getMap(); @@ -246,8 +246,10 @@ class TbPolygonDataLayerItem extends TbLatestDataLayerItem { if (e.layer instanceof L.Polygon) { @@ -365,6 +367,7 @@ class TbPolygonDataLayerItem extends TbLatestDataLayerItem, dsData: FormattedData[]): void { @@ -156,27 +153,13 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem { - // const map = this.dataLayer.getMap().getMap(); - // if (!map.pm.globalCutModeEnabled()) { - // this.disablePolylineRotateMode(); - // this.disablePolylineEditMode(); - // } else { - // this.enablePolylineEditMode(); - // } - // } - // }, { id: 'rotate', - title: this.getDataLayer().getCtx().translate.instant('widgets.maps.data-layer.polygon.rotate'), + title: this.getDataLayer().getCtx().translate.instant('widgets.maps.data-layer.polyline.rotate'), iconClass: 'tb-rotate', click: (e, button) => { if (!this.polyline.pm.rotateEnabled()) { @@ -195,8 +178,8 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem { @@ -224,7 +207,7 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem this.editing = true); this.polyline.on('pm:markerdragend', () => setTimeout(() => { this.editing = false; - }) ); + })); this.polyline.on('pm:edit', () => this.savePolylineCoordinates()); this.polyline.pm.enable(); const map = this.dataLayer.getMap(); @@ -267,13 +250,6 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem boundsArray.find(boundPoint => boundPoint.equals(point as L.LatLng)) !== undefined)) { - coordinates = [bounds.getNorthWest(), bounds.getSouthEast()]; - } - } this.dataLayer.savePolylineCoordinates(this.data, coordinates).subscribe(); } @@ -283,24 +259,24 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem { @@ -340,7 +311,7 @@ export class TbPolylineDataLayer extends TbShapesDataLayer): TbPolygonRawCoordinates { + public extractPolylineCoordinates(data: FormattedData): TbPolylineRawCoordinates { let rawPolyData = data[this.settings.polylineKey.label]; if (isString(rawPolyData)) { rawPolyData = JSON.parse(rawPolyData); @@ -349,7 +320,7 @@ export class TbPolylineDataLayer extends TbShapesDataLayer, coordinates: TbPolylineCoordinates): Observable { - const converted = coordinates ? this.map.coordinatesToPolygonData(coordinates) : null; + const converted = coordinates ? this.map.coordinatesToPolylineData(coordinates) : null; const polylineData = [ { dataKey: this.settings.polylineKey, @@ -366,7 +337,7 @@ export class TbPolylineDataLayer extends TbShapesDataLayer): Partial { - return defaultBasePolygonsDataLayerSettings(map.type()); + return defaultBasePolylinesDataLayerSettings(map.type()); } protected doSetup(): Observable { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/shapes-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/shapes-data-layer.ts index c24d40c5bf..b2a42e5d35 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/shapes-data-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/shapes-data-layer.ts @@ -37,10 +37,12 @@ import { isDefinedAndNotNull, objectHashCode, parseTbFunction, safeExecuteTbFunc import { CompiledTbFunction } from '@shared/models/js-function.models'; import { ImagePipe } from '@shared/pipe/image.pipe'; -export type ShapePatternStorage = {[id: string]: { - pattern: L.TB.Pattern; - refCount: number; -}}; +export type ShapePatternStorage = { + [id: string]: { + pattern: L.TB.Pattern; + refCount: number; + } +}; interface ShapePatternInfo { type: ShapeFillType; @@ -88,7 +90,8 @@ abstract class ShapePatternProcessor { } protected constructor(protected dataLayer: TbMapDataLayer, - protected settings: S) {} + protected settings: S) { + } public abstract setup(): Observable; @@ -119,8 +122,10 @@ abstract class ShapePatternProcessor { let pattern: L.TB.Pattern; if (patternInfo.type === ShapeFillType.color) { pattern = new L.TB.Pattern({width: 1, height: 1}); - const fillRect = new L.TB.PatternRect({x: 0, y: 0, width: 1, height: 1, - fillOpacity: 1, stroke: false, fill: true, fillColor: patternInfo.fillColor}); + const fillRect = new L.TB.PatternRect({ + x: 0, y: 0, width: 1, height: 1, + fillOpacity: 1, stroke: false, fill: true, fillColor: patternInfo.fillColor + }); pattern.addElement(fillRect); } else if (patternInfo.type === ShapeFillType.image) { const patternOptions: L.TB.PatternOptions = { @@ -128,7 +133,7 @@ abstract class ShapePatternProcessor { height: 1, patternUnits: 'objectBoundingBox', patternContentUnits: 'objectBoundingBox', - viewBox: [0,0,patternInfo.fillImage.width,patternInfo.fillImage.height] + viewBox: [0, 0, patternInfo.fillImage.width, patternInfo.fillImage.height] }; if (patternInfo.fillImage.preserveAspectRatio) { patternOptions.preserveAspectRatioAlign = 'xMidYMid'; @@ -301,7 +306,7 @@ class ShapeStripePatternProcessor extends ShapePatternProcessor> extends TbLatestMapDataLayer { +export abstract class TbShapesDataLayer> extends TbLatestMapDataLayer { private shapePatternProcessor: ShapePatternProcessor; private strokeColorProcessor: DataLayerColorProcessor; @@ -349,7 +354,7 @@ export abstract class TbShapesDataLayer { this.shapePatternProcessor = ShapePatternProcessor.fromSettings(this, this.settings); this.strokeColorProcessor = new DataLayerColorProcessor(this, this.settings.strokeColor); - return forkJoin([this.shapePatternProcessor.setup(), this.strokeColorProcessor.setup()]); + return forkJoin([this.shapePatternProcessor ? this.shapePatternProcessor.setup() : of([]), this.strokeColorProcessor.setup()]); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/geo-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/geo-map.ts index d28c0359f2..2fc1f54d81 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/geo-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/geo-map.ts @@ -61,16 +61,16 @@ export class TbGeoMap extends TbMap { return of(map); } - protected onResize(): void {} + protected onResize(): void { + } protected fitBounds(bounds: L.LatLngBounds) { if (bounds.isValid()) { if (!this.settings.fitMapBounds && this.settings.defaultZoomLevel) { - this.map.setZoom(this.settings.defaultZoomLevel, { animate: false }); + this.map.setZoom(this.settings.defaultZoomLevel, {animate: false}); if (this.settings.useDefaultCenterPosition) { - this.map.panTo(this.defaultCenterPosition, { animate: false }); - } - else { + this.map.panTo(this.defaultCenterPosition, {animate: false}); + } else { this.map.panTo(bounds.getCenter()); } } else { @@ -80,13 +80,13 @@ export class TbGeoMap extends TbMap { minZoom = Math.max(minZoom, this.settings.defaultZoomLevel); } if (this.map.getZoom() > minZoom) { - this.map.setZoom(minZoom, { animate: false }); + this.map.setZoom(minZoom, {animate: false}); } }); if (this.settings.useDefaultCenterPosition) { bounds = bounds.extend(this.defaultCenterPosition); } - this.map.fitBounds(bounds, { padding: [50, 50], animate: false }); + this.map.fitBounds(bounds, {padding: [50, 50], animate: false}); this.map.invalidateSize(); } } @@ -125,12 +125,15 @@ export class TbGeoMap extends TbMap { ); } - public locationDataToLatLng(position: {x: number; y: number}): L.LatLng { + public locationDataToLatLng(position: { x: number; y: number }): L.LatLng { return L.latLng(position.x, position.y) as L.LatLng; } - public latLngToLocationData(position: L.LatLng): {x: number; y: number} { - position = position ? latLngPointToBounds(position, this.southWest, this.northEast, 0) : {lat: null, lng: null} as L.LatLng; + public latLngToLocationData(position: L.LatLng): { x: number; y: number } { + position = position ? latLngPointToBounds(position, this.southWest, this.northEast, 0) : { + lat: null, + lng: null + } as L.LatLng; return { x: position.lat, y: position.lng @@ -187,11 +190,9 @@ export class TbGeoMap extends TbMap { return (expression).map((el: TbPolylineRawCoordinate) => { if (!Array.isArray(el[0]) && !Array.isArray(el[1]) && el.length === 2) { return el; - } - // else if (Array.isArray(el) && el.length) { - // return this.polylineDataToCoordinates(el as TbPolylineRawCoordinates) as TbPolylineRawCoordinates; - // } - else { + } else if (Array.isArray(el) && el.length) { + return this.polylineDataToCoordinates(el as TbPolylineRawCoordinates) as TbPolylineRawCoordinate; + } else { return null; } }).filter(el => !!el); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/image-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/image-map.ts index df5f9e34f6..753528f006 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/image-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/image-map.ts @@ -352,8 +352,7 @@ export class TbImageMap extends TbMap { el[1] * this.height ); return [latLng.lat, latLng.lng] as TbPolylineRawCoordinate; - } - else if (Array.isArray(el) && el.length) { + } else if (Array.isArray(el) && el.length) { return this.polylineDataToCoordinates(el as TbPolylineRawCoordinates) as TbPolylineRawCoordinate; } else { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts index 24f88308ff..9f3c359a6e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts @@ -27,7 +27,9 @@ import { TbCircleData, TbMapDatasource, TbPolygonCoordinates, - TbPolygonRawCoordinates, TbPolylineCoordinates, TbPolylineRawCoordinates + TbPolygonRawCoordinates, + TbPolylineCoordinates, + TbPolylineRawCoordinates } from '@shared/models/widget/maps/map.models'; import { WidgetContext } from '@home/models/widget-component.models'; import { @@ -80,12 +82,12 @@ import { TbTripsDataLayer } from '@home/components/widget/lib/maps/data-layer/tr import { CompiledTbFunction } from '@shared/models/js-function.models'; import { TbMapDataLayer } from '@home/components/widget/lib/maps/data-layer/map-data-layer'; import { EntityType } from '@shared/models/entity-type.models'; -import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance; -import TooltipPositioningSide = JQueryTooltipster.TooltipPositioningSide; import { ShapePatternStorage } from '@home/components/widget/lib/maps/data-layer/shapes-data-layer'; import { TbPolylineDataLayer } from '@home/components/widget/lib/maps/data-layer/polylines-data-layer'; +import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance; +import TooltipPositioningSide = JQueryTooltipster.TooltipPositioningSide; -type TooltipInstancesData = {root: HTMLElement, instances: ITooltipsterInstance[]}; +type TooltipInstancesData = { root: HTMLElement, instances: ITooltipsterInstance[] }; export abstract class TbMap { @@ -327,7 +329,7 @@ export abstract class TbMap { let datasources: TbMapDatasource[]; for (const layerType of mapDataLayerTypes) { const typeDatasources = this.latestDataLayers.filter(dl => dl.dataLayerType() === layerType) - .map(dl => dl.getDataSources()).flat(); + .map(dl => dl.getDataSources()).flat(); if (!datasources) { datasources = typeDatasources; } else { @@ -427,100 +429,100 @@ export abstract class TbMap { private setupEditMode() { - this.editToolbar = L.TB.bottomToolbar({ - mapElement: $(this.mapElement), - closeTitle: this.ctx.translate.instant('action.cancel'), - onClose: () => { - return this.deselectItem(true); - } - }); - - this.map.on('click', () => { - this.deselectItem(); - }); - - if (this.latestDataLayers.some(dl => dl.isEditable())) { - this.map.pm.setGlobalOptions({ snappable: false }); - this.map.pm.applyGlobalOptions(); - } - - const dragSupportedDataLayers = this.latestDataLayers.filter(dl => dl.isDragEnabled()); - const showDragModeButton = this.settings.dragModeButton && dragSupportedDataLayers.length; - const addSupportedDataLayers = this.latestDataLayers.filter(dl => dl.isAddEnabled()); - - if (showDragModeButton || addSupportedDataLayers.length) { - const drawToolbar = L.TB.toolbar({ - position: this.settings.controlsPosition - }).addTo(this.map); - if (showDragModeButton) { - this.dragModeButton = drawToolbar.toolbarButton({ - id: 'dragMode', - title: this.ctx.translate.instant('widgets.maps.data-layer.drag-drop-mode'), - iconClass: 'tb-drag-mode', - click: (e, button) => { - this.toggleDragMode(e, button); - } - }); - } - this.addMarkerDataLayers = addSupportedDataLayers.filter(dl => dl.dataLayerType() === 'markers'); - if (this.addMarkerDataLayers.length) { - this.addMarkerButton = drawToolbar.toolbarButton({ - id: 'addMarker', - title: this.ctx.translate.instant('widgets.maps.data-layer.marker.place-marker'), - iconClass: 'tb-place-marker', - click: (e, button) => { - this.placeMarker(e, button); - } - }); - this.addMarkerButton.setDisabled(true); - this.setPlaceMarkerStyle(); - } - this.addPolygonDataLayers = addSupportedDataLayers.filter(dl => dl.dataLayerType() === 'polygons'); - if (this.addPolygonDataLayers.length) { - this.addRectangleButton = drawToolbar.toolbarButton({ - id: 'addRectangle', - title: this.ctx.translate.instant('widgets.maps.data-layer.polygon.draw-rectangle'), - iconClass: 'tb-draw-rectangle', - click: (e, button) => { - this.drawRectangle(e, button); - } - }); - this.addRectangleButton.setDisabled(true); - this.addPolygonButton = drawToolbar.toolbarButton({ - id: 'addPolygon', - title: this.ctx.translate.instant('widgets.maps.data-layer.polygon.draw-polygon'), - iconClass: 'tb-draw-polygon', - click: (e, button) => { - this.drawPolygon(e, button); - } - }); - this.addPolygonButton.setDisabled(true); - } - this.addCircleDataLayers = addSupportedDataLayers.filter(dl => dl.dataLayerType() === 'circles'); - if (this.addCircleDataLayers.length) { - this.addCircleButton = drawToolbar.toolbarButton({ - id: 'addCircle', - title: this.ctx.translate.instant('widgets.maps.data-layer.circle.draw-circle'), - iconClass: 'tb-draw-circle', - click: (e, button) => { - this.drawCircle(e, button); - } - }); - this.addCircleButton.setDisabled(true); - } - this.addPolylineDataLayers = addSupportedDataLayers.filter(dl => dl.dataLayerType() === 'polylines'); - if (this.addPolylineDataLayers.length) { - this.addPolylineButton = drawToolbar.toolbarButton({ - id: 'addPolyline', - title: this.ctx.translate.instant('widgets.maps.data-layer.polyline.draw-polyline'), - iconClass: 'tb-draw-polyline', - click: (e, button) => { - this.drawPolyline(e, button); - } - }); - this.addPolylineButton.setDisabled(true); - } - } + this.editToolbar = L.TB.bottomToolbar({ + mapElement: $(this.mapElement), + closeTitle: this.ctx.translate.instant('action.cancel'), + onClose: () => { + return this.deselectItem(true); + } + }); + + this.map.on('click', () => { + this.deselectItem(); + }); + + if (this.latestDataLayers.some(dl => dl.isEditable())) { + this.map.pm.setGlobalOptions({snappable: false}); + this.map.pm.applyGlobalOptions(); + } + + const dragSupportedDataLayers = this.latestDataLayers.filter(dl => dl.isDragEnabled()); + const showDragModeButton = this.settings.dragModeButton && dragSupportedDataLayers.length; + const addSupportedDataLayers = this.latestDataLayers.filter(dl => dl.isAddEnabled()); + + if (showDragModeButton || addSupportedDataLayers.length) { + const drawToolbar = L.TB.toolbar({ + position: this.settings.controlsPosition + }).addTo(this.map); + if (showDragModeButton) { + this.dragModeButton = drawToolbar.toolbarButton({ + id: 'dragMode', + title: this.ctx.translate.instant('widgets.maps.data-layer.drag-drop-mode'), + iconClass: 'tb-drag-mode', + click: (e, button) => { + this.toggleDragMode(e, button); + } + }); + } + this.addMarkerDataLayers = addSupportedDataLayers.filter(dl => dl.dataLayerType() === 'markers'); + if (this.addMarkerDataLayers.length) { + this.addMarkerButton = drawToolbar.toolbarButton({ + id: 'addMarker', + title: this.ctx.translate.instant('widgets.maps.data-layer.marker.place-marker'), + iconClass: 'tb-place-marker', + click: (e, button) => { + this.placeMarker(e, button); + } + }); + this.addMarkerButton.setDisabled(true); + this.setPlaceMarkerStyle(); + } + this.addPolygonDataLayers = addSupportedDataLayers.filter(dl => dl.dataLayerType() === 'polygons'); + if (this.addPolygonDataLayers.length) { + this.addRectangleButton = drawToolbar.toolbarButton({ + id: 'addRectangle', + title: this.ctx.translate.instant('widgets.maps.data-layer.polygon.draw-rectangle'), + iconClass: 'tb-draw-rectangle', + click: (e, button) => { + this.drawRectangle(e, button); + } + }); + this.addRectangleButton.setDisabled(true); + this.addPolygonButton = drawToolbar.toolbarButton({ + id: 'addPolygon', + title: this.ctx.translate.instant('widgets.maps.data-layer.polygon.draw-polygon'), + iconClass: 'tb-draw-polygon', + click: (e, button) => { + this.drawPolygon(e, button); + } + }); + this.addPolygonButton.setDisabled(true); + } + this.addCircleDataLayers = addSupportedDataLayers.filter(dl => dl.dataLayerType() === 'circles'); + if (this.addCircleDataLayers.length) { + this.addCircleButton = drawToolbar.toolbarButton({ + id: 'addCircle', + title: this.ctx.translate.instant('widgets.maps.data-layer.circle.draw-circle'), + iconClass: 'tb-draw-circle', + click: (e, button) => { + this.drawCircle(e, button); + } + }); + this.addCircleButton.setDisabled(true); + } + this.addPolylineDataLayers = addSupportedDataLayers.filter(dl => dl.dataLayerType() === 'polylines'); + if (this.addPolylineDataLayers.length) { + this.addPolylineButton = drawToolbar.toolbarButton({ + id: 'addPolyline', + title: this.ctx.translate.instant('widgets.maps.data-layer.polyline.draw-polyline'), + iconClass: 'tb-draw-polyline', + click: (e, button) => { + this.drawPolyline(e, button); + } + }); + this.addPolylineButton.setDisabled(true); + } + } } private toggleDragMode(_e: MouseEvent, button: L.TB.ToolbarButton): void { @@ -578,9 +580,9 @@ export abstract class TbMap { } private drawPolyline(e: MouseEvent, button: L.TB.ToolbarButton): void { - this.placeItem(e, button, this.addCircleDataLayers, (entity) => this.prepareDrawMode('Polyline', { - firstVertex: this.ctx.translate.instant('widgets.maps.data-layer.polyline.polyline-place-first-point-hint-with-entity', {entityName: entity.entity.entityDisplayName}), - continueLine: this.ctx.translate.instant('widgets.maps.data-layer.polyline.continue-polyline-hint-with-entity', {entityName: entity.entity.entityDisplayName}), + this.placeItem(e, button, this.addPolylineDataLayers, (entity) => this.prepareDrawMode('Line', { + startPolyline: this.ctx.translate.instant('widgets.maps.data-layer.polyline.polyline-place-first-point-hint-with-entity', {entityName: entity.entity.entityDisplayName}), + finishPolyline: this.ctx.translate.instant('widgets.maps.data-layer.polyline.finish-polyline-hint', {entityName: entity.entity.entityDisplayName}), })); } @@ -706,7 +708,7 @@ export abstract class TbMap { this.createCircle(actionData); break; case MapItemType.polyline: - // this.createPolyline(actionData); // TODO + this.createPolyline(actionData); break; } } @@ -755,6 +757,17 @@ export abstract class TbMap { })); } + private createPolyline(actionData: PlaceMapItemActionData): void { + this.createItem(actionData, () => this.prepareDrawMode('Line', { + startPolyline: actionData.action.mapItemTooltips.startPolyline + ? this.ctx.utilsService.customTranslation(actionData.action.mapItemTooltips.startPolyline) + : this.ctx.translate.instant(mapItemTooltipsTranslation.startPolyline), + finishPolyline: actionData.action.mapItemTooltips.finishPolyline + ? this.ctx.utilsService.customTranslation(actionData.action.mapItemTooltips.finishPolyline) + : this.ctx.translate.instant(mapItemTooltipsTranslation.finishPolyline), + })); + } + private createItem(actionData: PlaceMapItemActionData, prepareDrawMode: () => void) { const actionId = 'id' in actionData.action ? actionData.action.id : 'map-button'; if (this.createMapItemActionId === actionId) { @@ -795,7 +808,7 @@ export abstract class TbMap { this.createMapItemActionId = actionId; - const convertLayerToCoordinates = (type: MapItemType, layer: L.Layer): {x: number; y: number} | TbPolygonRawCoordinates | TbCircleData => { + const convertLayerToCoordinates = (type: MapItemType, layer: L.Layer): { x: number; y: number } | TbPolygonRawCoordinates | TbCircleData => { switch (type) { case MapItemType.marker: if (layer instanceof L.Marker) { @@ -852,8 +865,8 @@ export abstract class TbMap { this.editToolbar.close(); } - private prepareDrawMode(shape: 'Marker' | 'Rectangle' | 'Polygon' | 'Circle' | 'Polyline', tooltipsTranslation: Record) { - this.map.pm.setLang('en', { tooltips: tooltipsTranslation }, 'en'); + private prepareDrawMode(shape: 'Marker' | 'Rectangle' | 'Polygon' | 'Circle' | 'Line', tooltipsTranslation: Record) { + this.map.pm.setLang('en', {tooltips: tooltipsTranslation}, 'en'); this.map.pm.enableDraw(shape); // @ts-ignore L.DomUtil.addClass(this.map.pm.Draw[shape]._hintMarker.getTooltip()._container, 'tb-place-item-label'); @@ -887,46 +900,46 @@ export abstract class TbMap { tooltipData.instances = []; } $(root) - .find('a[role="button"]:not(.leaflet-pm-action)') - .each((_index, element) => { - let title: string; - if (element.title) { - title = element.title; - $(element).removeAttr('title'); - } else if (element.parentElement.title) { - title = element.parentElement.title; - $(element).parent().removeAttr('title'); - } - const tooltip = $(element).tooltipster( - { - content: title, - theme: 'tooltipster-shadow', - delay: 10, - triggerClose: { - click: true, - tap: true, - scroll: true, - mouseleave: true - }, - side, - distance: 2, - trackOrigin: true, - functionBefore: (_instance, helper) => { - if (helper.origin.ariaDisabled === 'true' || helper.origin.parentElement.classList.contains('active')) { - return false; - } - }, - } - ); - const instance = tooltip.tooltipster('instance'); - tooltipData.instances.push(instance); - instance.on('destroyed', () => { - const index = tooltipData.instances.indexOf(instance); - if (index > -1) { - tooltipData.instances.splice(index, 1); + .find('a[role="button"]:not(.leaflet-pm-action)') + .each((_index, element) => { + let title: string; + if (element.title) { + title = element.title; + $(element).removeAttr('title'); + } else if (element.parentElement.title) { + title = element.parentElement.title; + $(element).parent().removeAttr('title'); } + const tooltip = $(element).tooltipster( + { + content: title, + theme: 'tooltipster-shadow', + delay: 10, + triggerClose: { + click: true, + tap: true, + scroll: true, + mouseleave: true + }, + side, + distance: 2, + trackOrigin: true, + functionBefore: (_instance, helper) => { + if (helper.origin.ariaDisabled === 'true' || helper.origin.parentElement.classList.contains('active')) { + return false; + } + }, + } + ); + const instance = tooltip.tooltipster('instance'); + tooltipData.instances.push(instance); + instance.on('destroyed', () => { + const index = tooltipData.instances.indexOf(instance); + if (index > -1) { + tooltipData.instances.splice(index, 1); + } + }); }); - }); }); } @@ -938,6 +951,7 @@ export abstract class TbMap { this.updateTripsAnchors(); this.updateBounds(); this.updateEditButtonsStates(); + } private updateTrips(subscription: IWidgetSubscription) { @@ -982,6 +996,7 @@ export abstract class TbMap { private updateTripsAppearance() { this.tripDataLayers.forEach(dl => dl.updateAppearance()); } + private updateTripsTime() { this.tripDataLayers.forEach(dl => dl.updateCurrentTime()); } @@ -1034,8 +1049,7 @@ export abstract class TbMap { (!this.bounds || !this.bounds.isValid() || (!this.bounds.equals(bounds) || force) && this.settings.fitMapBounds) && !mapBounds.contains(bounds) ) - ) - { + ) { this.bounds = bounds; if (!this.ignoreUpdateBounds && !this.isPlacingItem) { this.fitBounds(bounds); @@ -1062,6 +1076,9 @@ export abstract class TbMap { if (this.addCircleButton && this.addCircleButton !== this.currentEditButton) { this.addCircleButton.setDisabled(true); } + if (this.addPolylineButton && this.addPolylineButton !== this.currentEditButton) { + this.addPolylineButton.setDisabled(true); + } this.customActionsToolbar?.setDisabled(true); } else { if (this.dragModeButton) { @@ -1079,7 +1096,9 @@ export abstract class TbMap { if (this.addCircleButton) { this.addCircleButton.setDisabled(!this.addCircleDataLayers.some(dl => dl.isEnabled() && dl.hasUnplacedItems())); } - // TODO + if (this.addPolylineButton) { + this.addPolylineButton.setDisabled(!this.addPolylineDataLayers.some(dl => dl.isEnabled() && dl.hasUnplacedItems())); + } this.customActionsToolbar?.setDisabled(false); } } @@ -1190,7 +1209,7 @@ export abstract class TbMap { $event.preventDefault(); $event.stopPropagation(); } - const { entityId, entityName, entityLabel, entityType } = data.$datasource; + const {entityId, entityName, entityLabel, entityType} = data.$datasource; this.ctx.actionsApi.handleWidgetAction($event, action, { entityType, id: entityId @@ -1259,7 +1278,7 @@ export abstract class TbMap { } } - public saveLocation(data: FormattedData, values: {[key: string]: any}): Observable { + public saveLocation(data: FormattedData, values: { [key: string]: any }): Observable { const datasource = data.$datasource; let dataKeys = datasource.dataKeys; if (datasource.latestDataKeys) { @@ -1369,9 +1388,9 @@ export abstract class TbMap { } } - public abstract locationDataToLatLng(position: {x: number; y: number}): L.LatLng; + public abstract locationDataToLatLng(position: { x: number; y: number }): L.LatLng; - public abstract latLngToLocationData(position: L.LatLng): {x: number; y: number}; + public abstract latLngToLocationData(position: L.LatLng): { x: number; y: number }; public abstract polygonDataToCoordinates(coordinates: TbPolygonRawCoordinates): TbPolygonRawCoordinates; @@ -1386,5 +1405,4 @@ export abstract class TbMap { public abstract coordinatesToCircleData(center: L.LatLng, radius: number): TbCircleData; - } diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index c27639e45c..9addfc7b18 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -827,6 +827,8 @@ export interface MapItemTooltips { finishRect?: string; startCircle?: string; finishCircle?: string; + startPolyline?: string; + finishPolyline?: string; } export const mapItemTooltipsTranslation: Required = Object.freeze({ @@ -837,7 +839,9 @@ export const mapItemTooltipsTranslation: Required = Object.free startRect: 'widgets.maps.data-layer.polygon.rectangle-place-first-point-hint', finishRect: 'widgets.maps.data-layer.polygon.finish-rectangle-hint', startCircle: 'widgets.maps.data-layer.circle.place-circle-center-hint', - finishCircle: 'widgets.maps.data-layer.circle.finish-circle-hint' + finishCircle: 'widgets.maps.data-layer.circle.finish-circle-hint', + startPolyline: 'widgets.maps.data-layer.polyline.polyline-place-first-point-hint', + finishPolyline: 'widgets.maps.data-layer.polyline.finish-polyline-hint' }) export interface WidgetActionDescriptor extends WidgetAction { diff --git a/ui-ngx/src/app/shared/models/widget/maps/map.models.ts b/ui-ngx/src/app/shared/models/widget/maps/map.models.ts index 99a3bc499b..00a84b014e 100644 --- a/ui-ngx/src/app/shared/models/widget/maps/map.models.ts +++ b/ui-ngx/src/app/shared/models/widget/maps/map.models.ts @@ -79,7 +79,7 @@ export const mapDataSourceSettingsToDatasource = (settings: MapDataSourceSetting }; const mapDataLayerDatasourceDataKeys = (settings: MapDataLayerSettings, - dataLayerType: MapDataLayerType): DataKey[] => { + dataLayerType: MapDataLayerType): DataKey[] => { const dataKeys = settings.additionalDataKeys?.length ? deepClone(settings.additionalDataKeys) : []; switch (dataLayerType) { case 'trips': @@ -169,7 +169,7 @@ export interface MapDataLayerSettings extends MapDataSourceSettings { tooltip: DataLayerTooltipSettings; click: WidgetAction; groups?: string[]; - edit: DataLayerEditSettings; + edit: DataLayerEditSettings; } export const defaultBaseDataLayerSettings = (mapType: MapType): Partial => ({ @@ -185,7 +185,7 @@ export const defaultBaseDataLayerSettings = (mapType: MapType): Partial${entityName}

Latitude: ${latitude:7}
Longitude: ${longitude:7}
Temperature: ${temperature} °C
See tooltip settings for details' - : '${entityName}

X Pos: ${xPos:2}
Y Pos: ${yPos:2}
Temperature: ${temperature} °C
See tooltip settings for details', + : '${entityName}

X Pos: ${xPos:2}
Y Pos: ${yPos:2}
Temperature: ${temperature} °C
See tooltip settings for details', offsetX: 0, offsetY: -1 }, @@ -225,7 +225,7 @@ export const mapDataLayerValid = (dataLayer: MapDataLayerSettings, type: MapData case 'markers': const markersDataLayer = dataLayer as MarkersDataLayerSettings; if (!markersDataLayer.xKey?.type || !markersDataLayer.xKey?.name || - !markersDataLayer.yKey?.type || !markersDataLayer.xKey?.name) { + !markersDataLayer.yKey?.type || !markersDataLayer.xKey?.name) { return false; } break; @@ -309,6 +309,7 @@ export interface MarkerIconSettings extends BaseMarkerShapeSettings { iconContainer?: MarkerIconContainer; icon: string; } + export interface MarkerClusteringSettings { enable: boolean; zoomOnClick: boolean; @@ -612,8 +613,11 @@ export const defaultBasePolygonsDataLayerSettings = (mapType: MapType): Partial< color: '#3388ff', }, strokeWeight: 3 -} as Partial, defaultBaseDataLayerSettings(mapType), - {label: {show: false}, tooltip: {show: false, pattern: '${entityName}

TimeStamp: ${ts:7}'}} as Partial) + } as Partial, defaultBaseDataLayerSettings(mapType), + { + label: {show: false}, + tooltip: {show: false, pattern: '${entityName}

TimeStamp: ${ts:7}'} + } as Partial) export interface CirclesDataLayerSettings extends ShapeDataLayerSettings { circleKey: DataKey; @@ -663,8 +667,11 @@ export const defaultBaseCirclesDataLayerSettings = (mapType: MapType): Partial, defaultBaseDataLayerSettings(mapType), - {label: {show: false}, tooltip: {show: false, pattern: '${entityName}

TimeStamp: ${ts:7}'}} as Partial) + } as Partial, defaultBaseDataLayerSettings(mapType), + { + label: {show: false}, + tooltip: {show: false, pattern: '${entityName}

TimeStamp: ${ts:7}'} + } as Partial) export interface PolylinesDataLayerSettings extends ShapeDataLayerSettings, PathDataLayerSettings { polylineKey: DataKey; @@ -683,6 +690,11 @@ export const defaultPolylinesDataLayerSettings = (mapType: MapType, functionsOnl } as PolylinesDataLayerSettings, defaultBasePolylinesDataLayerSettings(mapType) as PolylinesDataLayerSettings); export const defaultBasePolylinesDataLayerSettings = (mapType: MapType): Partial => mergeDeep({ + fillType: ShapeFillType.color, + fillColor: { + type: DataLayerColorType.constant, + color: 'rgba(51,136,255,0.2)', + }, strokeColor: { type: DataLayerColorType.constant, color: '#3388ff', @@ -713,7 +725,10 @@ export const defaultBasePolylinesDataLayerSettings = (mapType: MapType): Partial offsetY: -1 }, } as Partial, defaultBaseDataLayerSettings(mapType), - {label: {show: false}, tooltip: {show: false, pattern: '${entityName}

TimeStamp: ${ts:7}'}} as Partial) + { + label: {show: false}, + tooltip: {show: false, pattern: '${entityName}

TimeStamp: ${ts:7}'} + } as Partial) export const defaultMapDataLayerSettings = (mapType: MapType, dataLayerType: MapDataLayerType, functionsOnly = false): MapDataLayerSettings => { @@ -793,13 +808,13 @@ export const additionalMapDataSourceValid = (dataSource: AdditionalMapDataSource }; export const additionalMapDataSourceValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => { - const dataSource: AdditionalMapDataSourceSettings = control.value; - if (!additionalMapDataSourceValid(dataSource)) { - return { - dataSource: true - }; - } - return null; + const dataSource: AdditionalMapDataSourceSettings = control.value; + if (!additionalMapDataSourceValid(dataSource)) { + return { + dataSource: true + }; + } + return null; }; export const defaultAdditionalMapDataSourceSettings = (functionsOnly = false): AdditionalMapDataSourceSettings => { @@ -922,7 +937,7 @@ export const defaultBaseMapSettings: BaseMapSettings = { tripTimeline: { showTimelineControl: false, timeStep: 1000, - speedOptions: [1,5,10,15,25], + speedOptions: [1, 5, 10, 15, 25], showTimestamp: true, timestampFormat: simpleDateFormat('yyyy-MM-dd HH:mm:ss'), snapToRealLocation: false, @@ -1294,7 +1309,7 @@ export type MapStringFunction = (data: FormattedData, dsData: FormattedData[]) => string; export type MapBooleanFunction = (data: FormattedData, - dsData: FormattedData[]) => boolean; + dsData: FormattedData[]) => boolean; export type MarkerImageFunction = (data: FormattedData, markerImages: string[], dsData: FormattedData[]) => MarkerImageInfo; @@ -1316,7 +1331,7 @@ export type TbPolygonCoordinates = TbPolygonCoordinate[]; export type TbPolylineRawCoordinate = L.LatLngTuple | L.LatLngTuple[] | L.LatLngTuple[][]; export type TbPolylineRawCoordinates = TbPolylineRawCoordinate[]; -export type TbPolylineData = L.LatLngTuple[] | L.LatLngTuple[][]; +export type TbPolylineData = L.LatLngTuple[] | L.LatLngTuple[][] | L.LatLngTuple[][][]; export type TbPolylineCoordinate = L.LatLng | L.LatLng[] | L.LatLng[][]; export type TbPolylineCoordinates = TbPolylineCoordinate[]; @@ -1354,7 +1369,7 @@ export const isValidLatLng = (latitude: any, longitude: any): boolean => isValidLatitude(latitude) && isValidLongitude(longitude); export const isCutPolygon = (data: TbPolygonCoordinates | TbPolygonRawCoordinates): boolean => { - return data.length > 1 && Array.isArray(data[0]) && (Array.isArray(data[0][0]) || (isNumber((data[0][0] as any).lat) && isNumber((data[0][0] as any).lng)) ); + return data.length > 1 && Array.isArray(data[0]) && (Array.isArray(data[0][0]) || (isNumber((data[0][0] as any).lat) && isNumber((data[0][0] as any).lng))); } export const parseCenterPosition = (position: string | [number, number]): [number, number] => { @@ -1438,7 +1453,7 @@ const mergeMapDatasource = (target: TbMapDatasource, source: TbMapDatasource): T return target; } -const imageAspectMap: {[key: string]: ImageWithAspect} = {}; +const imageAspectMap: { [key: string]: ImageWithAspect } = {}; const imageLoader = (imageUrl: string): Observable => new Observable((observer: Observer) => { const image = document.createElement('img'); // support IE @@ -1485,7 +1500,7 @@ export const loadImageWithAspect = (imagePipe: ImagePipe, imageUrl: string): Obs url, width: size[0], height: size[1], - aspect: size[0]/size[1] + aspect: size[0] / size[1] }; imageAspectMap[hash] = imageWithAspect; return imageWithAspect; @@ -1563,7 +1578,7 @@ export const latLngPointToBounds = (point: L.LatLng, southWest: L.LatLng, northE return point; } -export type TripRouteData = {[time: number]: FormattedData}; +export type TripRouteData = { [time: number]: FormattedData }; export const calculateInterpolationRatio = (firsMoment: number, secondMoment: number, intermediateMoment: number): number => { return (intermediateMoment - firsMoment) / (secondMoment - firsMoment); diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 0e13fda6be..4290f39c24 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -8638,11 +8638,12 @@ "polyline-configuration": "Polyline configuration", "remove-polyline": "Remove polyline", "edit": "Edit polyline", + "rotate": "Rotate polyline", "remove-polyline-for": "Remove polyline for '{{entityName}}'", "draw-polyline": "Draw polyline", + "polyline-place-first-point-hint-with-entity": "Polyline for '{{entityName}}': click to place first point", "polyline-place-first-point-hint": "Polyline: click to place first point", - "continue-polyline-hint-with-entity": "Polyline for '{{entityName}}': click to continue drawing", - "finish-polyline-hint": "Polyline: click to finish drawing" + "finish-polyline-hint": "Polyline for '{{entityName}}': click to finish drawing" }, "select-entity": "Select entity", "select-entity-hint": "Hint: after selection click at the map to set position" From f332096288de06b4ccfd463b0190ee8c3d0becf6 Mon Sep 17 00:00:00 2001 From: LeoMorgan113 Date: Tue, 14 Oct 2025 16:06:29 +0300 Subject: [PATCH 16/38] Added cut action for polyline --- .../maps/data-layer/polylines-data-layer.ts | 96 ++++++++++++++++++- .../lib/maps/data-layer/trips-data-layer.ts | 2 +- .../map/map-data-layer-dialog.component.html | 2 - .../map/map-data-layer-dialog.component.ts | 45 --------- .../shared/models/widget/maps/map.models.ts | 45 ++------- .../assets/locale/locale.constant-en_US.json | 4 +- 6 files changed, 107 insertions(+), 87 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polylines-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polylines-data-layer.ts index e2b5f2b1c9..06cb32d71f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polylines-data-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polylines-data-layer.ts @@ -157,12 +157,29 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem { + const map = this.dataLayer.getMap().getMap(); + if (!map.pm.globalCutModeEnabled()) { + this.disablePolylineRotateMode(); + this.disablePolylineEditMode(); + this.enablePolylineCutMode(button); + } else { + this.disablePolylineCutMode(button); + this.enablePolylineEditMode(); + } + } + }, { id: 'rotate', title: this.getDataLayer().getCtx().translate.instant('widgets.maps.data-layer.polyline.rotate'), iconClass: 'tb-rotate', click: (e, button) => { if (!this.polyline.pm.rotateEnabled()) { + this.disablePolylineCutMode(); this.disablePolylineEditMode(); this.enablePolylineRotateMode(button); } else { @@ -184,7 +201,13 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem { + if (e.layer instanceof L.Polyline) { + this.polylineContainer.removeLayer(this.polyline); + this.polyline = L.polyline(e.layer.getLatLngs() as L.LatLngExpression[] | L.LatLngExpression[][], { + ...this.polylineStyleInfo.style, + snapIgnore: !this.dataLayer.isSnappable(), + bubblingMouseEvents: !this.dataLayer.isEditMode() + }); + this.polyline.addTo(this.polylineContainer); + } + // @ts-ignore + e.layer._pmTempLayer = true; + e.layer.remove(); + this.polylineContainer.removeLayer(this.polyline); + // @ts-ignore + this.polyline._pmTempLayer = false; + this.polyline.addTo(this.polylineContainer); + this.updateSelectedState(); + cutButton?.setActive(false); + this.savePolylineCoordinates() + }); + const map = this.dataLayer.getMap().getMap(); + map.pm.setLang('en', { + tooltips: { + firstVertex: this.getDataLayer().getCtx().translate.instant('widgets.maps.data-layer.polyline.polyline-place-first-point-cut-hint'), + finishLine: this.getDataLayer().getCtx().translate.instant('widgets.maps.data-layer.polyline.finish-polyline-cut-hint') + } + }, 'en'); + map.pm.enableGlobalCutMode({ + // @ts-ignore + layersToCut: [this.polyline] + }); + // @ts-ignore + L.DomUtil.addClass(map.pm.Draw.Cut._hintMarker.getTooltip()._container, 'tb-place-item-label'); + cutButton?.setActive(true); + map.once('pm:globalcutmodetoggled', (e) => { + if (!e.enabled) { + this.disablePolylineCutMode(cutButton); + this.enablePolylineEditMode(); + } + }); + } + + private disablePolylineCutMode(cutButton?: L.TB.ToolbarButton) { + this.editing = false; + this.polyline.options.bubblingMouseEvents = !this.dataLayer.isEditMode(); + this.polyline.setStyle({...this.polylineStyleInfo.style, dashArray: null}); + this.removeItemClass('tb-cut-mode'); + this.polyline.off('pm:cut'); + const map = this.dataLayer.getMap().getMap(); + map.pm.disableGlobalCutMode(); + cutButton?.setActive(false); + } + private enablePolylineRotateMode(rotateButton?: L.TB.ToolbarButton) { this.polylineContainer.closePopup(); this.editing = true; @@ -250,6 +336,13 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem boundsArray.find(boundPoint => boundPoint.equals(point as L.LatLng)) !== undefined)) { + coordinates = [bounds.getNorthWest(), bounds.getSouthEast()]; + } + } this.dataLayer.savePolylineCoordinates(this.data, coordinates).subscribe(); } @@ -279,7 +372,6 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/trips-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/trips-data-layer.ts index ae2f62853e..bfb872c75a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/trips-data-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/trips-data-layer.ts @@ -44,7 +44,7 @@ import { MarkerDataProcessor } from '@home/components/widget/lib/maps/data-layer type TripRouteData = {[time: number]: FormattedData}; -interface PointItem { +export interface PointItem { point: L.CircleMarker; tooltip?: L.Popup; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.html index df53c44be7..178c948f43 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.html @@ -390,8 +390,6 @@
- -
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.ts index 71b6a2b31a..af653308da 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-data-layer-dialog.component.ts @@ -306,24 +306,6 @@ export class MapDataLayerDialogComponent extends DialogComponent - this.updateValidators() - ); break; } this.dataLayerFormGroup.get('dsType').valueChanges.pipe( @@ -473,33 +455,6 @@ export class MapDataLayerDialogComponent extends DialogComponent mergeDeep( defaultMarkersDataSourceSettings(mapType, true, functionsOnly) as TripsDataLayerSettings, defaultBaseTripsDataLayerSettings(mapType) as TripsDataLayerSettings); @@ -673,7 +670,7 @@ export const defaultBaseCirclesDataLayerSettings = (mapType: MapType): Partial${entityName}

TimeStamp: ${ts:7}'} } as Partial) -export interface PolylinesDataLayerSettings extends ShapeDataLayerSettings, PathDataLayerSettings { +export interface PolylinesDataLayerSettings extends ShapeDataLayerSettings { polylineKey: DataKey; } @@ -699,31 +696,7 @@ export const defaultBasePolylinesDataLayerSettings = (mapType: MapType): Partial type: DataLayerColorType.constant, color: '#3388ff', }, - strokeWeight: 3, - // usePathDecorator: false, - // pathDecoratorSymbol: PathDecoratorSymbol.arrowHead, - // pathDecoratorSymbolSize: 10, - // pathDecoratorSymbolColor: '#307FE5', - // pathDecoratorOffset: 20, - // pathEndDecoratorOffset: 20, - // pathDecoratorRepeat: 20, - showPoints: false, - pointSize: 10, - pointColor: { - type: DataLayerColorType.constant, - color: '#307FE5', - }, - pointTooltip: { - show: false, - trigger: DataLayerTooltipTrigger.click, - autoclose: true, - type: DataLayerPatternType.pattern, - pattern: mapType === MapType.geoMap ? - '${entityName}

Latitude: ${latitude:7}
Longitude: ${longitude:7}
End Time: ${maxTime}
Start Time: ${minTime}' - : '${entityName}

X Pos: ${xPos:2}
Y Pos: ${yPos:2}
End Time: ${maxTime}
Start Time: ${minTime}', - offsetX: 0, - offsetY: -1 - }, + strokeWeight: 3 } as Partial, defaultBaseDataLayerSettings(mapType), { label: {show: false}, diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 4290f39c24..47fdda19a3 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -8643,7 +8643,9 @@ "draw-polyline": "Draw polyline", "polyline-place-first-point-hint-with-entity": "Polyline for '{{entityName}}': click to place first point", "polyline-place-first-point-hint": "Polyline: click to place first point", - "finish-polyline-hint": "Polyline for '{{entityName}}': click to finish drawing" + "finish-polyline-hint": "Polyline for '{{entityName}}': click to finish drawing", + "polyline-place-first-point-cut-hint": "Click to place first point", + "finish-polyline-cut-hint": "Click first marker to finish and save" }, "select-entity": "Select entity", "select-entity-hint": "Hint: after selection click at the map to set position" From b518c125aa26540e590222ddd28fe7e3ee4162c9 Mon Sep 17 00:00:00 2001 From: LeoMorgan113 Date: Tue, 14 Oct 2025 18:10:00 +0300 Subject: [PATCH 17/38] Fix for cut button --- .../maps/data-layer/polygons-data-layer.ts | 3 ++ .../maps/data-layer/polylines-data-layer.ts | 49 +++++++++++++++++-- .../assets/locale/locale.constant-en_US.json | 1 + 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polygons-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polygons-data-layer.ts index ec23850a9a..98b8546400 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polygons-data-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polygons-data-layer.ts @@ -32,6 +32,7 @@ import { UnplacedMapDataItem } from '@home/components/widget/lib/maps/data-layer/latest-map-data-layer'; import { map } from 'rxjs/operators'; +import { instanceOfSearchableComponent } from '@home/models/searchable-component.models'; class TbPolygonDataLayerItem extends TbLatestDataLayerItem { @@ -252,6 +253,8 @@ class TbPolygonDataLayerItem extends TbLatestDataLayerItem { + console.log("(e.layer in polygon", e.layer ) + console.log("this.polygon instanceof L.Rectangle", this.polygon instanceof L.Rectangle) if (e.layer instanceof L.Polygon) { if (this.polygon instanceof L.Rectangle) { this.polygonContainer.removeLayer(this.polygon); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polylines-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polylines-data-layer.ts index 06cb32d71f..0bc2fdaed5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polylines-data-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polylines-data-layer.ts @@ -36,6 +36,9 @@ import { UnplacedMapDataItem } from '@home/components/widget/lib/maps/data-layer/latest-map-data-layer'; import { map } from 'rxjs/operators'; +import { PointItem } from '@home/components/widget/lib/maps/data-layer/trips-data-layer'; +import _ from 'lodash'; +import { createTooltip, updateTooltip } from '@home/components/widget/lib/maps/data-layer/data-layer-utils'; class TbPolylineDataLayerItem extends TbLatestDataLayerItem { @@ -257,14 +260,54 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem { if (e.layer instanceof L.Polyline) { - this.polylineContainer.removeLayer(this.polyline); - this.polyline = L.polyline(e.layer.getLatLngs() as L.LatLngExpression[] | L.LatLngExpression[][], { + // fallback for single segment case + this.polyline = L.polyline(e.layer.getLatLngs() as L.LatLngExpression[] | L.LatLngExpression[][] , { ...this.polylineStyleInfo.style, snapIgnore: !this.dataLayer.isSnappable(), bubblingMouseEvents: !this.dataLayer.isEditMode() }); this.polyline.addTo(this.polylineContainer); + } else if (e.layer instanceof L.LayerGroup) { + if(e.layer.getLayers){ + console.log(e.layer, e.layer.getLayers, e.layer.getLayers()) + const cutSegments = e.layer.getLayers() as L.Polyline[]; + this.polylineContainer.removeLayer(this.polyline); + cutSegments.forEach(segment => { + console.log(segment instanceof L.Polyline) + segment.setStyle({ + ...this.polylineStyleInfo.style, + snapIgnore: !this.dataLayer.isSnappable(), + bubblingMouseEvents: !this.dataLayer.isEditMode() + }); + // segment.setStyle({ + // ...this.polylineStyleInfo.style, + // snapIgnore: !this.dataLayer.isSnappable(), + // bubblingMouseEvents: !this.dataLayer.isEditMode() + // }); + segment.addTo(this.polylineContainer); + }); + + } + + + } + // if (e.layer instanceof L.Polyline) { + // // if (this.polyline instanceof L.Polyline) { + // this.polylineContainer.removeLayer(this.polyline); + // this.polyline = L.polyline(e.layer.getLatLngs() as L.LatLngExpression[] | L.LatLngExpression[][] , { + // ...this.polylineStyleInfo.style, + // snapIgnore: !this.dataLayer.isSnappable(), + // bubblingMouseEvents: !this.dataLayer.isEditMode() + // }); + // this.polyline.addTo(this.polylineContainer); + // } else { + // // @ts-ignore + // this.polyline.setLatLngs(e.layer.getLatLngs()); + // // @ts-ignore + // console.log("ELSE :", this.polyline) + // // } + // } // @ts-ignore e.layer._pmTempLayer = true; e.layer.remove(); @@ -274,7 +317,7 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem Date: Wed, 15 Oct 2025 12:07:37 +0300 Subject: [PATCH 18/38] Fix for cut button --- .../maps/data-layer/polygons-data-layer.ts | 2 - .../maps/data-layer/polylines-data-layer.ts | 56 ++++++------------- 2 files changed, 16 insertions(+), 42 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polygons-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polygons-data-layer.ts index 98b8546400..7f45ea36da 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polygons-data-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polygons-data-layer.ts @@ -253,8 +253,6 @@ class TbPolygonDataLayerItem extends TbLatestDataLayerItem { - console.log("(e.layer in polygon", e.layer ) - console.log("this.polygon instanceof L.Rectangle", this.polygon instanceof L.Rectangle) if (e.layer instanceof L.Polygon) { if (this.polygon instanceof L.Rectangle) { this.polygonContainer.removeLayer(this.polygon); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polylines-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polylines-data-layer.ts index 0bc2fdaed5..1bb4b97b4c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polylines-data-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polylines-data-layer.ts @@ -36,9 +36,6 @@ import { UnplacedMapDataItem } from '@home/components/widget/lib/maps/data-layer/latest-map-data-layer'; import { map } from 'rxjs/operators'; -import { PointItem } from '@home/components/widget/lib/maps/data-layer/trips-data-layer'; -import _ from 'lodash'; -import { createTooltip, updateTooltip } from '@home/components/widget/lib/maps/data-layer/data-layer-utils'; class TbPolylineDataLayerItem extends TbLatestDataLayerItem { @@ -260,7 +257,6 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem { if (e.layer instanceof L.Polyline) { - // fallback for single segment case this.polyline = L.polyline(e.layer.getLatLngs() as L.LatLngExpression[] | L.LatLngExpression[][] , { ...this.polylineStyleInfo.style, snapIgnore: !this.dataLayer.isSnappable(), @@ -268,46 +264,26 @@ class TbPolylineDataLayerItem extends TbLatestDataLayerItem { - console.log(segment instanceof L.Polyline) - segment.setStyle({ - ...this.polylineStyleInfo.style, - snapIgnore: !this.dataLayer.isSnappable(), - bubblingMouseEvents: !this.dataLayer.isEditMode() - }); - // segment.setStyle({ - // ...this.polylineStyleInfo.style, - // snapIgnore: !this.dataLayer.isSnappable(), - // bubblingMouseEvents: !this.dataLayer.isEditMode() - // }); - segment.addTo(this.polylineContainer); - }); + const parts: L.LatLngExpression[][] = []; + if (e.layer && typeof e.layer.getLayers === 'function') { + const segments: L.Polyline[] = e.layer.getLayers() as L.Polyline[]; + segments.forEach(segment => { + parts.push(segment.getLatLngs() as L.LatLngExpression[]); + }); + } else if (e.layer instanceof L.Polyline) { + parts.push(e.layer.getLatLngs() as L.LatLngExpression[]); } - - + if (parts.length > 0) { + this.polyline = L.polyline(parts, { + ...this.polylineStyleInfo.style, + snapIgnore: !this.dataLayer.isSnappable(), + bubblingMouseEvents: !this.dataLayer.isEditMode() + }); + this.polyline.addTo(this.polylineContainer); + } } - // if (e.layer instanceof L.Polyline) { - // // if (this.polyline instanceof L.Polyline) { - // this.polylineContainer.removeLayer(this.polyline); - // this.polyline = L.polyline(e.layer.getLatLngs() as L.LatLngExpression[] | L.LatLngExpression[][] , { - // ...this.polylineStyleInfo.style, - // snapIgnore: !this.dataLayer.isSnappable(), - // bubblingMouseEvents: !this.dataLayer.isEditMode() - // }); - // this.polyline.addTo(this.polylineContainer); - // } else { - // // @ts-ignore - // this.polyline.setLatLngs(e.layer.getLatLngs()); - // // @ts-ignore - // console.log("ELSE :", this.polyline) - // // } - // } // @ts-ignore e.layer._pmTempLayer = true; e.layer.remove(); From 04fcbd36dccaf27bb0d2a33d8b83c554a7c1c287 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Tue, 14 Oct 2025 10:35:27 +0300 Subject: [PATCH 19/38] added save to image gallery --- .../lib/photo-camera-input.component.html | 2 +- .../lib/photo-camera-input.component.ts | 94 +++++++++++++------ ...amera-input-widget-settings.component.html | 84 +++++++++++------ ...-camera-input-widget-settings.component.ts | 24 ++++- .../assets/locale/locale.constant-en_US.json | 13 ++- 5 files changed, 152 insertions(+), 65 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.html index bc3ad72e58..fe53ef9f66 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.html @@ -20,7 +20,7 @@
widgets.input-widgets.no-image - last photo + last photo
+ + @if (photoCameraInputWidgetSettingsForm.get('imageFormat').value !== 'image/png') { +
+
widgets.input-widgets.image-quality
+ + + % + +
+ } + +
+
Size
+
+
widgets.input-widgets.max-image-width
+ + + px + + +
widgets.input-widgets.max-image-height
+ + + px + +
+
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/photo-camera-input-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/photo-camera-input-widget-settings.component.ts index 30c3e39833..7bc75573f1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/photo-camera-input-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/photo-camera-input-widget-settings.component.ts @@ -19,6 +19,7 @@ import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.m import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; +import { deepClone } from '@app/core/utils'; @Component({ selector: 'tb-photo-camera-input-widget-settings', @@ -42,6 +43,8 @@ export class PhotoCameraInputWidgetSettingsComponent extends WidgetSettingsCompo return { widgetTitle: '', + saveToGallery: false, + imageVisibility: true, imageFormat: 'image/png', imageQuality: 0.92, maxWidth: 640, @@ -57,11 +60,28 @@ export class PhotoCameraInputWidgetSettingsComponent extends WidgetSettingsCompo widgetTitle: [settings.widgetTitle, []], // Image settings - + saveToGallery: [settings.saveToGallery], + imageVisibility: [settings.imageVisibility], imageFormat: [settings.imageFormat, []], - imageQuality: [settings.imageQuality, [Validators.min(0), Validators.max(1)]], + imageQuality: [settings.imageQuality, [Validators.min(0), Validators.max(100)]], maxWidth: [settings.maxWidth, [Validators.min(1)]], maxHeight: [settings.maxHeight, [Validators.min(1)]] }); } + + protected prepareInputSettings(settings: WidgetSettings): WidgetSettings { + return { + ...settings, + saveToGallery: settings.saveToGallery || false, + imageQuality: settings.imageQuality * 100 + } + } + + protected prepareOutputSettings(settings: WidgetSettings): WidgetSettings { + return { + ...settings, + saveToGallery: settings.saveToGallery || false, + imageQuality: settings.imageQuality / 100 + } + } } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 4a347ab6cd..36f62219cd 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -8049,14 +8049,14 @@ "attribute-scope-server": "Server attribute", "attribute-scope-shared": "Shared attribute", "value-required": "Value required", - "image-settings": "Image settings", + "image-settings": "Image output settings", "image-format": "Image format", "image-format-jpeg": "JPEG", "image-format-png": "PNG", "image-format-webp": "WEBP", - "image-quality": "Image quality that use lossy compression such as jpeg and webp", - "max-image-width": "Maximum image width", - "max-image-height": "Maximum image height", + "image-quality": "Image quality", + "max-image-width": "Max width", + "max-image-height": "Max height", "action-buttons": "Action buttons", "show-action-buttons": "Show action buttons", "update-all-values": "Update all values, not only modified", @@ -8137,7 +8137,10 @@ "add-radio-option": "Add radio option", "radio-label-position": "Label position", "radio-label-position-before": "Before", - "radio-label-position-after": "After" + "radio-label-position-after": "After", + "save-image": "Save image", + "save-to-gallery": "Automatically store captured images in Image Gallery", + "public-image": "Makes image avaliable for any unauthorized user" }, "invalid-qr-code-text": "Invalid input text for QR code. Input should have a string type", "qr-code": { From 9c6170d8c0c75e68f10c68b7fe479eed4a3244b4 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Thu, 23 Oct 2025 14:24:43 +0300 Subject: [PATCH 20/38] refactore --- .../lib/photo-camera-input.component.ts | 25 +++++++++---------- ...-camera-input-widget-settings.component.ts | 13 +++++----- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.ts index d977dc80e2..38d14a937b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.ts @@ -24,25 +24,25 @@ import { ViewChild, ViewEncapsulation } from '@angular/core'; -import { PageComponent } from '@shared/components/page.component'; -import { WidgetContext } from '@home/models/widget-component.models'; -import { Store } from '@ngrx/store'; +import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; +import { ImageService } from '@app/core/public-api'; import { AppState } from '@core/core.state'; +import { AttributeService } from '@core/http/attribute.service'; import { UtilsService } from '@core/services/utils.service'; -import { Datasource, DatasourceData, DatasourceType } from '@shared/models/widget.models'; import { WINDOW } from '@core/services/window.service'; -import { AttributeService } from '@core/http/attribute.service'; +import { isString } from '@core/utils'; +import { WidgetContext } from '@home/models/widget-component.models'; +import { Store } from '@ngrx/store'; +import { PageComponent } from '@shared/components/page.component'; import { EntityId } from '@shared/models/id/entity-id'; import { AttributeScope, DataKeyType } from '@shared/models/telemetry/telemetry.models'; -import { map, Observable, of, switchMap, tap } from 'rxjs'; -import { isFile, isString } from '@core/utils'; -import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; -import { ImageService } from '@app/core/public-api'; +import { Datasource, DatasourceData, DatasourceType } from '@shared/models/widget.models'; +import { map, Observable, of, switchMap } from 'rxjs'; interface PhotoCameraInputWidgetSettings { widgetTitle: string; saveToGallery: boolean; - imageVisibility: boolean; + usePublicGalleryLink: boolean; imageQuality: number; imageFormat: string; maxWidth: number; @@ -284,8 +284,7 @@ export class PhotoCameraInputWidgetComponent extends PageComponent implements On this.canvasElement.height = this.videoHeight; this.canvasElement.getContext('2d').drawImage(this.videoElement, 0, 0, this.videoWidth, this.videoHeight); - const previewDataUrl = this.canvasElement.toDataURL(this.mimeType, this.quality); - this.previewPhoto = previewDataUrl; + this.previewPhoto = this.canvasElement.toDataURL(this.mimeType, this.quality); this.isPreviewPhoto = true; } @@ -310,7 +309,7 @@ export class PhotoCameraInputWidgetComponent extends PageComponent implements On return this.imageService.uploadImage(file, fileName); }), map((imageInfo) => - this.settings.imageVisibility ? imageInfo.publicLink : imageInfo.link + this.settings.usePublicGalleryLink ? imageInfo.publicLink : imageInfo.link ) ); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/photo-camera-input-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/photo-camera-input-widget-settings.component.ts index 7bc75573f1..4e7d552f44 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/photo-camera-input-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/photo-camera-input-widget-settings.component.ts @@ -15,11 +15,10 @@ /// import { Component } from '@angular/core'; -import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; -import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { deepClone } from '@app/core/utils'; +import { Store } from '@ngrx/store'; +import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; @Component({ selector: 'tb-photo-camera-input-widget-settings', @@ -44,7 +43,7 @@ export class PhotoCameraInputWidgetSettingsComponent extends WidgetSettingsCompo widgetTitle: '', saveToGallery: false, - imageVisibility: true, + usePublicGalleryLink: true, imageFormat: 'image/png', imageQuality: 0.92, maxWidth: 640, @@ -61,7 +60,7 @@ export class PhotoCameraInputWidgetSettingsComponent extends WidgetSettingsCompo // Image settings saveToGallery: [settings.saveToGallery], - imageVisibility: [settings.imageVisibility], + usePublicGalleryLink: [settings.usePublicGalleryLink], imageFormat: [settings.imageFormat, []], imageQuality: [settings.imageQuality, [Validators.min(0), Validators.max(100)]], maxWidth: [settings.maxWidth, [Validators.min(1)]], @@ -72,7 +71,8 @@ export class PhotoCameraInputWidgetSettingsComponent extends WidgetSettingsCompo protected prepareInputSettings(settings: WidgetSettings): WidgetSettings { return { ...settings, - saveToGallery: settings.saveToGallery || false, + saveToGallery: settings.saveToGallery ?? false, + usePublicGalleryLink: settings.usePublicGalleryLink ?? false, imageQuality: settings.imageQuality * 100 } } @@ -80,7 +80,6 @@ export class PhotoCameraInputWidgetSettingsComponent extends WidgetSettingsCompo protected prepareOutputSettings(settings: WidgetSettings): WidgetSettings { return { ...settings, - saveToGallery: settings.saveToGallery || false, imageQuality: settings.imageQuality / 100 } } From cb12d93ce85a6f9b974d0e221d74a271d8e8881a Mon Sep 17 00:00:00 2001 From: LeoMorgan113 Date: Thu, 23 Oct 2025 15:06:30 +0300 Subject: [PATCH 21/38] Reset files --- .../lib/maps/data-layer/map-data-layer.ts | 20 ++++++++----------- .../maps/data-layer/polygons-data-layer.ts | 13 ++++-------- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts index f3c13167ae..435b63ad49 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts @@ -28,6 +28,7 @@ import { } from '@shared/models/widget/maps/map.models'; import { createLabelFromPattern, + guid, isDefined, isDefinedAndNotNull, isNumber, @@ -52,8 +53,7 @@ export class DataLayerPatternProcessor { private pattern: string; constructor(private dataLayer: TbMapDataLayer, - private settings: DataLayerPatternSettings) { - } + private settings: DataLayerPatternSettings) {} public setup(): Observable { if (this.settings.type === DataLayerPatternType.function) { @@ -91,8 +91,7 @@ export class DataLayerColorProcessor { private range: ColorRange[]; constructor(private dataLayer: TbMapDataLayer, - private settings: DataLayerColorSettings) { - } + private settings: DataLayerColorSettings) {} public setup(): Observable { this.color = this.settings.color; @@ -151,8 +150,7 @@ export abstract class TbDataLayerItem(); - protected groupsState: { [group: string]: boolean } = {}; + protected groupsState: {[group: string]: boolean} = {}; protected enabled = true; @@ -199,7 +197,7 @@ export abstract class TbMapDataLayer { @@ -149,7 +147,7 @@ class TbPolygonDataLayerItem extends TbLatestDataLayerItem this.editing = true); this.polygon.on('pm:markerdragend', () => setTimeout(() => { this.editing = false; - })); + }) ); this.polygon.on('pm:edit', () => this.savePolygonCoordinates()); this.polygon.pm.enable(); const map = this.dataLayer.getMap(); @@ -247,10 +245,8 @@ class TbPolygonDataLayerItem extends TbLatestDataLayerItem { if (e.layer instanceof L.Polygon) { @@ -368,7 +364,6 @@ class TbPolygonDataLayerItem extends TbLatestDataLayerItem Date: Thu, 23 Oct 2025 15:12:10 +0300 Subject: [PATCH 22/38] Reset files --- .../widget/lib/maps/data-layer/map-data-layer.ts | 2 +- .../widget/lib/maps/data-layer/polygons-data-layer.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts index 435b63ad49..4380f36186 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts @@ -298,7 +298,7 @@ export abstract class TbMapDataLayer settings.type === DataLayerColorType.range && settings.rangeKey) - .map(settings => settings.rangeKey); + .map(settings => settings.rangeKey); dataKeys.push(...colorRangeKeys); dataKeys.push(...this.getDataKeys()); return dataKeys; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polygons-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polygons-data-layer.ts index d45f70d403..cfe564e7fd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polygons-data-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polygons-data-layer.ts @@ -90,7 +90,7 @@ class TbPolygonDataLayerItem extends TbLatestDataLayerItem, dsData: FormattedData[]): void { @@ -189,9 +189,9 @@ class TbPolygonDataLayerItem extends TbLatestDataLayerItem Date: Fri, 24 Oct 2025 09:22:52 +0300 Subject: [PATCH 23/38] doSetup in shapes-data-layer --- .../components/widget/lib/maps/data-layer/shapes-data-layer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/shapes-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/shapes-data-layer.ts index b2a42e5d35..3b23436441 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/shapes-data-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/shapes-data-layer.ts @@ -371,7 +371,7 @@ export abstract class TbShapesDataLayer { this.shapePatternProcessor = ShapePatternProcessor.fromSettings(this, this.settings); this.strokeColorProcessor = new DataLayerColorProcessor(this, this.settings.strokeColor); - return forkJoin([this.shapePatternProcessor ? this.shapePatternProcessor.setup() : of([]), this.strokeColorProcessor.setup()]); + return forkJoin([this.shapePatternProcessor.setup(), this.strokeColorProcessor.setup()]); } } From 8fb976af4cc21ec009a368e4465c43bb366b730e Mon Sep 17 00:00:00 2001 From: LeoMorgan113 Date: Fri, 24 Oct 2025 09:28:38 +0300 Subject: [PATCH 24/38] Removed extra formatting --- .../lib/maps/data-layer/shapes-data-layer.ts | 15 +++++------- .../components/widget/lib/maps/geo-map.ts | 23 ++++++++----------- .../shared/models/widget/maps/map.models.ts | 13 ++++++----- 3 files changed, 23 insertions(+), 28 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/shapes-data-layer.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/shapes-data-layer.ts index 3b23436441..ef68f0a6b3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/shapes-data-layer.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/shapes-data-layer.ts @@ -90,8 +90,7 @@ abstract class ShapePatternProcessor { } protected constructor(protected dataLayer: TbMapDataLayer, - protected settings: S) { - } + protected settings: S) {} public abstract setup(): Observable; @@ -122,10 +121,8 @@ abstract class ShapePatternProcessor { let pattern: L.TB.Pattern; if (patternInfo.type === ShapeFillType.color) { pattern = new L.TB.Pattern({width: 1, height: 1}); - const fillRect = new L.TB.PatternRect({ - x: 0, y: 0, width: 1, height: 1, - fillOpacity: 1, stroke: false, fill: true, fillColor: patternInfo.fillColor - }); + const fillRect = new L.TB.PatternRect({x: 0, y: 0, width: 1, height: 1, + fillOpacity: 1, stroke: false, fill: true, fillColor: patternInfo.fillColor}); pattern.addElement(fillRect); } else if (patternInfo.type === ShapeFillType.image) { const patternOptions: L.TB.PatternOptions = { @@ -133,7 +130,7 @@ abstract class ShapePatternProcessor { height: 1, patternUnits: 'objectBoundingBox', patternContentUnits: 'objectBoundingBox', - viewBox: [0, 0, patternInfo.fillImage.width, patternInfo.fillImage.height] + viewBox: [0,0,patternInfo.fillImage.width,patternInfo.fillImage.height] }; if (patternInfo.fillImage.preserveAspectRatio) { patternOptions.preserveAspectRatioAlign = 'xMidYMid'; @@ -306,7 +303,7 @@ class ShapeStripePatternProcessor extends ShapePatternProcessor> extends TbLatestMapDataLayer { +export abstract class TbShapesDataLayer> extends TbLatestMapDataLayer { private shapePatternProcessor: ShapePatternProcessor; private strokeColorProcessor: DataLayerColorProcessor; @@ -354,7 +351,7 @@ export abstract class TbShapesDataLayer { return of(map); } - protected onResize(): void { - } + protected onResize(): void {} protected fitBounds(bounds: L.LatLngBounds) { if (bounds.isValid()) { if (!this.settings.fitMapBounds && this.settings.defaultZoomLevel) { - this.map.setZoom(this.settings.defaultZoomLevel, {animate: false}); + this.map.setZoom(this.settings.defaultZoomLevel, { animate: false }); if (this.settings.useDefaultCenterPosition) { - this.map.panTo(this.defaultCenterPosition, {animate: false}); - } else { + this.map.panTo(this.defaultCenterPosition, { animate: false }); + } + else { this.map.panTo(bounds.getCenter()); } } else { @@ -80,13 +80,13 @@ export class TbGeoMap extends TbMap { minZoom = Math.max(minZoom, this.settings.defaultZoomLevel); } if (this.map.getZoom() > minZoom) { - this.map.setZoom(minZoom, {animate: false}); + this.map.setZoom(minZoom, { animate: false }); } }); if (this.settings.useDefaultCenterPosition) { bounds = bounds.extend(this.defaultCenterPosition); } - this.map.fitBounds(bounds, {padding: [50, 50], animate: false}); + this.map.fitBounds(bounds, { padding: [50, 50], animate: false }); this.map.invalidateSize(); } } @@ -125,15 +125,12 @@ export class TbGeoMap extends TbMap { ); } - public locationDataToLatLng(position: { x: number; y: number }): L.LatLng { + public locationDataToLatLng(position: {x: number; y: number}): L.LatLng { return L.latLng(position.x, position.y) as L.LatLng; } - public latLngToLocationData(position: L.LatLng): { x: number; y: number } { - position = position ? latLngPointToBounds(position, this.southWest, this.northEast, 0) : { - lat: null, - lng: null - } as L.LatLng; + public latLngToLocationData(position: L.LatLng): {x: number; y: number} { + position = position ? latLngPointToBounds(position, this.southWest, this.northEast, 0) : {lat: null, lng: null} as L.LatLng; return { x: position.lat, y: position.lng diff --git a/ui-ngx/src/app/shared/models/widget/maps/map.models.ts b/ui-ngx/src/app/shared/models/widget/maps/map.models.ts index 05ca031692..5be6331870 100644 --- a/ui-ngx/src/app/shared/models/widget/maps/map.models.ts +++ b/ui-ngx/src/app/shared/models/widget/maps/map.models.ts @@ -162,6 +162,7 @@ export interface DataLayerEditSettings { snappable: boolean; } + export interface MapDataLayerSettings extends MapDataSourceSettings { additionalDataSources?: MapDataSourceSettings[]; additionalDataKeys?: DataKey[]; @@ -910,7 +911,7 @@ export const defaultBaseMapSettings: BaseMapSettings = { tripTimeline: { showTimelineControl: false, timeStep: 1000, - speedOptions: [1, 5, 10, 15, 25], + speedOptions: [1,5,10,15,25], showTimestamp: true, timestampFormat: simpleDateFormat('yyyy-MM-dd HH:mm:ss'), snapToRealLocation: false, @@ -1282,7 +1283,7 @@ export type MapStringFunction = (data: FormattedData, dsData: FormattedData[]) => string; export type MapBooleanFunction = (data: FormattedData, - dsData: FormattedData[]) => boolean; + dsData: FormattedData[]) => boolean; export type MarkerImageFunction = (data: FormattedData, markerImages: string[], dsData: FormattedData[]) => MarkerImageInfo; @@ -1342,7 +1343,7 @@ export const isValidLatLng = (latitude: any, longitude: any): boolean => isValidLatitude(latitude) && isValidLongitude(longitude); export const isCutPolygon = (data: TbPolygonCoordinates | TbPolygonRawCoordinates): boolean => { - return data.length > 1 && Array.isArray(data[0]) && (Array.isArray(data[0][0]) || (isNumber((data[0][0] as any).lat) && isNumber((data[0][0] as any).lng))); + return data.length > 1 && Array.isArray(data[0]) && (Array.isArray(data[0][0]) || (isNumber((data[0][0] as any).lat) && isNumber((data[0][0] as any).lng)) ); } export const parseCenterPosition = (position: string | [number, number]): [number, number] => { @@ -1426,7 +1427,7 @@ const mergeMapDatasource = (target: TbMapDatasource, source: TbMapDatasource): T return target; } -const imageAspectMap: { [key: string]: ImageWithAspect } = {}; +const imageAspectMap: {[key: string]: ImageWithAspect} = {}; const imageLoader = (imageUrl: string): Observable => new Observable((observer: Observer) => { const image = document.createElement('img'); // support IE @@ -1473,7 +1474,7 @@ export const loadImageWithAspect = (imagePipe: ImagePipe, imageUrl: string): Obs url, width: size[0], height: size[1], - aspect: size[0] / size[1] + aspect: size[0]/size[1] }; imageAspectMap[hash] = imageWithAspect; return imageWithAspect; @@ -1551,7 +1552,7 @@ export const latLngPointToBounds = (point: L.LatLng, southWest: L.LatLng, northE return point; } -export type TripRouteData = { [time: number]: FormattedData }; +export type TripRouteData = {[time: number]: FormattedData}; export const calculateInterpolationRatio = (firsMoment: number, secondMoment: number, intermediateMoment: number): number => { return (intermediateMoment - firsMoment) / (secondMoment - firsMoment); From e09f7df512eacd63758892d4c8406ffda0df484c Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Fri, 24 Oct 2025 11:34:46 +0300 Subject: [PATCH 25/38] minor fix --- .../input/photo-camera-input-widget-settings.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/photo-camera-input-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/photo-camera-input-widget-settings.component.html index 8d0d60364e..cabd91d4e7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/photo-camera-input-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/input/photo-camera-input-widget-settings.component.html @@ -31,7 +31,7 @@ {{ 'widgets.input-widgets.save-to-gallery' | translate }} @if (photoCameraInputWidgetSettingsForm.get('saveToGallery').value) { - + {{ 'widgets.input-widgets.public-image' | translate }} } From b6dd32216c89325c760cac8c4f293ef07afc1f0a Mon Sep 17 00:00:00 2001 From: LeoMorgan113 Date: Fri, 24 Oct 2025 11:49:38 +0300 Subject: [PATCH 26/38] Added logic for 'Place map item action', extended helper functions --- .../modules/home/components/widget/lib/maps/map.ts | 2 +- .../common/action/map-item-tooltips.component.html | 14 ++++++++++++++ .../common/action/place-map-item-sample-js.raw | 2 +- ui-ngx/src/app/shared/models/widget.models.ts | 1 + .../app/shared/models/widget/maps/map.models.ts | 1 - .../action/place_map_item/create_dialog_js.md | 2 +- .../action/place_map_item/place_map_item_action.md | 5 +++-- .../src/assets/locale/locale.constant-en_US.json | 10 +++++++--- 8 files changed, 28 insertions(+), 9 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts index 9f3c359a6e..2c111110c9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts @@ -582,7 +582,7 @@ export abstract class TbMap { private drawPolyline(e: MouseEvent, button: L.TB.ToolbarButton): void { this.placeItem(e, button, this.addPolylineDataLayers, (entity) => this.prepareDrawMode('Line', { startPolyline: this.ctx.translate.instant('widgets.maps.data-layer.polyline.polyline-place-first-point-hint-with-entity', {entityName: entity.entity.entityDisplayName}), - finishPolyline: this.ctx.translate.instant('widgets.maps.data-layer.polyline.finish-polyline-hint', {entityName: entity.entity.entityDisplayName}), + finishPolyline: this.ctx.translate.instant('widgets.maps.data-layer.polyline.finish-polyline-hint-with-entity', {entityName: entity.entity.entityDisplayName}), })); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/map-item-tooltips.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/map-item-tooltips.component.html index f0abe17509..16218b1e11 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/map-item-tooltips.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/map-item-tooltips.component.html @@ -76,6 +76,20 @@
} + @case (MapItemType.polyline) { +
+
widget-action.map-item-tooltip.start-draw-polyline
+ + + +
+
+
widget-action.map-item-tooltip.finish-draw-polyline
+ + + +
+ } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/place-map-item-sample-js.raw b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/place-map-item-sample-js.raw index cb8c23faee..05e06542a5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/place-map-item-sample-js.raw +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/place-map-item-sample-js.raw @@ -79,7 +79,7 @@ function AddEntityDialogController(instance) { const mapType = widgetContext.mapInstance.type(); attributes.push({key: mapType === 'image' ? 'xPos' : 'latitude', value: additionalParams.coordinates.x}); attributes.push({key: mapType === 'image' ? 'yPos' : 'longitude', value: additionalParams.coordinates.y}); - } else if (mapItemType === 'Rectangle' || mapItemType === 'Polygon') { + } else if (mapItemType === 'Rectangle' || mapItemType === 'Polygon' || mapItemType === 'Line' ) { attributes.push({key: 'perimeter', value: additionalParams.coordinates}); } else if (mapItemType === 'Circle') { attributes.push({key: 'circle', value: additionalParams.coordinates}); diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 9addfc7b18..f612dd1503 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -700,6 +700,7 @@ export const mapItemTypeTranslationMap = new Map( [ MapItemType.polygon, 'widget-action.map-item.polygon' ], [ MapItemType.rectangle, 'widget-action.map-item.rectangle' ], [ MapItemType.circle, 'widget-action.map-item.circle' ], + [ MapItemType.polyline, 'widget-action.map-item.polyline' ] ] ) diff --git a/ui-ngx/src/app/shared/models/widget/maps/map.models.ts b/ui-ngx/src/app/shared/models/widget/maps/map.models.ts index 5be6331870..d3be99541f 100644 --- a/ui-ngx/src/app/shared/models/widget/maps/map.models.ts +++ b/ui-ngx/src/app/shared/models/widget/maps/map.models.ts @@ -1302,7 +1302,6 @@ export type TbPolyData = L.LatLngTuple[] | L.LatLngTuple[][] | L.LatLngTuple[][] export type TbPolygonCoordinate = L.LatLng | L.LatLng[] | L.LatLng[][]; export type TbPolygonCoordinates = TbPolygonCoordinate[]; - export type TbPolylineRawCoordinate = L.LatLngTuple | L.LatLngTuple[] | L.LatLngTuple[][]; export type TbPolylineRawCoordinates = TbPolylineRawCoordinate[]; export type TbPolylineData = L.LatLngTuple[] | L.LatLngTuple[][] | L.LatLngTuple[][][]; diff --git a/ui-ngx/src/assets/help/en_US/widget/action/place_map_item/create_dialog_js.md b/ui-ngx/src/assets/help/en_US/widget/action/place_map_item/create_dialog_js.md index bc8823c777..3bb410f10b 100644 --- a/ui-ngx/src/assets/help/en_US/widget/action/place_map_item/create_dialog_js.md +++ b/ui-ngx/src/assets/help/en_US/widget/action/place_map_item/create_dialog_js.md @@ -79,7 +79,7 @@ function AddEntityDialogController(instance) { const mapType = widgetContext.mapInstance.type(); attributes.push({key: mapType === 'image' ? 'xPos' : 'latitude', value: additionalParams.coordinates.x}); attributes.push({key: mapType === 'image' ? 'yPos' : 'longitude', value: additionalParams.coordinates.y}); - } else if (mapItemType === 'Rectangle' || mapItemType === 'Polygon') { + } else if (mapItemType === 'Rectangle' || mapItemType === 'Polygon' || mapItemType === 'Line') { attributes.push({key: 'perimeter', value: additionalParams.coordinates}); } else if (mapItemType === 'Circle') { attributes.push({key: 'circle', value: additionalParams.coordinates}); diff --git a/ui-ngx/src/assets/help/en_US/widget/action/place_map_item/place_map_item_action.md b/ui-ngx/src/assets/help/en_US/widget/action/place_map_item/place_map_item_action.md index 131a674099..170a3ab32b 100644 --- a/ui-ngx/src/assets/help/en_US/widget/action/place_map_item/place_map_item_action.md +++ b/ui-ngx/src/assets/help/en_US/widget/action/place_map_item/place_map_item_action.md @@ -26,8 +26,9 @@ A JavaScript function triggered after a map item is placed. Optionally uses an H
  • coordinates: Coordinates - Represents geographical coordinates of the placed map item. The actual format of this parameter depends on the type of the selected map item:
    • Marker: {x: number; y: number}, where x represents latitude, and y represents longitude.
    • -
    • Polygon, Rectangle: TbPolygonRawCoordinates contains an array of points defining the shape boundaries.
    • -
    • Circle: TbCircleData contains center coordinates and radius information.
    • +
    • Polygon, Rectangle: TbPolygonRawCoordinates contains an array of points defining the shape boundaries.
    • +
    • Circle: TbCircleData contains center coordinates and radius information.
    • +
    • Polyline: TbPolylineRawCoordinates contains an array of points defining the polyline.
    Note: The coordinates will be automatically converted according to the selected map type.
  • diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 33c0189508..74271e2e4f 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -7021,7 +7021,8 @@ "marker": "Marker", "polygon": "Polygon", "rectangle": "Rectangle", - "circle": "Circle" + "circle": "Circle", + "polyline": "Polyline" }, "place-map-item": "Place map item", "map-item-tooltip": { @@ -7033,7 +7034,9 @@ "continue-draw-polygon": "Continue draw polygon", "finish-draw-polygon": "Finish draw polygon", "start-draw-circle": "Start draw circle", - "finish-draw-circle": "Finish draw circle" + "finish-draw-circle": "Finish draw circle", + "start-draw-polyline": "Start draw polyline", + "finish-draw-polyline": "Finish draw polyline" } }, "widgets-bundle": { @@ -8644,7 +8647,8 @@ "draw-polyline": "Draw polyline", "polyline-place-first-point-hint-with-entity": "Polyline for '{{entityName}}': click to place first point", "polyline-place-first-point-hint": "Polyline: click to place first point", - "finish-polyline-hint": "Polyline for '{{entityName}}': click to finish drawing", + "finish-polyline-hint-with-entity": "Polyline for '{{entityName}}': click to finish drawing", + "finish-polyline-hint": "Polyline: click to finish drawing", "polyline-place-first-point-cut-hint": "Click to place first point", "finish-polyline-cut-hint": "Click first marker to finish and save" }, From 834c373e09b9b0d247d65c4b4bcd5bd77dd0f3bc Mon Sep 17 00:00:00 2001 From: LeoMorgan113 Date: Fri, 24 Oct 2025 11:58:57 +0300 Subject: [PATCH 27/38] Updated links --- .../widget/action/place_map_item/place_map_item_action.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/assets/help/en_US/widget/action/place_map_item/place_map_item_action.md b/ui-ngx/src/assets/help/en_US/widget/action/place_map_item/place_map_item_action.md index 170a3ab32b..b192042d2f 100644 --- a/ui-ngx/src/assets/help/en_US/widget/action/place_map_item/place_map_item_action.md +++ b/ui-ngx/src/assets/help/en_US/widget/action/place_map_item/place_map_item_action.md @@ -26,9 +26,9 @@ A JavaScript function triggered after a map item is placed. Optionally uses an H
  • coordinates: Coordinates - Represents geographical coordinates of the placed map item. The actual format of this parameter depends on the type of the selected map item:
    • Marker: {x: number; y: number}, where x represents latitude, and y represents longitude.
    • -
    • Polygon, Rectangle: TbPolygonRawCoordinates contains an array of points defining the shape boundaries.
    • -
    • Circle: TbCircleData contains center coordinates and radius information.
    • -
    • Polyline: TbPolylineRawCoordinates contains an array of points defining the polyline.
    • +
    • Polygon, Rectangle: TbPolygonRawCoordinates contains an array of points defining the shape boundaries.
    • +
    • Circle: TbCircleData contains center coordinates and radius information.
    • +
    • Polyline: TbPolylineRawCoordinates contains an array of points defining the polyline.
    Note: The coordinates will be automatically converted according to the selected map type.
  • From fc3f78e5c4b46fb424caf8be87b2b6f1c089e16d Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Thu, 9 Oct 2025 12:48:00 +0300 Subject: [PATCH 28/38] refactore css --- .../widget/lib/table-widget.models.ts | 9 ++--- .../components/widget/lib/table-widget.scss | 37 ++++++------------- ui-ngx/src/styles.scss | 8 ++-- 3 files changed, 19 insertions(+), 35 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts index 64e5e9c1f7..8f36242d95 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts @@ -454,7 +454,8 @@ export function constructTableCssString(widgetConfig: WidgetConfig): string { const cssString = ` { --mat-toolbar-container-text-color: ${mdDark}; - --mat-tab-header-inactive-label-text-color: ${mdDarkSecondary}; + --mat-tab-header-active-label-text-color: ${mdDark}; + --mat-tab-header-inactive-label-text-color: ${mdDark}; --mat-tab-header-pagination-icon-color: ${mdDark}; --mat-tab-header-pagination-disabled-icon-color: ${mdDarkDisabled2}; --mat-table-header-headline-color: ${mdDarkSecondary}; @@ -468,12 +469,10 @@ export function constructTableCssString(widgetConfig: WidgetConfig): string { --mat-paginator-disabled-icon-color: ${mdDarkDisabled}; --mat-select-enabled-trigger-text-color: ${mdDarkSecondary}; --mat-select-disabled-trigger-text-color: ${mdDarkDisabled}; - --mat-table-header-headline-color: ${mdDarkSecondary}; + --mat-table-row-item-outline-color: ${mdDarkDivider}; + --mdc-checkbox-unselected-focus-icon-color: ${mdDarkSecondary}; --tb-orig-background-color: ${origBackgroundColor}; - --tb-secondary-text-color: ${mdDarkSecondary}; - --tb-second-disabled-color: ${mdDarkDisabled2}; - --tb-divider-color: ${mdDarkDivider}; --tb-current-entity-color: ${currentEntityColor}; --tb-current-entity-sticky-color: ${currentEntityStickyColor}; --tb-hover-color: ${hoverColor}; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.scss b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.scss index 7c14750ba8..dd8a026db4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.scss @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -:host ::ng-deep{ +:host { .tb-table-widget { .mat-mdc-table, .mat-mdc-paginator, mat-toolbar.mat-mdc-table-toolbar:not([color="primary"]) { background: transparent; @@ -25,9 +25,6 @@ height: 39px; max-height: 39px; } - .mat-mdc-table-toolbar:not([color="primary"]) .mat-toolbar-tools button.mat-mdc-icon-button mat-icon { - color:var(--tb-secondary-text-color) - } } .table-container { overflow: auto; @@ -49,33 +46,22 @@ } .mat-mdc-tab-header-pagination-disabled .mat-mdc-tab-header-pagination-chevron { - border-color: var(--tb-second-disabled-color); + border-color: var(--mat-tab-header-pagination-disabled-icon-color); } } - .mat-mdc-input-element { - &::placeholder, - &::-moz-placeholder, - &::-webkit-input-placeholder, - &::-ms-input-placeholder { - color: var(--tb-secondary-text-color); - } + .mat-mdc-input-element::placeholder { + color: var(--mat-table-header-headline-color); } - + .mat-mdc-table { .mat-mdc-header-row { background-color: var(--tb-orig-background-color); } .mat-mdc-cell, .mat-mdc-header-cell { - border-bottom-color: var(--tb-divider-color); - &.mat-mdc-table-sticky{ - background-color:var(--tb-orig-background-color) - } - .mat-mdc-checkbox { - .mdc-checkbox__native-control:focus:enabled:not(:checked):not(:indeterminate):not([data-indeterminate='true']) ~ .mdc-checkbox__background { - border-color: var(--tb-secondary-text-color); - } + &.mat-mdc-table-sticky { + background-color:var(--tb-orig-background-color); } } @@ -83,26 +69,25 @@ background-color: rgba(0,0,0,0); &.tb-current-entity { background-color: var(--tb-current-entity-color); - .mat-mdc-cell.mat-mdc-table-sticky { background-color: var(--tb-current-entity-sticky-color); } } &:hover:not(.tb-current-entity){ background-color: var(--tb-hover-color); - .mat-mdc-cell.mat-mdc-table-sticky{ + .mat-mdc-cell.mat-mdc-table-sticky { background-color: var(--tb-hover-sticky-color); } } &.mat-row-select.mat-selected:not(.tb-current-entity){ background-color: var(--tb-selected-color); - .mat-mdc-cell.mat-mdc-table-sticky{ + .mat-mdc-cell.mat-mdc-table-sticky { background-color: var(--tb-selected-sticky-color); } } - .mat-mdc-cell.mat-mdc-table-sticky { + &, .mat-mdc-cell.mat-mdc-table-sticky { transition: background-color .2s; - background-color:var(--tb-orig-background-color) + background-color:var(--tb-orig-background-color); } } } diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index 6d49e9f698..66006ec701 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -692,7 +692,7 @@ pre.tb-highlight { mat-toolbar.mat-mdc-table-toolbar:not(.mat-primary), .mat-mdc-cell, .mat-expansion-panel-header, mat-card-header.mat-mdc-card-header { button.mat-mdc-icon-button { .mat-icon { - color: rgba(0, 0, 0, .54); + color:var(--mat-icon-color, rgba(0, 0, 0, .54)); } &[disabled][disabled] { .mat-icon { @@ -760,11 +760,11 @@ pre.tb-highlight { transition: background-color .2s; &:hover:not(.tb-current-entity) { - background-color: #f4f4f4; + background-color: var(--tb-hover-color, #f4f4f4); } &.tb-current-entity { - background-color: #e9e9e9; + background-color: var(--tb-current-entity-color, #e9e9e9); } &.tb-pointer { @@ -816,7 +816,7 @@ pre.tb-highlight { vertical-align: middle; border-width: 0; border-bottom-width: 1px; - border-bottom-color: rgba(0, 0, 0, 0.12); + border-bottom-color: var(--mat-table-row-item-outline-color,rgba(0, 0, 0, 0.12)); border-style: solid; text-overflow: ellipsis; touch-action: auto !important; From 2d4cf8558eaf539129756a77093f9b42ef81992e Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Wed, 15 Oct 2025 12:43:41 +0300 Subject: [PATCH 29/38] fixed bar chart label position --- .../widget/lib/chart/latest-chart.component.scss | 10 ++++++++-- .../widget/lib/chart/latest-chart.component.ts | 4 ---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.scss index 2eb0ceb940..e373fcc263 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.scss @@ -51,7 +51,7 @@ flex-direction: row-reverse; } .tb-latest-chart-shape { - flex: 1; + flex: 1 1 100%; min-width: 10px; min-height: 10px; display: flex; @@ -95,8 +95,14 @@ &.legend-right, &.legend-left { gap: 24px; .tb-latest-chart-legend { + flex: 0 1 auto; + overflow: hidden auto; + max-height: 100%; + flex-wrap: nowrap; + min-width: fit-content; + max-width: 100%; flex-direction: column; - justify-content: center; + justify-content: flex-start; align-items: stretch; .tb-latest-chart-legend-item { flex-direction: row; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.ts index 85905dfd3e..1fa0437cea 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.ts @@ -181,17 +181,13 @@ export class LatestChartComponent implements OnInit, OnDestroy, AfterViewInit { private onResize() { if (this.legendHorizontal) { - this.renderer.setStyle(this.chartShape.nativeElement, 'max-width', null); this.renderer.setStyle(this.chartShape.nativeElement, 'min-width', null); - this.renderer.setStyle(this.chartLegend.nativeElement, 'flex', null); } const shapeWidth = this.chartShape.nativeElement.getBoundingClientRect().width; const shapeHeight = this.chartShape.nativeElement.getBoundingClientRect().height; const size = Math.min(shapeWidth, shapeHeight); if (this.legendHorizontal) { - this.renderer.setStyle(this.chartShape.nativeElement, 'max-width', `${size}px`); this.renderer.setStyle(this.chartShape.nativeElement, 'min-width', `${size}px`); - this.renderer.setStyle(this.chartLegend.nativeElement, 'flex', '1'); } } From 6be7bd61a35471417d52921abaeffc635218d33a Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Wed, 15 Oct 2025 13:35:08 +0300 Subject: [PATCH 30/38] added option to sort tab entities in alphabetical order --- ...eries-table-widget-settings.component.html | 10 +++++ ...eseries-table-widget-settings.component.ts | 10 ++++- .../timeseries-table-widget.component.html | 2 +- .../lib/timeseries-table-widget.component.ts | 44 ++++++++++++++++--- ui-ngx/src/assets/dashboard/api_usage.json | 3 +- .../assets/locale/locale.constant-en_US.json | 6 ++- 6 files changed, 64 insertions(+), 11 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html index 52dbf0ba79..3902d699cf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html @@ -115,6 +115,16 @@ {{ 'widgets.table.use-entity-label-tab-name' | translate }} +
    +
    widgets.table.sort-by
    +
    + + {{ 'widgets.table.sort-timestamp-option' | translate }} + {{ 'widgets.table.sort-asc' | translate }} + {{ 'widgets.table.sort-desc' | translate }} + +
    +
    widgets.table.rows
    diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.ts index 05d49aca26..dc5e51c1c3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.ts @@ -20,6 +20,7 @@ import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { buildPageStepSizeValues } from '@home/components/widget/lib/table-widget.models'; +import { TabSortKey } from '@app/modules/home/components/widget/lib/timeseries-table-widget.component' @Component({ selector: 'tb-timeseries-table-widget-settings', @@ -28,6 +29,8 @@ import { buildPageStepSizeValues } from '@home/components/widget/lib/table-widge }) export class TimeseriesTableWidgetSettingsComponent extends WidgetSettingsComponent { + TabSortKey = TabSortKey; + timeseriesTableWidgetSettingsForm: UntypedFormGroup; pageStepSizeValues = []; @@ -58,12 +61,14 @@ export class TimeseriesTableWidgetSettingsComponent extends WidgetSettingsCompon hideEmptyLines: false, disableStickyHeader: false, useRowStyleFunction: false, - rowStyleFunction: '' + rowStyleFunction: '', + tabSortKey: TabSortKey.TIMESTAMP }; } protected prepareInputSettings(settings: WidgetSettings): WidgetSettings { settings.pageStepIncrement = settings.pageStepIncrement ?? settings.defaultPageSize; + settings.tabSortKey = settings.sortEntityAliasKey ?? TabSortKey.TIMESTAMP; this.pageStepSizeValues = buildPageStepSizeValues(settings.pageStepCount, settings.pageStepIncrement); return settings; } @@ -93,7 +98,8 @@ export class TimeseriesTableWidgetSettingsComponent extends WidgetSettingsCompon hideEmptyLines: [settings.hideEmptyLines, []], disableStickyHeader: [settings.disableStickyHeader, []], useRowStyleFunction: [settings.useRowStyleFunction, []], - rowStyleFunction: [settings.rowStyleFunction, [Validators.required]] + rowStyleFunction: [settings.rowStyleFunction, [Validators.required]], + sortEntityAliasKey: [settings.sortEntityAliasKey, []], }); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html index 73ea953a15..73ab2e679c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html @@ -41,7 +41,7 @@ class="flex-1" mat-stretch-tabs="false" [(selectedIndex)]="sourceIndex" (selectedIndexChange)="onSourceIndexChanged()"> - +
    ):string { + if (entityLabelCache.has(source.entityId)) { + return entityLabelCache.get(source.entityId); + } + + const value = this.useEntityLabel + ? (source.entityLabel || source.entityName) + : source.entityName; + + const translated = this.utils.customTranslation(value); + entityLabelCache.set(source.entityId, translated); + return translated; + } + + private sortDatasources(source: Datasource[], entityLabelCache:Map): Datasource[] { + source.forEach(ds => this.getTabLabel(ds, entityLabelCache)); + + if (this.settings.tabSortKey === TabSortKey.TIMESTAMP) { + return source; } + return source.sort((a, b) => { + const valueA = entityLabelCache.get(a.entityId); + const valueB = entityLabelCache.get(b.entityId); + return this.settings.tabSortKey === TabSortKey.NAME_ASC + ? valueA.localeCompare(valueB) + : valueB.localeCompare(valueA); + }); } private updateDatasources() { @@ -405,9 +434,11 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI this.sourceIndex = 0; let keyOffset = 0; let latestKeyOffset = 0; + const entityLabelCache = new Map(); const pageSize = this.displayPagination ? this.defaultPageSize : Number.POSITIVE_INFINITY; if (this.datasources) { - for (const datasource of this.datasources) { + const sortedDatasources = this.sortDatasources(this.datasources, entityLabelCache); + for (const datasource of sortedDatasources) { const sortOrder: SortOrder = sortOrderFromString(this.defaultSortOrder); const source = {} as TimeseriesTableSource; source.header = this.prepareHeader(datasource); @@ -424,6 +455,7 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI source.pageLink = new PageLink(pageSize, 0, null, sortOrder); source.rowDataTemplate = {}; source.rowDataTemplate.Timestamp = null; + source.displayName = entityLabelCache.get(datasource.entityId); if (this.showTimestamp) { source.displayedColumns.push('0'); } diff --git a/ui-ngx/src/assets/dashboard/api_usage.json b/ui-ngx/src/assets/dashboard/api_usage.json index 92c9bdb054..088861b6c1 100644 --- a/ui-ngx/src/assets/dashboard/api_usage.json +++ b/ui-ngx/src/assets/dashboard/api_usage.json @@ -98,7 +98,8 @@ "settings": { "showTimestamp": true, "displayPagination": true, - "defaultPageSize": 10 + "defaultPageSize": 10, + "tabSortKey": "timestamp" }, "title": "{i18n:api-usage.exceptions}", "dropShadow": true, diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 4a347ab6cd..6ebab2888e 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -9132,7 +9132,11 @@ "alarm-column-error": "At least one alarm column should be specified", "table-tabs": "Table tabs", "show-cell-actions-menu-mobile": "Show cell actions dropdown menu in mobile mode", - "disable-sorting": "Disable sorting" + "disable-sorting": "Disable sorting", + "sort-by": "Sort tabs by", + "sort-asc": "Name Ascending", + "sort-desc": "Name Descending", + "sort-timestamp-option": "Created time" }, "latest-chart": { "total": "Total", From 32f62bd5336a091ede4e0a9e3e31339d8df46808 Mon Sep 17 00:00:00 2001 From: Vladyslav Prykhodko Date: Thu, 30 Oct 2025 10:51:47 +0200 Subject: [PATCH 31/38] Update api_usage.json --- ui-ngx/src/assets/dashboard/api_usage.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/assets/dashboard/api_usage.json b/ui-ngx/src/assets/dashboard/api_usage.json index 088861b6c1..994b431ade 100644 --- a/ui-ngx/src/assets/dashboard/api_usage.json +++ b/ui-ngx/src/assets/dashboard/api_usage.json @@ -99,7 +99,7 @@ "showTimestamp": true, "displayPagination": true, "defaultPageSize": 10, - "tabSortKey": "timestamp" + "tabSortKey": "NAME_ASC" }, "title": "{i18n:api-usage.exceptions}", "dropShadow": true, From 3bc32b76c0ab82788eaeff7ec49fb76edb91e75d Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Thu, 30 Oct 2025 11:43:16 +0200 Subject: [PATCH 32/38] fixed renaming errors --- .../cards/timeseries-table-widget-settings.component.html | 2 +- .../cards/timeseries-table-widget-settings.component.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html index 3902d699cf..905d643caa 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html @@ -118,7 +118,7 @@
    widgets.table.sort-by
    - + {{ 'widgets.table.sort-timestamp-option' | translate }} {{ 'widgets.table.sort-asc' | translate }} {{ 'widgets.table.sort-desc' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.ts index dc5e51c1c3..ccbb582679 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.ts @@ -68,7 +68,7 @@ export class TimeseriesTableWidgetSettingsComponent extends WidgetSettingsCompon protected prepareInputSettings(settings: WidgetSettings): WidgetSettings { settings.pageStepIncrement = settings.pageStepIncrement ?? settings.defaultPageSize; - settings.tabSortKey = settings.sortEntityAliasKey ?? TabSortKey.TIMESTAMP; + settings.tabSortKey = settings.tabSortKey ?? TabSortKey.TIMESTAMP; this.pageStepSizeValues = buildPageStepSizeValues(settings.pageStepCount, settings.pageStepIncrement); return settings; } @@ -99,7 +99,7 @@ export class TimeseriesTableWidgetSettingsComponent extends WidgetSettingsCompon disableStickyHeader: [settings.disableStickyHeader, []], useRowStyleFunction: [settings.useRowStyleFunction, []], rowStyleFunction: [settings.rowStyleFunction, [Validators.required]], - sortEntityAliasKey: [settings.sortEntityAliasKey, []], + tabSortKey: [settings.tabSortKey, []], }); } From dd22c57c2b71ada0f5919fc2e4f97fdccdcc14a9 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Thu, 30 Oct 2025 12:22:43 +0200 Subject: [PATCH 33/38] fixed styles --- .../components/widget/lib/chart/latest-chart.component.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.scss index e373fcc263..1c97102fe0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/latest-chart.component.scss @@ -96,13 +96,13 @@ gap: 24px; .tb-latest-chart-legend { flex: 0 1 auto; - overflow: hidden auto; + overflow-y: auto; max-height: 100%; flex-wrap: nowrap; min-width: fit-content; max-width: 100%; flex-direction: column; - justify-content: flex-start; + justify-content: stretch; align-items: stretch; .tb-latest-chart-legend-item { flex-direction: row; From 810c125946077db93b65906d5b1a3a2d43b0cb71 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Thu, 30 Oct 2025 13:02:05 +0200 Subject: [PATCH 34/38] changed type of color picker fields --- .../@iplab+ngx-color-picker+18.0.1.patch | 97 +++++++++++++++++++ .../color-picker/hex-input.component.html | 2 +- 2 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 ui-ngx/patches/@iplab+ngx-color-picker+18.0.1.patch diff --git a/ui-ngx/patches/@iplab+ngx-color-picker+18.0.1.patch b/ui-ngx/patches/@iplab+ngx-color-picker+18.0.1.patch new file mode 100644 index 0000000000..c8090076cc --- /dev/null +++ b/ui-ngx/patches/@iplab+ngx-color-picker+18.0.1.patch @@ -0,0 +1,97 @@ +diff --git a/node_modules/@iplab/ngx-color-picker/esm2022/lib/components/parts/inputs/hex-input/hex-input.component.mjs b/node_modules/@iplab/ngx-color-picker/esm2022/lib/components/parts/inputs/hex-input/hex-input.component.mjs +index bf5480b..02c8246 100644 +--- a/node_modules/@iplab/ngx-color-picker/esm2022/lib/components/parts/inputs/hex-input/hex-input.component.mjs ++++ b/node_modules/@iplab/ngx-color-picker/esm2022/lib/components/parts/inputs/hex-input/hex-input.component.mjs +@@ -29,10 +29,10 @@ export class HexComponent { + } + } + static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.0", ngImport: i0, type: HexComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } +- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.0.0", type: HexComponent, isStandalone: true, selector: "hex-input-component", inputs: { color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: true, transformFunction: null }, labelVisible: { classPropertyName: "labelVisible", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, prefixValue: { classPropertyName: "prefixValue", publicName: "prefix", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { color: "colorChange" }, ngImport: i0, template: "
    \r\n \r\n @if (labelVisible()) {\r\n HEX\r\n }\r\n
    ", styles: [":host,:host ::ng-deep *{padding:0;margin:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}\n", ":host{display:table;width:100%;text-align:center;color:#b4b4b4;font-size:11px}.column{display:table-cell;padding:0 2px}input{width:100%;border:1px solid rgb(218,218,218);color:#272727;text-align:center;font-size:12px;-webkit-appearance:none;border-radius:0;margin:0 0 6px;height:26px;outline:none}\n", ""], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } ++ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.0.0", type: HexComponent, isStandalone: true, selector: "hex-input-component", inputs: { color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: true, transformFunction: null }, labelVisible: { classPropertyName: "labelVisible", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, prefixValue: { classPropertyName: "prefixValue", publicName: "prefix", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { color: "colorChange" }, ngImport: i0, template: "
    \r\n \r\n @if (labelVisible()) {\r\n HEX\r\n }\r\n
    ", styles: [":host,:host ::ng-deep *{padding:0;margin:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}\n", ":host{display:table;width:100%;text-align:center;color:#b4b4b4;font-size:11px}.column{display:table-cell;padding:0 2px}input{width:100%;border:1px solid rgb(218,218,218);color:#272727;text-align:center;font-size:12px;-webkit-appearance:none;border-radius:0;margin:0 0 6px;height:26px;outline:none}\n", ""], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } + } + i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.0", ngImport: i0, type: HexComponent, decorators: [{ + type: Component, +- args: [{ selector: `hex-input-component`, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: "
    \r\n \r\n @if (labelVisible()) {\r\n HEX\r\n }\r\n
    ", styles: [":host,:host ::ng-deep *{padding:0;margin:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}\n", ":host{display:table;width:100%;text-align:center;color:#b4b4b4;font-size:11px}.column{display:table-cell;padding:0 2px}input{width:100%;border:1px solid rgb(218,218,218);color:#272727;text-align:center;font-size:12px;-webkit-appearance:none;border-radius:0;margin:0 0 6px;height:26px;outline:none}\n"] }] ++ args: [{ selector: `hex-input-component`, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: "
    \r\n \r\n @if (labelVisible()) {\r\n HEX\r\n }\r\n
    ", styles: [":host,:host ::ng-deep *{padding:0;margin:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}\n", ":host{display:table;width:100%;text-align:center;color:#b4b4b4;font-size:11px}.column{display:table-cell;padding:0 2px}input{width:100%;border:1px solid rgb(218,218,218);color:#272727;text-align:center;font-size:12px;-webkit-appearance:none;border-radius:0;margin:0 0 6px;height:26px;outline:none}\n"] }] + }] }); + //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGV4LWlucHV0LmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3Byb2plY3RzL2lwbGFiL25neC1jb2xvci1waWNrZXIvc3JjL2xpYi9jb21wb25lbnRzL3BhcnRzL2lucHV0cy9oZXgtaW5wdXQvaGV4LWlucHV0LmNvbXBvbmVudC50cyIsIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3Byb2plY3RzL2lwbGFiL25neC1jb2xvci1waWNrZXIvc3JjL2xpYi9jb21wb25lbnRzL3BhcnRzL2lucHV0cy9oZXgtaW5wdXQvaGV4LWlucHV0LmNvbXBvbmVudC5odG1sIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxTQUFTLEVBQUUsdUJBQXVCLEVBQUUsZ0JBQWdCLEVBQWUsS0FBSyxFQUFFLEtBQUssRUFBZSxNQUFNLGVBQWUsQ0FBQztBQUM3SCxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0saUNBQWlDLENBQUM7O0FBY3hELE1BQU0sT0FBTyxZQUFZO0lBWHpCO1FBYVcsVUFBSyxHQUF1QixLQUFLLENBQUMsUUFBUSxFQUFTLENBQUM7UUFFcEQsaUJBQVksR0FBeUIsS0FBSyxDQUFtQixLQUFLLEVBQUUsRUFBRSxLQUFLLEVBQUUsT0FBTyxFQUFFLFNBQVMsRUFBRSxnQkFBZ0IsRUFBRSxDQUFDLENBQUM7UUFFckgsZ0JBQVcsR0FBd0IsS0FBSyxDQUFTLEVBQUUsRUFBRSxFQUFFLEtBQUssRUFBRSxRQUFRLEVBQUUsQ0FBQyxDQUFDO0tBMkJwRjtJQXpCRyxJQUFXLEtBQUs7UUFDWixPQUFPLElBQUksQ0FBQyxXQUFXLEVBQUUsR0FBRyxDQUFDLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLENBQUMsT0FBTyxFQUFFLENBQUMsS0FBSyxHQUFHLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxHQUFHLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQ2xJLENBQUM7SUFFTSxhQUFhLENBQUMsS0FBb0IsRUFBRSxVQUFrQjtRQUN6RCxNQUFNLEtBQUssR0FBRyxVQUFVLENBQUMsV0FBVyxFQUFFLENBQUMsT0FBTyxDQUFDLEdBQUcsRUFBRSxFQUFFLENBQUMsQ0FBQztRQUV4RCxJQUNBLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTyxLQUFLLEVBQUUsSUFBSSxLQUFLLENBQUMsR0FBRyxDQUFDLFdBQVcsRUFBRSxLQUFLLE9BQU8sQ0FBQyxJQUFJLEtBQUssQ0FBQyxNQUFNLEtBQUssQ0FBQyxDQUFDO2VBQ2xGLEtBQUssQ0FBQyxNQUFNLEtBQUssQ0FBQyxJQUFJLEtBQUssQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLENBQUM7WUFDMUMsTUFBTSxHQUFHLEdBQUcsUUFBUSxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQztZQUNoQyxNQUFNLE1BQU0sR0FBRyxHQUFHLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxDQUFDO1lBRWhDOzs7OztlQUtHO1lBQ0gsSUFBSSxNQUFNLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxNQUFNLEVBQUUsR0FBRyxDQUFDLEtBQUssS0FBSyxJQUFJLElBQUksQ0FBQyxLQUFLLEtBQUssS0FBSyxFQUFFLENBQUM7Z0JBQ3ZFLE1BQU0sUUFBUSxHQUFHLElBQUksS0FBSyxDQUFDLElBQUksS0FBSyxFQUFFLENBQUMsQ0FBQztnQkFDeEMsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsUUFBUSxDQUFDLENBQUM7WUFDN0IsQ0FBQztRQUNMLENBQUM7SUFDTCxDQUFDOzhHQWhDUSxZQUFZO2tHQUFaLFlBQVksZ2dCQ2Z6Qiw0TUFLTTs7MkZEVU8sWUFBWTtrQkFYeEIsU0FBUzsrQkFDSSxxQkFBcUIsbUJBT2QsdUJBQXVCLENBQUMsTUFBTSxjQUNuQyxJQUFJIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgQ29tcG9uZW50LCBDaGFuZ2VEZXRlY3Rpb25TdHJhdGVneSwgYm9vbGVhbkF0dHJpYnV0ZSwgSW5wdXRTaWduYWwsIGlucHV0LCBtb2RlbCwgTW9kZWxTaWduYWwgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcclxuaW1wb3J0IHsgQ29sb3IgfSBmcm9tICcuLi8uLi8uLi8uLi9oZWxwZXJzL2NvbG9yLmNsYXNzJztcclxuXHJcblxyXG5AQ29tcG9uZW50KHtcclxuICAgIHNlbGVjdG9yOiBgaGV4LWlucHV0LWNvbXBvbmVudGAsXHJcbiAgICB0ZW1wbGF0ZVVybDogYC4vaGV4LWlucHV0LmNvbXBvbmVudC5odG1sYCxcclxuICAgIHN0eWxlVXJsczogW1xyXG4gICAgICAgIGAuLy4uLy4uL2Jhc2Uuc3R5bGUuc2Nzc2AsXHJcbiAgICAgICAgYC4vLi4vaW5wdXQuY29tcG9uZW50LnNjc3NgLFxyXG4gICAgICAgIGAuL2hleC1pbnB1dC5jb21wb25lbnQuc2Nzc2BcclxuICAgIF0sXHJcbiAgICBjaGFuZ2VEZXRlY3Rpb246IENoYW5nZURldGVjdGlvblN0cmF0ZWd5Lk9uUHVzaCxcclxuICAgIHN0YW5kYWxvbmU6IHRydWVcclxufSlcclxuZXhwb3J0IGNsYXNzIEhleENvbXBvbmVudCB7XHJcblxyXG4gICAgcHVibGljIGNvbG9yOiBNb2RlbFNpZ25hbDxDb2xvcj4gPSBtb2RlbC5yZXF1aXJlZDxDb2xvcj4oKTtcclxuXHJcbiAgICBwdWJsaWMgbGFiZWxWaXNpYmxlOiBJbnB1dFNpZ25hbDxib29sZWFuPiA9IGlucHV0PGJvb2xlYW4sIGJvb2xlYW4+KGZhbHNlLCB7IGFsaWFzOiAnbGFiZWwnLCB0cmFuc2Zvcm06IGJvb2xlYW5BdHRyaWJ1dGUgfSk7XHJcblxyXG4gICAgcHVibGljIHByZWZpeFZhbHVlOiBJbnB1dFNpZ25hbDxzdHJpbmc+ID0gaW5wdXQ8c3RyaW5nPignJywgeyBhbGlhczogJ3ByZWZpeCcgfSk7XHJcblxyXG4gICAgcHVibGljIGdldCB2YWx1ZSgpIHtcclxuICAgICAgICByZXR1cm4gdGhpcy5wcmVmaXhWYWx1ZSgpICsgKHRoaXMuY29sb3IoKSA/IHRoaXMuY29sb3IoKS50b0hleFN0cmluZyh0aGlzLmNvbG9yKCkuZ2V0UmdiYSgpLmFscGhhIDwgMSkucmVwbGFjZSgnIycsICcnKSA6ICcnKTtcclxuICAgIH1cclxuXHJcbiAgICBwdWJsaWMgb25JbnB1dENoYW5nZShldmVudDogS2V5Ym9hcmRFdmVudCwgaW5wdXRWYWx1ZTogc3RyaW5nKTogdm9pZCB7XHJcbiAgICAgICAgY29uc3QgdmFsdWUgPSBpbnB1dFZhbHVlLnRvTG93ZXJDYXNlKCkucmVwbGFjZSgnIycsICcnKTtcclxuXHJcbiAgICAgICAgaWYgKFxyXG4gICAgICAgICgoZXZlbnQua2V5Q29kZSA9PT0gMTMgfHwgZXZlbnQua2V5LnRvTG93ZXJDYXNlKCkgPT09ICdlbnRlcicpICYmIHZhbHVlLmxlbmd0aCA9PT0gMylcclxuICAgICAgICB8fCB2YWx1ZS5sZW5ndGggPT09IDYgfHwgdmFsdWUubGVuZ3RoID09PSA4KSB7XHJcbiAgICAgICAgICAgIGNvbnN0IGhleCA9IHBhcnNlSW50KHZhbHVlLCAxNik7XHJcbiAgICAgICAgICAgIGNvbnN0IGhleFN0ciA9IGhleC50b1N0cmluZygxNik7XHJcblxyXG4gICAgICAgICAgICAvKipcclxuICAgICAgICAgICAgICogaWYgdmFsdWUgaXMgdmFsaWRcclxuICAgICAgICAgICAgICogY2hhbmdlIGNvbG9yIGVsc2UgZG8gbm90aGluZ1xyXG4gICAgICAgICAgICAgKiBhZnRlciBwYXJzaW5nIG51bWJlciBsZWFkaW5nIDAgaXMgcmVtb3ZlZCxcclxuICAgICAgICAgICAgICogY29tcGFyZSBsZW5ndGggYW5kIGFkZCBsZWFkaW5nIDAgYmVmb3JlIGNvbXBhcmluZyB0d28gdmFsdWVzXHJcbiAgICAgICAgICAgICAqL1xyXG4gICAgICAgICAgICBpZiAoaGV4U3RyLnBhZFN0YXJ0KHZhbHVlLmxlbmd0aCwgJzAnKSA9PT0gdmFsdWUgJiYgdGhpcy52YWx1ZSAhPT0gdmFsdWUpIHtcclxuICAgICAgICAgICAgICAgIGNvbnN0IG5ld0NvbG9yID0gbmV3IENvbG9yKGAjJHt2YWx1ZX1gKTtcclxuICAgICAgICAgICAgICAgIHRoaXMuY29sb3Iuc2V0KG5ld0NvbG9yKTtcclxuICAgICAgICAgICAgfVxyXG4gICAgICAgIH1cclxuICAgIH1cclxufVxyXG4iLCI8ZGl2IGNsYXNzPVwiY29sdW1uXCI+XHJcbiAgICA8aW5wdXQgI2VsUmVmIHR5cGU9XCJ0ZXh0XCIgW3ZhbHVlXT1cInZhbHVlXCIgKGtleXVwKT1cIm9uSW5wdXRDaGFuZ2UoJGV2ZW50LCBlbFJlZi52YWx1ZSlcIiAvPlxyXG4gICAgQGlmIChsYWJlbFZpc2libGUoKSkge1xyXG4gICAgICAgIDxzcGFuPkhFWDwvc3Bhbj5cclxuICAgIH1cclxuPC9kaXY+Il19 +diff --git a/node_modules/@iplab/ngx-color-picker/esm2022/lib/components/parts/inputs/hsla-input/hsla-input.component.mjs b/node_modules/@iplab/ngx-color-picker/esm2022/lib/components/parts/inputs/hsla-input/hsla-input.component.mjs +index 2454b27..dd007ce 100644 +--- a/node_modules/@iplab/ngx-color-picker/esm2022/lib/components/parts/inputs/hsla-input/hsla-input.component.mjs ++++ b/node_modules/@iplab/ngx-color-picker/esm2022/lib/components/parts/inputs/hsla-input/hsla-input.component.mjs +@@ -21,10 +21,10 @@ export class HslaComponent { + this.color.set(newColor); + } + static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.0", ngImport: i0, type: HslaComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } +- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.0.0", type: HslaComponent, isStandalone: true, selector: "hsla-input-component", inputs: { color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: true, transformFunction: null }, labelVisible: { classPropertyName: "labelVisible", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, isAlphaVisible: { classPropertyName: "isAlphaVisible", publicName: "alpha", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { color: "colorChange" }, ngImport: i0, template: "
    \r\n \r\n @if (labelVisible()) {\r\n H\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n S\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n L\r\n }\r\n
    \r\n@if (isAlphaVisible()) {\r\n
    \r\n \r\n @if (labelVisible()) {\r\n A\r\n }\r\n
    \r\n}", styles: [":host,:host ::ng-deep *{padding:0;margin:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}\n", ":host{display:table;width:100%;text-align:center;color:#b4b4b4;font-size:11px}.column{display:table-cell;padding:0 2px}input{width:100%;border:1px solid rgb(218,218,218);color:#272727;text-align:center;font-size:12px;-webkit-appearance:none;border-radius:0;margin:0 0 6px;height:26px;outline:none}\n", ""], dependencies: [{ kind: "directive", type: ColorPickerInputDirective, selector: "[inputChange]", inputs: ["min", "max"], outputs: ["inputChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } ++ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.0.0", type: HslaComponent, isStandalone: true, selector: "hsla-input-component", inputs: { color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: true, transformFunction: null }, labelVisible: { classPropertyName: "labelVisible", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, isAlphaVisible: { classPropertyName: "isAlphaVisible", publicName: "alpha", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { color: "colorChange" }, ngImport: i0, template: "
    \r\n \r\n @if (labelVisible()) {\r\n H\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n S\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n L\r\n }\r\n
    \r\n@if (isAlphaVisible()) {\r\n
    \r\n \r\n @if (labelVisible()) {\r\n A\r\n }\r\n
    \r\n}", styles: [":host,:host ::ng-deep *{padding:0;margin:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}\n", ":host{display:table;width:100%;text-align:center;color:#b4b4b4;font-size:11px}.column{display:table-cell;padding:0 2px}input{width:100%;border:1px solid rgb(218,218,218);color:#272727;text-align:center;font-size:12px;-webkit-appearance:none;border-radius:0;margin:0 0 6px;height:26px;outline:none}\n", ""], dependencies: [{ kind: "directive", type: ColorPickerInputDirective, selector: "[inputChange]", inputs: ["min", "max"], outputs: ["inputChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } + } + i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.0", ngImport: i0, type: HslaComponent, decorators: [{ + type: Component, +- args: [{ selector: `hsla-input-component`, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [ColorPickerInputDirective], template: "
    \r\n \r\n @if (labelVisible()) {\r\n H\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n S\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n L\r\n }\r\n
    \r\n@if (isAlphaVisible()) {\r\n
    \r\n \r\n @if (labelVisible()) {\r\n A\r\n }\r\n
    \r\n}", styles: [":host,:host ::ng-deep *{padding:0;margin:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}\n", ":host{display:table;width:100%;text-align:center;color:#b4b4b4;font-size:11px}.column{display:table-cell;padding:0 2px}input{width:100%;border:1px solid rgb(218,218,218);color:#272727;text-align:center;font-size:12px;-webkit-appearance:none;border-radius:0;margin:0 0 6px;height:26px;outline:none}\n"] }] ++ args: [{ selector: `hsla-input-component`, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [ColorPickerInputDirective], template: "
    \r\n \r\n @if (labelVisible()) {\r\n H\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n S\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n L\r\n }\r\n
    \r\n@if (isAlphaVisible()) {\r\n
    \r\n \r\n @if (labelVisible()) {\r\n A\r\n }\r\n
    \r\n}", styles: [":host,:host ::ng-deep *{padding:0;margin:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}\n", ":host{display:table;width:100%;text-align:center;color:#b4b4b4;font-size:11px}.column{display:table-cell;padding:0 2px}input{width:100%;border:1px solid rgb(218,218,218);color:#272727;text-align:center;font-size:12px;-webkit-appearance:none;border-radius:0;margin:0 0 6px;height:26px;outline:none}\n"] }] + }] }); + //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaHNsYS1pbnB1dC5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi9wcm9qZWN0cy9pcGxhYi9uZ3gtY29sb3ItcGlja2VyL3NyYy9saWIvY29tcG9uZW50cy9wYXJ0cy9pbnB1dHMvaHNsYS1pbnB1dC9oc2xhLWlucHV0LmNvbXBvbmVudC50cyIsIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3Byb2plY3RzL2lwbGFiL25neC1jb2xvci1waWNrZXIvc3JjL2xpYi9jb21wb25lbnRzL3BhcnRzL2lucHV0cy9oc2xhLWlucHV0L2hzbGEtaW5wdXQuY29tcG9uZW50Lmh0bWwiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFNBQVMsRUFBRSx1QkFBdUIsRUFBRSxnQkFBZ0IsRUFBZSxLQUFLLEVBQUUsS0FBSyxFQUFlLE1BQU0sZUFBZSxDQUFDO0FBQzdILE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSxtQ0FBbUMsQ0FBQztBQUMxRCxPQUFPLEVBQUUseUJBQXlCLEVBQUUsTUFBTSxxREFBcUQsQ0FBQzs7QUFlaEcsTUFBTSxPQUFPLGFBQWE7SUFaMUI7UUFjVyxVQUFLLEdBQXVCLEtBQUssQ0FBQyxRQUFRLEVBQVMsQ0FBQztRQUVwRCxpQkFBWSxHQUF5QixLQUFLLENBQW1CLEtBQUssRUFBRSxFQUFFLEtBQUssRUFBRSxPQUFPLEVBQUUsU0FBUyxFQUFFLGdCQUFnQixFQUFFLENBQUMsQ0FBQztRQUVySCxtQkFBYyxHQUF5QixLQUFLLENBQW1CLElBQUksRUFBRSxFQUFFLEtBQUssRUFBRSxPQUFPLEVBQUUsU0FBUyxFQUFFLGdCQUFnQixFQUFFLENBQUMsQ0FBQztLQWdCaEk7SUFkRyxJQUFXLEtBQUs7UUFDWixPQUFPLElBQUksQ0FBQyxLQUFLLEVBQUUsRUFBRSxPQUFPLEVBQUUsQ0FBQztJQUNuQyxDQUFDO0lBRU0sYUFBYSxDQUFDLFFBQWdCLEVBQUUsS0FBNEI7UUFDL0QsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQztRQUN6QixNQUFNLEdBQUcsR0FBRyxLQUFLLEtBQUssR0FBRyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUM7UUFDakQsTUFBTSxVQUFVLEdBQUcsS0FBSyxLQUFLLEdBQUcsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsVUFBVSxDQUFDO1FBQy9ELE1BQU0sU0FBUyxHQUFHLEtBQUssS0FBSyxHQUFHLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLFNBQVMsQ0FBQztRQUM3RCxNQUFNLEtBQUssR0FBRyxLQUFLLEtBQUssR0FBRyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUM7UUFFckQsTUFBTSxRQUFRLEdBQUcsSUFBSSxLQUFLLEVBQUUsQ0FBQyxPQUFPLENBQUMsR0FBRyxFQUFFLFVBQVUsRUFBRSxTQUFTLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDeEUsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDN0IsQ0FBQzs4R0FyQlEsYUFBYTtrR0FBYixhQUFhLHNnQkNqQjFCLGlsQ0F5QkMseWVEVmEseUJBQXlCOzsyRkFFMUIsYUFBYTtrQkFaekIsU0FBUzsrQkFDSSxzQkFBc0IsbUJBT2YsdUJBQXVCLENBQUMsTUFBTSxjQUNuQyxJQUFJLFdBQ1AsQ0FBQyx5QkFBeUIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IENvbXBvbmVudCwgQ2hhbmdlRGV0ZWN0aW9uU3RyYXRlZ3ksIGJvb2xlYW5BdHRyaWJ1dGUsIElucHV0U2lnbmFsLCBpbnB1dCwgbW9kZWwsIE1vZGVsU2lnbmFsIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XHJcbmltcG9ydCB7IENvbG9yIH0gZnJvbSAnLi8uLi8uLi8uLi8uLi9oZWxwZXJzL2NvbG9yLmNsYXNzJztcclxuaW1wb3J0IHsgQ29sb3JQaWNrZXJJbnB1dERpcmVjdGl2ZSB9IGZyb20gJy4uLy4uLy4uLy4uL2RpcmVjdGl2ZXMvY29sb3ItcGlja2VyLWlucHV0LmRpcmVjdGl2ZSc7XHJcblxyXG5cclxuQENvbXBvbmVudCh7XHJcbiAgICBzZWxlY3RvcjogYGhzbGEtaW5wdXQtY29tcG9uZW50YCxcclxuICAgIHRlbXBsYXRlVXJsOiBgLi9oc2xhLWlucHV0LmNvbXBvbmVudC5odG1sYCxcclxuICAgIHN0eWxlVXJsczogW1xyXG4gICAgICAgIGAuLy4uLy4uL2Jhc2Uuc3R5bGUuc2Nzc2AsXHJcbiAgICAgICAgYC4vLi4vaW5wdXQuY29tcG9uZW50LnNjc3NgLFxyXG4gICAgICAgIGAuL2hzbGEtaW5wdXQuY29tcG9uZW50LnNjc3NgXHJcbiAgICBdLFxyXG4gICAgY2hhbmdlRGV0ZWN0aW9uOiBDaGFuZ2VEZXRlY3Rpb25TdHJhdGVneS5PblB1c2gsXHJcbiAgICBzdGFuZGFsb25lOiB0cnVlLFxyXG4gICAgaW1wb3J0czogW0NvbG9yUGlja2VySW5wdXREaXJlY3RpdmVdXHJcbn0pXHJcbmV4cG9ydCBjbGFzcyBIc2xhQ29tcG9uZW50IHtcclxuXHJcbiAgICBwdWJsaWMgY29sb3I6IE1vZGVsU2lnbmFsPENvbG9yPiA9IG1vZGVsLnJlcXVpcmVkPENvbG9yPigpO1xyXG5cclxuICAgIHB1YmxpYyBsYWJlbFZpc2libGU6IElucHV0U2lnbmFsPGJvb2xlYW4+ID0gaW5wdXQ8Ym9vbGVhbiwgYm9vbGVhbj4oZmFsc2UsIHsgYWxpYXM6ICdsYWJlbCcsIHRyYW5zZm9ybTogYm9vbGVhbkF0dHJpYnV0ZSB9KTtcclxuXHJcbiAgICBwdWJsaWMgaXNBbHBoYVZpc2libGU6IElucHV0U2lnbmFsPGJvb2xlYW4+ID0gaW5wdXQ8Ym9vbGVhbiwgYm9vbGVhbj4odHJ1ZSwgeyBhbGlhczogJ2FscGhhJywgdHJhbnNmb3JtOiBib29sZWFuQXR0cmlidXRlIH0pO1xyXG5cclxuICAgIHB1YmxpYyBnZXQgdmFsdWUoKSB7XHJcbiAgICAgICAgcmV0dXJuIHRoaXMuY29sb3IoKT8uZ2V0SHNsYSgpO1xyXG4gICAgfVxyXG5cclxuICAgIHB1YmxpYyBvbklucHV0Q2hhbmdlKG5ld1ZhbHVlOiBudW1iZXIsIGNvbG9yOiAnSCcgfCAnUycgfCAnTCcgfCAnQScpIHtcclxuICAgICAgICBjb25zdCB2YWx1ZSA9IHRoaXMudmFsdWU7XHJcbiAgICAgICAgY29uc3QgaHVlID0gY29sb3IgPT09ICdIJyA/IG5ld1ZhbHVlIDogdmFsdWUuaHVlO1xyXG4gICAgICAgIGNvbnN0IHNhdHVyYXRpb24gPSBjb2xvciA9PT0gJ1MnID8gbmV3VmFsdWUgOiB2YWx1ZS5zYXR1cmF0aW9uO1xyXG4gICAgICAgIGNvbnN0IGxpZ2h0bmVzcyA9IGNvbG9yID09PSAnTCcgPyBuZXdWYWx1ZSA6IHZhbHVlLmxpZ2h0bmVzcztcclxuICAgICAgICBjb25zdCBhbHBoYSA9IGNvbG9yID09PSAnQScgPyBuZXdWYWx1ZSA6IHZhbHVlLmFscGhhO1xyXG5cclxuICAgICAgICBjb25zdCBuZXdDb2xvciA9IG5ldyBDb2xvcigpLnNldEhzbGEoaHVlLCBzYXR1cmF0aW9uLCBsaWdodG5lc3MsIGFscGhhKTtcclxuICAgICAgICB0aGlzLmNvbG9yLnNldChuZXdDb2xvcik7XHJcbiAgICB9XHJcbn1cclxuIiwiPGRpdiBjbGFzcz1cImNvbHVtblwiPlxyXG4gICAgPGlucHV0IHR5cGU9XCJ0ZXh0XCIgcGF0dGVybj1cIlswLTldKlwiIG1pbj1cIjBcIiBtYXg9XCIzNjBcIiBbdmFsdWVdPVwidmFsdWU/LmdldEh1ZSgpLnRvU3RyaW5nKClcIiAoaW5wdXRDaGFuZ2UpPVwib25JbnB1dENoYW5nZSgkZXZlbnQsICdIJylcIiAvPlxyXG4gICAgQGlmIChsYWJlbFZpc2libGUoKSkge1xyXG4gICAgICAgIDxzcGFuPkg8L3NwYW4+XHJcbiAgICB9XHJcbjwvZGl2PlxyXG48ZGl2IGNsYXNzPVwiY29sdW1uXCI+XHJcbiAgICA8aW5wdXQgdHlwZT1cInRleHRcIiBwYXR0ZXJuPVwiWzAtOV0qXCIgbWluPVwiMFwiIG1heD1cIjEwMFwiIFt2YWx1ZV09XCJ2YWx1ZT8uZ2V0U2F0dXJhdGlvbigpICsgJyUnXCIgKGlucHV0Q2hhbmdlKT1cIm9uSW5wdXRDaGFuZ2UoJGV2ZW50LCAnUycpXCIgLz5cclxuICAgIEBpZiAobGFiZWxWaXNpYmxlKCkpIHtcclxuICAgICAgICA8c3Bhbj5TPC9zcGFuPlxyXG4gICAgfVxyXG48L2Rpdj5cclxuPGRpdiBjbGFzcz1cImNvbHVtblwiPlxyXG4gICAgPGlucHV0IHR5cGU9XCJ0ZXh0XCIgcGF0dGVybj1cIlswLTldKlwiIG1pbj1cIjBcIiBtYXg9XCIxMDBcIiBbdmFsdWVdPVwidmFsdWU/LmdldExpZ2h0bmVzcygpICsgJyUnXCIgKGlucHV0Q2hhbmdlKT1cIm9uSW5wdXRDaGFuZ2UoJGV2ZW50LCAnTCcpXCIgLz5cclxuICAgIEBpZiAobGFiZWxWaXNpYmxlKCkpIHtcclxuICAgICAgICA8c3Bhbj5MPC9zcGFuPlxyXG4gICAgfVxyXG48L2Rpdj5cclxuQGlmIChpc0FscGhhVmlzaWJsZSgpKSB7XHJcbiAgICA8ZGl2IGNsYXNzPVwiY29sdW1uXCI+XHJcbiAgICAgICAgPGlucHV0IHR5cGU9XCJ0ZXh0XCIgcGF0dGVybj1cIlswLTldKyhbXFwuLF1bMC05XXsxLDJ9KT9cIiBtaW49XCIwXCIgbWF4PVwiMVwiIFt2YWx1ZV09XCJ2YWx1ZT8uZ2V0QWxwaGEoKS50b1N0cmluZygpXCIgKGlucHV0Q2hhbmdlKT1cIm9uSW5wdXRDaGFuZ2UoJGV2ZW50LCAnQScpXCIgLz5cclxuICAgICAgICBAaWYgKGxhYmVsVmlzaWJsZSgpKSB7XHJcbiAgICAgICAgICAgIDxzcGFuPkE8L3NwYW4+XHJcbiAgICAgICAgfVxyXG4gICAgPC9kaXY+XHJcbn0iXX0= +diff --git a/node_modules/@iplab/ngx-color-picker/esm2022/lib/components/parts/inputs/rgba-input/rgba-input.component.mjs b/node_modules/@iplab/ngx-color-picker/esm2022/lib/components/parts/inputs/rgba-input/rgba-input.component.mjs +index 6d976e8..8989aad 100644 +--- a/node_modules/@iplab/ngx-color-picker/esm2022/lib/components/parts/inputs/rgba-input/rgba-input.component.mjs ++++ b/node_modules/@iplab/ngx-color-picker/esm2022/lib/components/parts/inputs/rgba-input/rgba-input.component.mjs +@@ -21,10 +21,10 @@ export class RgbaComponent { + this.color.set(newColor); + } + static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.0", ngImport: i0, type: RgbaComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } +- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.0.0", type: RgbaComponent, isStandalone: true, selector: "rgba-input-component", inputs: { color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: true, transformFunction: null }, labelVisible: { classPropertyName: "labelVisible", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, isAlphaVisible: { classPropertyName: "isAlphaVisible", publicName: "alpha", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { color: "colorChange" }, ngImport: i0, template: "
    \r\n \r\n @if (labelVisible()) {\r\n R\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n G\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n B\r\n }\r\n
    \r\n@if (isAlphaVisible()) {\r\n
    \r\n \r\n @if (labelVisible()) {\r\n A\r\n }\r\n
    \r\n}", styles: [":host,:host ::ng-deep *{padding:0;margin:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}\n", ":host{display:table;width:100%;text-align:center;color:#b4b4b4;font-size:11px}.column{display:table-cell;padding:0 2px}input{width:100%;border:1px solid rgb(218,218,218);color:#272727;text-align:center;font-size:12px;-webkit-appearance:none;border-radius:0;margin:0 0 6px;height:26px;outline:none}\n", ""], dependencies: [{ kind: "directive", type: ColorPickerInputDirective, selector: "[inputChange]", inputs: ["min", "max"], outputs: ["inputChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } ++ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.0.0", type: RgbaComponent, isStandalone: true, selector: "rgba-input-component", inputs: { color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: true, transformFunction: null }, labelVisible: { classPropertyName: "labelVisible", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, isAlphaVisible: { classPropertyName: "isAlphaVisible", publicName: "alpha", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { color: "colorChange" }, ngImport: i0, template: "
    \r\n \r\n @if (labelVisible()) {\r\n R\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n G\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n B\r\n }\r\n
    \r\n@if (isAlphaVisible()) {\r\n
    \r\n \r\n @if (labelVisible()) {\r\n A\r\n }\r\n
    \r\n}", styles: [":host,:host ::ng-deep *{padding:0;margin:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}\n", ":host{display:table;width:100%;text-align:center;color:#b4b4b4;font-size:11px}.column{display:table-cell;padding:0 2px}input{width:100%;border:1px solid rgb(218,218,218);color:#272727;text-align:center;font-size:12px;-webkit-appearance:none;border-radius:0;margin:0 0 6px;height:26px;outline:none}\n", ""], dependencies: [{ kind: "directive", type: ColorPickerInputDirective, selector: "[inputChange]", inputs: ["min", "max"], outputs: ["inputChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } + } + i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.0", ngImport: i0, type: RgbaComponent, decorators: [{ + type: Component, +- args: [{ selector: `rgba-input-component`, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [ColorPickerInputDirective], template: "
    \r\n \r\n @if (labelVisible()) {\r\n R\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n G\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n B\r\n }\r\n
    \r\n@if (isAlphaVisible()) {\r\n
    \r\n \r\n @if (labelVisible()) {\r\n A\r\n }\r\n
    \r\n}", styles: [":host,:host ::ng-deep *{padding:0;margin:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}\n", ":host{display:table;width:100%;text-align:center;color:#b4b4b4;font-size:11px}.column{display:table-cell;padding:0 2px}input{width:100%;border:1px solid rgb(218,218,218);color:#272727;text-align:center;font-size:12px;-webkit-appearance:none;border-radius:0;margin:0 0 6px;height:26px;outline:none}\n"] }] ++ args: [{ selector: `rgba-input-component`, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [ColorPickerInputDirective], template: "
    \r\n \r\n @if (labelVisible()) {\r\n R\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n G\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n B\r\n }\r\n
    \r\n@if (isAlphaVisible()) {\r\n
    \r\n \r\n @if (labelVisible()) {\r\n A\r\n }\r\n
    \r\n}", styles: [":host,:host ::ng-deep *{padding:0;margin:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}\n", ":host{display:table;width:100%;text-align:center;color:#b4b4b4;font-size:11px}.column{display:table-cell;padding:0 2px}input{width:100%;border:1px solid rgb(218,218,218);color:#272727;text-align:center;font-size:12px;-webkit-appearance:none;border-radius:0;margin:0 0 6px;height:26px;outline:none}\n"] }] + }] }); + //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmdiYS1pbnB1dC5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi9wcm9qZWN0cy9pcGxhYi9uZ3gtY29sb3ItcGlja2VyL3NyYy9saWIvY29tcG9uZW50cy9wYXJ0cy9pbnB1dHMvcmdiYS1pbnB1dC9yZ2JhLWlucHV0LmNvbXBvbmVudC50cyIsIi4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL3Byb2plY3RzL2lwbGFiL25neC1jb2xvci1waWNrZXIvc3JjL2xpYi9jb21wb25lbnRzL3BhcnRzL2lucHV0cy9yZ2JhLWlucHV0L3JnYmEtaW5wdXQuY29tcG9uZW50Lmh0bWwiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFNBQVMsRUFBRSx1QkFBdUIsRUFBRSxnQkFBZ0IsRUFBZSxLQUFLLEVBQUUsS0FBSyxFQUFlLE1BQU0sZUFBZSxDQUFDO0FBQzdILE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSxtQ0FBbUMsQ0FBQztBQUMxRCxPQUFPLEVBQUUseUJBQXlCLEVBQUUsTUFBTSxxREFBcUQsQ0FBQzs7QUFlaEcsTUFBTSxPQUFPLGFBQWE7SUFaMUI7UUFjVyxVQUFLLEdBQXVCLEtBQUssQ0FBQyxRQUFRLEVBQVMsQ0FBQztRQUVwRCxpQkFBWSxHQUF5QixLQUFLLENBQW1CLEtBQUssRUFBRSxFQUFFLEtBQUssRUFBRSxPQUFPLEVBQUUsU0FBUyxFQUFFLGdCQUFnQixFQUFFLENBQUMsQ0FBQztRQUVySCxtQkFBYyxHQUF5QixLQUFLLENBQW1CLElBQUksRUFBRSxFQUFFLEtBQUssRUFBRSxPQUFPLEVBQUUsU0FBUyxFQUFFLGdCQUFnQixFQUFFLENBQUMsQ0FBQztLQWdCaEk7SUFkRyxJQUFXLEtBQUs7UUFDWixPQUFPLElBQUksQ0FBQyxLQUFLLEVBQUUsRUFBRSxPQUFPLEVBQUUsQ0FBQztJQUNuQyxDQUFDO0lBRU0sYUFBYSxDQUFDLFFBQWdCLEVBQUUsS0FBNEI7UUFDL0QsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQztRQUN6QixNQUFNLEdBQUcsR0FBRyxLQUFLLEtBQUssR0FBRyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUM7UUFDakQsTUFBTSxLQUFLLEdBQUcsS0FBSyxLQUFLLEdBQUcsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDO1FBQ3JELE1BQU0sSUFBSSxHQUFHLEtBQUssS0FBSyxHQUFHLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQztRQUNuRCxNQUFNLEtBQUssR0FBRyxLQUFLLEtBQUssR0FBRyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUM7UUFFckQsTUFBTSxRQUFRLEdBQUcsSUFBSSxLQUFLLEVBQUUsQ0FBQyxPQUFPLENBQUMsR0FBRyxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDOUQsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDN0IsQ0FBQzs4R0FyQlEsYUFBYTtrR0FBYixhQUFhLHNnQkNqQjFCLGlsQ0F5QkMseWVEVmEseUJBQXlCOzsyRkFFMUIsYUFBYTtrQkFaekIsU0FBUzsrQkFDSSxzQkFBc0IsbUJBT2YsdUJBQXVCLENBQUMsTUFBTSxjQUNuQyxJQUFJLFdBQ1AsQ0FBQyx5QkFBeUIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IENvbXBvbmVudCwgQ2hhbmdlRGV0ZWN0aW9uU3RyYXRlZ3ksIGJvb2xlYW5BdHRyaWJ1dGUsIElucHV0U2lnbmFsLCBpbnB1dCwgbW9kZWwsIE1vZGVsU2lnbmFsIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XHJcbmltcG9ydCB7IENvbG9yIH0gZnJvbSAnLi8uLi8uLi8uLi8uLi9oZWxwZXJzL2NvbG9yLmNsYXNzJztcclxuaW1wb3J0IHsgQ29sb3JQaWNrZXJJbnB1dERpcmVjdGl2ZSB9IGZyb20gJy4uLy4uLy4uLy4uL2RpcmVjdGl2ZXMvY29sb3ItcGlja2VyLWlucHV0LmRpcmVjdGl2ZSc7XHJcblxyXG5cclxuQENvbXBvbmVudCh7XHJcbiAgICBzZWxlY3RvcjogYHJnYmEtaW5wdXQtY29tcG9uZW50YCxcclxuICAgIHRlbXBsYXRlVXJsOiBgLi9yZ2JhLWlucHV0LmNvbXBvbmVudC5odG1sYCxcclxuICAgIHN0eWxlVXJsczogW1xyXG4gICAgICAgIGAuLy4uLy4uL2Jhc2Uuc3R5bGUuc2Nzc2AsXHJcbiAgICAgICAgYC4vLi4vaW5wdXQuY29tcG9uZW50LnNjc3NgLFxyXG4gICAgICAgIGAuL3JnYmEtaW5wdXQuY29tcG9uZW50LnNjc3NgXHJcbiAgICBdLFxyXG4gICAgY2hhbmdlRGV0ZWN0aW9uOiBDaGFuZ2VEZXRlY3Rpb25TdHJhdGVneS5PblB1c2gsXHJcbiAgICBzdGFuZGFsb25lOiB0cnVlLFxyXG4gICAgaW1wb3J0czogW0NvbG9yUGlja2VySW5wdXREaXJlY3RpdmVdXHJcbn0pXHJcbmV4cG9ydCBjbGFzcyBSZ2JhQ29tcG9uZW50IHtcclxuXHJcbiAgICBwdWJsaWMgY29sb3I6IE1vZGVsU2lnbmFsPENvbG9yPiA9IG1vZGVsLnJlcXVpcmVkPENvbG9yPigpO1xyXG5cclxuICAgIHB1YmxpYyBsYWJlbFZpc2libGU6IElucHV0U2lnbmFsPGJvb2xlYW4+ID0gaW5wdXQ8Ym9vbGVhbiwgYm9vbGVhbj4oZmFsc2UsIHsgYWxpYXM6ICdsYWJlbCcsIHRyYW5zZm9ybTogYm9vbGVhbkF0dHJpYnV0ZSB9KTtcclxuXHJcbiAgICBwdWJsaWMgaXNBbHBoYVZpc2libGU6IElucHV0U2lnbmFsPGJvb2xlYW4+ID0gaW5wdXQ8Ym9vbGVhbiwgYm9vbGVhbj4odHJ1ZSwgeyBhbGlhczogJ2FscGhhJywgdHJhbnNmb3JtOiBib29sZWFuQXR0cmlidXRlIH0pO1xyXG5cclxuICAgIHB1YmxpYyBnZXQgdmFsdWUoKSB7XHJcbiAgICAgICAgcmV0dXJuIHRoaXMuY29sb3IoKT8uZ2V0UmdiYSgpO1xyXG4gICAgfVxyXG5cclxuICAgIHB1YmxpYyBvbklucHV0Q2hhbmdlKG5ld1ZhbHVlOiBudW1iZXIsIGNvbG9yOiAnUicgfCAnRycgfCAnQicgfCAnQScpIHtcclxuICAgICAgICBjb25zdCB2YWx1ZSA9IHRoaXMudmFsdWU7XHJcbiAgICAgICAgY29uc3QgcmVkID0gY29sb3IgPT09ICdSJyA/IG5ld1ZhbHVlIDogdmFsdWUucmVkO1xyXG4gICAgICAgIGNvbnN0IGdyZWVuID0gY29sb3IgPT09ICdHJyA/IG5ld1ZhbHVlIDogdmFsdWUuZ3JlZW47XHJcbiAgICAgICAgY29uc3QgYmx1ZSA9IGNvbG9yID09PSAnQicgPyBuZXdWYWx1ZSA6IHZhbHVlLmJsdWU7XHJcbiAgICAgICAgY29uc3QgYWxwaGEgPSBjb2xvciA9PT0gJ0EnID8gbmV3VmFsdWUgOiB2YWx1ZS5hbHBoYTtcclxuXHJcbiAgICAgICAgY29uc3QgbmV3Q29sb3IgPSBuZXcgQ29sb3IoKS5zZXRSZ2JhKHJlZCwgZ3JlZW4sIGJsdWUsIGFscGhhKTtcclxuICAgICAgICB0aGlzLmNvbG9yLnNldChuZXdDb2xvcik7XHJcbiAgICB9XHJcbn1cclxuIiwiPGRpdiBjbGFzcz1cImNvbHVtblwiPlxyXG4gICAgPGlucHV0IHR5cGU9XCJ0ZXh0XCIgcGF0dGVybj1cIlswLTldKlwiIG1pbj1cIjBcIiBtYXg9XCIyNTVcIiBbdmFsdWVdPVwidmFsdWU/LmdldFJlZCgpLnRvU3RyaW5nKClcIiAoaW5wdXRDaGFuZ2UpPVwib25JbnB1dENoYW5nZSgkZXZlbnQsICdSJylcIiAvPlxyXG4gICAgQGlmIChsYWJlbFZpc2libGUoKSkge1xyXG4gICAgICAgIDxzcGFuPlI8L3NwYW4+XHJcbiAgICB9XHJcbjwvZGl2PlxyXG48ZGl2IGNsYXNzPVwiY29sdW1uXCI+XHJcbiAgICA8aW5wdXQgdHlwZT1cInRleHRcIiBwYXR0ZXJuPVwiWzAtOV0qXCIgbWluPVwiMFwiIG1heD1cIjI1NVwiIFt2YWx1ZV09XCJ2YWx1ZT8uZ2V0R3JlZW4oKS50b1N0cmluZygpXCIgKGlucHV0Q2hhbmdlKT1cIm9uSW5wdXRDaGFuZ2UoJGV2ZW50LCAnRycpXCIgLz5cclxuICAgIEBpZiAobGFiZWxWaXNpYmxlKCkpIHtcclxuICAgICAgICA8c3Bhbj5HPC9zcGFuPlxyXG4gICAgfVxyXG48L2Rpdj5cclxuPGRpdiBjbGFzcz1cImNvbHVtblwiPlxyXG4gICAgPGlucHV0IHR5cGU9XCJ0ZXh0XCIgcGF0dGVybj1cIlswLTldKlwiIG1pbj1cIjBcIiBtYXg9XCIyNTVcIiBbdmFsdWVdPVwidmFsdWU/LmdldEJsdWUoKS50b1N0cmluZygpXCIgKGlucHV0Q2hhbmdlKT1cIm9uSW5wdXRDaGFuZ2UoJGV2ZW50LCAnQicpXCIgLz5cclxuICAgIEBpZiAobGFiZWxWaXNpYmxlKCkpIHtcclxuICAgICAgICA8c3Bhbj5CPC9zcGFuPlxyXG4gICAgfVxyXG48L2Rpdj5cclxuQGlmIChpc0FscGhhVmlzaWJsZSgpKSB7XHJcbiAgICA8ZGl2IGNsYXNzPVwiY29sdW1uXCI+XHJcbiAgICAgICAgPGlucHV0IHR5cGU9XCJ0ZXh0XCIgcGF0dGVybj1cIlswLTldKyhbXFwuLF1bMC05XXsxLDJ9KT9cIiBtaW49XCIwXCIgbWF4PVwiMVwiIFt2YWx1ZV09XCJ2YWx1ZT8uZ2V0QWxwaGEoKS50b1N0cmluZygpXCIgKGlucHV0Q2hhbmdlKT1cIm9uSW5wdXRDaGFuZ2UoJGV2ZW50LCAnQScpXCIgLz5cclxuICAgICAgICBAaWYgKGxhYmVsVmlzaWJsZSgpKSB7XHJcbiAgICAgICAgICAgIDxzcGFuPkE8L3NwYW4+XHJcbiAgICAgICAgfVxyXG4gICAgPC9kaXY+XHJcbn0iXX0= +diff --git a/node_modules/@iplab/ngx-color-picker/fesm2022/iplab-ngx-color-picker.mjs b/node_modules/@iplab/ngx-color-picker/fesm2022/iplab-ngx-color-picker.mjs +index a3b270c..40bea7f 100644 +--- a/node_modules/@iplab/ngx-color-picker/fesm2022/iplab-ngx-color-picker.mjs ++++ b/node_modules/@iplab/ngx-color-picker/fesm2022/iplab-ngx-color-picker.mjs +@@ -1123,11 +1123,11 @@ class RgbaComponent { + this.color.set(newColor); + } + static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.0", ngImport: i0, type: RgbaComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } +- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.0.0", type: RgbaComponent, isStandalone: true, selector: "rgba-input-component", inputs: { color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: true, transformFunction: null }, labelVisible: { classPropertyName: "labelVisible", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, isAlphaVisible: { classPropertyName: "isAlphaVisible", publicName: "alpha", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { color: "colorChange" }, ngImport: i0, template: "
    \r\n \r\n @if (labelVisible()) {\r\n R\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n G\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n B\r\n }\r\n
    \r\n@if (isAlphaVisible()) {\r\n
    \r\n \r\n @if (labelVisible()) {\r\n A\r\n }\r\n
    \r\n}", styles: [":host,:host ::ng-deep *{padding:0;margin:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}\n", ":host{display:table;width:100%;text-align:center;color:#b4b4b4;font-size:11px}.column{display:table-cell;padding:0 2px}input{width:100%;border:1px solid rgb(218,218,218);color:#272727;text-align:center;font-size:12px;-webkit-appearance:none;border-radius:0;margin:0 0 6px;height:26px;outline:none}\n", ""], dependencies: [{ kind: "directive", type: ColorPickerInputDirective, selector: "[inputChange]", inputs: ["min", "max"], outputs: ["inputChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } ++ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.0.0", type: RgbaComponent, isStandalone: true, selector: "rgba-input-component", inputs: { color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: true, transformFunction: null }, labelVisible: { classPropertyName: "labelVisible", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, isAlphaVisible: { classPropertyName: "isAlphaVisible", publicName: "alpha", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { color: "colorChange" }, ngImport: i0, template: "
    \r\n \r\n @if (labelVisible()) {\r\n R\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n G\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n B\r\n }\r\n
    \r\n@if (isAlphaVisible()) {\r\n
    \r\n \r\n @if (labelVisible()) {\r\n A\r\n }\r\n
    \r\n}", styles: [":host,:host ::ng-deep *{padding:0;margin:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}\n", ":host{display:table;width:100%;text-align:center;color:#b4b4b4;font-size:11px}.column{display:table-cell;padding:0 2px}input{width:100%;border:1px solid rgb(218,218,218);color:#272727;text-align:center;font-size:12px;-webkit-appearance:none;border-radius:0;margin:0 0 6px;height:26px;outline:none}\n", ""], dependencies: [{ kind: "directive", type: ColorPickerInputDirective, selector: "[inputChange]", inputs: ["min", "max"], outputs: ["inputChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } + } + i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.0", ngImport: i0, type: RgbaComponent, decorators: [{ + type: Component, +- args: [{ selector: `rgba-input-component`, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [ColorPickerInputDirective], template: "
    \r\n \r\n @if (labelVisible()) {\r\n R\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n G\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n B\r\n }\r\n
    \r\n@if (isAlphaVisible()) {\r\n
    \r\n \r\n @if (labelVisible()) {\r\n A\r\n }\r\n
    \r\n}", styles: [":host,:host ::ng-deep *{padding:0;margin:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}\n", ":host{display:table;width:100%;text-align:center;color:#b4b4b4;font-size:11px}.column{display:table-cell;padding:0 2px}input{width:100%;border:1px solid rgb(218,218,218);color:#272727;text-align:center;font-size:12px;-webkit-appearance:none;border-radius:0;margin:0 0 6px;height:26px;outline:none}\n"] }] ++ args: [{ selector: `rgba-input-component`, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [ColorPickerInputDirective], template: "
    \r\n \r\n @if (labelVisible()) {\r\n R\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n G\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n B\r\n }\r\n
    \r\n@if (isAlphaVisible()) {\r\n
    \r\n \r\n @if (labelVisible()) {\r\n A\r\n }\r\n
    \r\n}", styles: [":host,:host ::ng-deep *{padding:0;margin:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}\n", ":host{display:table;width:100%;text-align:center;color:#b4b4b4;font-size:11px}.column{display:table-cell;padding:0 2px}input{width:100%;border:1px solid rgb(218,218,218);color:#272727;text-align:center;font-size:12px;-webkit-appearance:none;border-radius:0;margin:0 0 6px;height:26px;outline:none}\n"] }] + }] }); + + class HslaComponent { +@@ -1149,11 +1149,11 @@ class HslaComponent { + this.color.set(newColor); + } + static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.0", ngImport: i0, type: HslaComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } +- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.0.0", type: HslaComponent, isStandalone: true, selector: "hsla-input-component", inputs: { color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: true, transformFunction: null }, labelVisible: { classPropertyName: "labelVisible", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, isAlphaVisible: { classPropertyName: "isAlphaVisible", publicName: "alpha", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { color: "colorChange" }, ngImport: i0, template: "
    \r\n \r\n @if (labelVisible()) {\r\n H\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n S\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n L\r\n }\r\n
    \r\n@if (isAlphaVisible()) {\r\n
    \r\n \r\n @if (labelVisible()) {\r\n A\r\n }\r\n
    \r\n}", styles: [":host,:host ::ng-deep *{padding:0;margin:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}\n", ":host{display:table;width:100%;text-align:center;color:#b4b4b4;font-size:11px}.column{display:table-cell;padding:0 2px}input{width:100%;border:1px solid rgb(218,218,218);color:#272727;text-align:center;font-size:12px;-webkit-appearance:none;border-radius:0;margin:0 0 6px;height:26px;outline:none}\n", ""], dependencies: [{ kind: "directive", type: ColorPickerInputDirective, selector: "[inputChange]", inputs: ["min", "max"], outputs: ["inputChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } ++ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.0.0", type: HslaComponent, isStandalone: true, selector: "hsla-input-component", inputs: { color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: true, transformFunction: null }, labelVisible: { classPropertyName: "labelVisible", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, isAlphaVisible: { classPropertyName: "isAlphaVisible", publicName: "alpha", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { color: "colorChange" }, ngImport: i0, template: "
    \r\n \r\n @if (labelVisible()) {\r\n H\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n S\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n L\r\n }\r\n
    \r\n@if (isAlphaVisible()) {\r\n
    \r\n \r\n @if (labelVisible()) {\r\n A\r\n }\r\n
    \r\n}", styles: [":host,:host ::ng-deep *{padding:0;margin:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}\n", ":host{display:table;width:100%;text-align:center;color:#b4b4b4;font-size:11px}.column{display:table-cell;padding:0 2px}input{width:100%;border:1px solid rgb(218,218,218);color:#272727;text-align:center;font-size:12px;-webkit-appearance:none;border-radius:0;margin:0 0 6px;height:26px;outline:none}\n", ""], dependencies: [{ kind: "directive", type: ColorPickerInputDirective, selector: "[inputChange]", inputs: ["min", "max"], outputs: ["inputChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } + } + i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.0", ngImport: i0, type: HslaComponent, decorators: [{ + type: Component, +- args: [{ selector: `hsla-input-component`, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [ColorPickerInputDirective], template: "
    \r\n \r\n @if (labelVisible()) {\r\n H\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n S\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n L\r\n }\r\n
    \r\n@if (isAlphaVisible()) {\r\n
    \r\n \r\n @if (labelVisible()) {\r\n A\r\n }\r\n
    \r\n}", styles: [":host,:host ::ng-deep *{padding:0;margin:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}\n", ":host{display:table;width:100%;text-align:center;color:#b4b4b4;font-size:11px}.column{display:table-cell;padding:0 2px}input{width:100%;border:1px solid rgb(218,218,218);color:#272727;text-align:center;font-size:12px;-webkit-appearance:none;border-radius:0;margin:0 0 6px;height:26px;outline:none}\n"] }] ++ args: [{ selector: `hsla-input-component`, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [ColorPickerInputDirective], template: "
    \r\n \r\n @if (labelVisible()) {\r\n H\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n S\r\n }\r\n
    \r\n
    \r\n \r\n @if (labelVisible()) {\r\n L\r\n }\r\n
    \r\n@if (isAlphaVisible()) {\r\n
    \r\n \r\n @if (labelVisible()) {\r\n A\r\n }\r\n
    \r\n}", styles: [":host,:host ::ng-deep *{padding:0;margin:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}\n", ":host{display:table;width:100%;text-align:center;color:#b4b4b4;font-size:11px}.column{display:table-cell;padding:0 2px}input{width:100%;border:1px solid rgb(218,218,218);color:#272727;text-align:center;font-size:12px;-webkit-appearance:none;border-radius:0;margin:0 0 6px;height:26px;outline:none}\n"] }] + }] }); + + class HexComponent { +@@ -1184,11 +1184,11 @@ class HexComponent { + } + } + static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.0", ngImport: i0, type: HexComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } +- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.0.0", type: HexComponent, isStandalone: true, selector: "hex-input-component", inputs: { color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: true, transformFunction: null }, labelVisible: { classPropertyName: "labelVisible", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, prefixValue: { classPropertyName: "prefixValue", publicName: "prefix", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { color: "colorChange" }, ngImport: i0, template: "
    \r\n \r\n @if (labelVisible()) {\r\n HEX\r\n }\r\n
    ", styles: [":host,:host ::ng-deep *{padding:0;margin:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}\n", ":host{display:table;width:100%;text-align:center;color:#b4b4b4;font-size:11px}.column{display:table-cell;padding:0 2px}input{width:100%;border:1px solid rgb(218,218,218);color:#272727;text-align:center;font-size:12px;-webkit-appearance:none;border-radius:0;margin:0 0 6px;height:26px;outline:none}\n", ""], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } ++ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.0.0", type: HexComponent, isStandalone: true, selector: "hex-input-component", inputs: { color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: true, transformFunction: null }, labelVisible: { classPropertyName: "labelVisible", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, prefixValue: { classPropertyName: "prefixValue", publicName: "prefix", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { color: "colorChange" }, ngImport: i0, template: "
    \r\n \r\n @if (labelVisible()) {\r\n HEX\r\n }\r\n
    ", styles: [":host,:host ::ng-deep *{padding:0;margin:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}\n", ":host{display:table;width:100%;text-align:center;color:#b4b4b4;font-size:11px}.column{display:table-cell;padding:0 2px}input{width:100%;border:1px solid rgb(218,218,218);color:#272727;text-align:center;font-size:12px;-webkit-appearance:none;border-radius:0;margin:0 0 6px;height:26px;outline:none}\n", ""], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } + } + i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.0", ngImport: i0, type: HexComponent, decorators: [{ + type: Component, +- args: [{ selector: `hex-input-component`, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: "
    \r\n \r\n @if (labelVisible()) {\r\n HEX\r\n }\r\n
    ", styles: [":host,:host ::ng-deep *{padding:0;margin:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}\n", ":host{display:table;width:100%;text-align:center;color:#b4b4b4;font-size:11px}.column{display:table-cell;padding:0 2px}input{width:100%;border:1px solid rgb(218,218,218);color:#272727;text-align:center;font-size:12px;-webkit-appearance:none;border-radius:0;margin:0 0 6px;height:26px;outline:none}\n"] }] ++ args: [{ selector: `hex-input-component`, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: "
    \r\n \r\n @if (labelVisible()) {\r\n HEX\r\n }\r\n
    ", styles: [":host,:host ::ng-deep *{padding:0;margin:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}\n", ":host{display:table;width:100%;text-align:center;color:#b4b4b4;font-size:11px}.column{display:table-cell;padding:0 2px}input{width:100%;border:1px solid rgb(218,218,218);color:#272727;text-align:center;font-size:12px;-webkit-appearance:none;border-radius:0;margin:0 0 6px;height:26px;outline:none}\n"] }] + }] }); + + const OpacityAnimation = trigger('opacityAnimation', [ diff --git a/ui-ngx/src/app/shared/components/color-picker/hex-input.component.html b/ui-ngx/src/app/shared/components/color-picker/hex-input.component.html index 5209bc6b24..0d83545c63 100644 --- a/ui-ngx/src/app/shared/components/color-picker/hex-input.component.html +++ b/ui-ngx/src/app/shared/components/color-picker/hex-input.component.html @@ -30,7 +30,7 @@ - + %
    From 52597229cf4e29befd12128b56f1ee34d56e0eb9 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Fri, 31 Oct 2025 13:28:44 +0200 Subject: [PATCH 35/38] updated widget json --- .../main/data/json/system/widget_types/photo_camera_input.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/data/json/system/widget_types/photo_camera_input.json b/application/src/main/data/json/system/widget_types/photo_camera_input.json index 69327395cc..fc676380ab 100644 --- a/application/src/main/data/json/system/widget_types/photo_camera_input.json +++ b/application/src/main/data/json/system/widget_types/photo_camera_input.json @@ -15,7 +15,7 @@ "settingsSchema": "", "dataKeySettingsSchema": "{}\n", "settingsDirective": "tb-photo-camera-input-widget-settings", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Photo camera input\",\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"showLegend\":false,\"actions\":{}}" + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"widgetTitle\":\"\",\"saveToGallery\":true,\"usePublicGalleryLink\":false,\"imageFormat\":\"image/png\",\"imageQuality\":0.92,\"maxWidth\":640,\"maxHeight\":480},\"title\":\"Photo camera input\",\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"showLegend\":false,\"actions\":{}}" }, "resources": [ { From 1bdc996a7d97c24363a0494bbf4e248e20164ac8 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Fri, 31 Oct 2025 13:49:33 +0200 Subject: [PATCH 36/38] moved to select and updated widget json --- .../system/widget_types/timeseries_table.json | 2 +- ...meseries-table-widget-settings.component.html | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/application/src/main/data/json/system/widget_types/timeseries_table.json b/application/src/main/data/json/system/widget_types/timeseries_table.json index 8343eb918c..f5381f23a2 100644 --- a/application/src/main/data/json/system/widget_types/timeseries_table.json +++ b/application/src/main/data/json/system/widget_types/timeseries_table.json @@ -17,7 +17,7 @@ "latestDataKeySettingsDirective": "tb-timeseries-table-latest-key-settings", "hasBasicMode": true, "basicModeDirective": "tb-timeseries-table-basic-config", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}],\"latestDataKeys\":null}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"displayTimewindow\":true,\"configMode\":\"basic\"}" + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}],\"latestDataKeys\":null}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"enableSearch\":true,\"enableSelectColumnDisplay\":true,\"enableStickyHeader\":true,\"enableStickyAction\":true,\"showCellActionsMenu\":true,\"reserveSpaceForHiddenAction\":\"true\",\"showTimestamp\":true,\"dateFormat\":{\"format\":\"yyyy-MM-dd HH:mm:ss\"},\"displayPagination\":true,\"useEntityLabel\":false,\"defaultPageSize\":10,\"pageStepCount\":3,\"pageStepIncrement\":10,\"hideEmptyLines\":false,\"disableStickyHeader\":false,\"useRowStyleFunction\":false,\"rowStyleFunction\":\"\",\"tabSortKey\":\"timestamp\"},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"displayTimewindow\":true,\"configMode\":\"advanced\"}" }, "resources": [ { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html index 905d643caa..9dba57a664 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html @@ -115,15 +115,15 @@ {{ 'widgets.table.use-entity-label-tab-name' | translate }} -
    +
    widgets.table.sort-by
    -
    - - {{ 'widgets.table.sort-timestamp-option' | translate }} - {{ 'widgets.table.sort-asc' | translate }} - {{ 'widgets.table.sort-desc' | translate }} - -
    + + + {{ 'widgets.table.sort-timestamp-option' | translate }} + {{ 'widgets.table.sort-asc' | translate }} + {{ 'widgets.table.sort-desc' | translate }} + +
    From 0feccf808f96506fafc7516509d5bba18a4df2af Mon Sep 17 00:00:00 2001 From: Vladyslav Prykhodko Date: Fri, 31 Oct 2025 13:54:00 +0200 Subject: [PATCH 37/38] Update timeseries_table.json --- .../main/data/json/system/widget_types/timeseries_table.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/data/json/system/widget_types/timeseries_table.json b/application/src/main/data/json/system/widget_types/timeseries_table.json index f5381f23a2..15779eb902 100644 --- a/application/src/main/data/json/system/widget_types/timeseries_table.json +++ b/application/src/main/data/json/system/widget_types/timeseries_table.json @@ -17,7 +17,7 @@ "latestDataKeySettingsDirective": "tb-timeseries-table-latest-key-settings", "hasBasicMode": true, "basicModeDirective": "tb-timeseries-table-basic-config", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}],\"latestDataKeys\":null}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"enableSearch\":true,\"enableSelectColumnDisplay\":true,\"enableStickyHeader\":true,\"enableStickyAction\":true,\"showCellActionsMenu\":true,\"reserveSpaceForHiddenAction\":\"true\",\"showTimestamp\":true,\"dateFormat\":{\"format\":\"yyyy-MM-dd HH:mm:ss\"},\"displayPagination\":true,\"useEntityLabel\":false,\"defaultPageSize\":10,\"pageStepCount\":3,\"pageStepIncrement\":10,\"hideEmptyLines\":false,\"disableStickyHeader\":false,\"useRowStyleFunction\":false,\"rowStyleFunction\":\"\",\"tabSortKey\":\"timestamp\"},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"displayTimewindow\":true,\"configMode\":\"advanced\"}" + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"entityAliasId\":null,\"filterId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}],\"latestDataKeys\":null}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"enableSearch\":true,\"enableSelectColumnDisplay\":true,\"enableStickyHeader\":true,\"enableStickyAction\":true,\"showCellActionsMenu\":true,\"reserveSpaceForHiddenAction\":\"true\",\"showTimestamp\":true,\"dateFormat\":{\"format\":\"yyyy-MM-dd HH:mm:ss\"},\"displayPagination\":true,\"useEntityLabel\":false,\"defaultPageSize\":10,\"pageStepCount\":3,\"pageStepIncrement\":10,\"hideEmptyLines\":false,\"disableStickyHeader\":false,\"useRowStyleFunction\":false,\"rowStyleFunction\":\"\",\"tabSortKey\":\"timestamp\"},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"displayTimewindow\":true,\"configMode\":\"basic\"}" }, "resources": [ { @@ -32,4 +32,4 @@ "public": true } ] -} \ No newline at end of file +} From 270b7abab0d4835d04f9c16a937479a44b707f60 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Fri, 31 Oct 2025 15:41:48 +0200 Subject: [PATCH 38/38] changed input width --- .../cards/timeseries-table-widget-settings.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html index 9dba57a664..f236dd1acd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/timeseries-table-widget-settings.component.html @@ -117,7 +117,7 @@
    widgets.table.sort-by
    - + {{ 'widgets.table.sort-timestamp-option' | translate }} {{ 'widgets.table.sort-asc' | translate }}