Browse Source

Merge pull request #12723 from vvlladd28/feature/custom-action-map/draw-and-create

Added new map action type - Map action buttons
pull/12807/head
Igor Kulikov 2 years ago
committed by GitHub
parent
commit
19ac154fbf
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      application/src/main/data/json/system/widget_types/image_map.json
  2. 2
      application/src/main/data/json/system/widget_types/map.json
  3. 1
      ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions-dialog.component.html
  4. 3
      ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions-dialog.component.ts
  5. 7
      ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.ts
  6. 3
      ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html
  7. 2
      ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts
  8. 3
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/widget-actions-panel.component.ts
  9. 109
      ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet/leaflet-tb.ts
  10. 53
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map.scss
  11. 289
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts
  12. 25
      ui-ngx/src/app/modules/home/components/widget/lib/maps/models/map.models.ts
  13. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings-panel.component.html
  14. 5
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings-panel.component.ts
  15. 13
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings.component.ts
  16. 16
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.html
  17. 42
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.ts
  18. 47
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-action-button-row.component.html
  19. 100
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-action-button-row.component.ts
  20. 63
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-action-buttons-settings.component.html
  21. 107
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-action-buttons-settings.component.ts
  22. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html
  23. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.ts
  24. 8
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts
  25. 10
      ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts
  26. 1
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.html
  27. 1
      ui-ngx/src/app/modules/home/components/widget/widget.component.ts
  28. 27
      ui-ngx/src/app/shared/models/widget.models.ts
  29. 46
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  30. 13
      ui-ngx/src/typings/leaflet-extend-tb.d.ts

2
application/src/main/data/json/system/widget_types/image_map.json

@ -11,7 +11,7 @@
"resources": [], "resources": [],
"templateHtml": "<tb-map-widget \n [ctx]=\"ctx\"\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-map-widget>", "templateHtml": "<tb-map-widget \n [ctx]=\"ctx\"\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-map-widget>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.mapWidget.onInit();\n};\n\nself.typeParameters = function() {\n return {\n hideDataTab: true,\n hideDataSettings: true,\n previewWidth: '80%',\n embedTitlePanel: true,\n datasourcesOptional: true\n };\n}\n", "controllerScript": "self.onInit = function() {\n self.ctx.$scope.mapWidget.onInit();\n};\n\nself.typeParameters = function() {\n return {\n hideDataTab: true,\n hideDataSettings: true,\n previewWidth: '80%',\n embedTitlePanel: true,\n datasourcesOptional: true,\n additionalWidgetActionTypes: ['placeMapItem']\n };\n}\n",
"settingsForm": [], "settingsForm": [],
"dataKeySettingsForm": [], "dataKeySettingsForm": [],
"settingsDirective": "tb-map-widget-settings", "settingsDirective": "tb-map-widget-settings",

2
application/src/main/data/json/system/widget_types/map.json

@ -11,7 +11,7 @@
"resources": [], "resources": [],
"templateHtml": "<tb-map-widget \n [ctx]=\"ctx\"\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-map-widget>", "templateHtml": "<tb-map-widget \n [ctx]=\"ctx\"\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-map-widget>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.mapWidget.onInit();\n};\n\nself.typeParameters = function() {\n return {\n hideDataTab: true,\n hideDataSettings: true,\n previewWidth: '80%',\n embedTitlePanel: true,\n datasourcesOptional: true\n };\n}\n", "controllerScript": "self.onInit = function() {\n self.ctx.$scope.mapWidget.onInit();\n};\n\nself.typeParameters = function() {\n return {\n hideDataTab: true,\n hideDataSettings: true,\n previewWidth: '80%',\n embedTitlePanel: true,\n datasourcesOptional: true,\n additionalWidgetActionTypes: ['placeMapItem']\n };\n}\n",
"settingsForm": [], "settingsForm": [],
"dataKeySettingsForm": [], "dataKeySettingsForm": [],
"settingsDirective": "tb-map-widget-settings", "settingsDirective": "tb-map-widget-settings",

1
ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions-dialog.component.html

@ -33,6 +33,7 @@
[callbacks]="data.callbacks" [callbacks]="data.callbacks"
[widgetType] = "data.widgetType" [widgetType] = "data.widgetType"
[actionSources]="actionSources" [actionSources]="actionSources"
[additionalWidgetActionTypes]="data.additionalWidgetActionTypes"
formControlName="actions"> formControlName="actions">
</tb-manage-widget-actions> </tb-manage-widget-actions>
</div> </div>

3
ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions-dialog.component.ts

@ -14,7 +14,7 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { widgetType } from '@shared/models/widget.models'; import { WidgetActionType, widgetType } from '@shared/models/widget.models';
import { import {
WidgetActionCallbacks, WidgetActionCallbacks,
WidgetActionsData WidgetActionsData
@ -32,6 +32,7 @@ export interface ManageWidgetActionsDialogData {
actionsData: WidgetActionsData; actionsData: WidgetActionsData;
callbacks: WidgetActionCallbacks; callbacks: WidgetActionCallbacks;
widgetType: widgetType; widgetType: widgetType;
additionalWidgetActionTypes?: WidgetActionType[];
} }
@Component({ @Component({

7
ui-ngx/src/app/modules/home/components/widget/action/manage-widget-actions.component.ts

@ -46,7 +46,7 @@ import {
WidgetActionsDatasource WidgetActionsDatasource
} from '@home/components/widget/action/manage-widget-actions.component.models'; } from '@home/components/widget/action/manage-widget-actions.component.models';
import { UtilsService } from '@core/services/utils.service'; import { UtilsService } from '@core/services/utils.service';
import { WidgetActionDescriptor, WidgetActionSource, widgetType } from '@shared/models/widget.models'; import { WidgetActionDescriptor, WidgetActionSource, WidgetActionType, widgetType } from '@shared/models/widget.models';
import { import {
WidgetActionDialogComponent, WidgetActionDialogComponent,
WidgetActionDialogData WidgetActionDialogData
@ -77,6 +77,8 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni
@Input() actionSources: {[actionSourceId: string]: WidgetActionSource}; @Input() actionSources: {[actionSourceId: string]: WidgetActionSource};
@Input() additionalWidgetActionTypes: WidgetActionType[];
innerValue: WidgetActionsData; innerValue: WidgetActionsData;
displayedColumns: string[]; displayedColumns: string[];
@ -236,7 +238,8 @@ export class ManageWidgetActionsComponent extends PageComponent implements OnIni
callbacks: this.callbacks, callbacks: this.callbacks,
actionsData, actionsData,
action: deepClone(action), action: deepClone(action),
widgetType: this.widgetType widgetType: this.widgetType,
additionalWidgetActionTypes: this.additionalWidgetActionTypes
} }
}).afterClosed().subscribe( }).afterClosed().subscribe(
(res) => { (res) => {

3
ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html

@ -121,7 +121,8 @@
<tb-widget-action <tb-widget-action
formControlName="widgetAction" formControlName="widgetAction"
[callbacks]="data.callbacks" [callbacks]="data.callbacks"
[widgetType]="data.widgetType"> [widgetType]="data.widgetType"
[additionalWidgetActionTypes]="data.additionalWidgetActionTypes">
</tb-widget-action> </tb-widget-action>
</div> </div>
</fieldset> </fieldset>

2
ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts

@ -43,6 +43,7 @@ import {
CellClickColumnInfo, CellClickColumnInfo,
defaultWidgetAction, defaultWidgetAction,
WidgetActionSource, WidgetActionSource,
WidgetActionType,
widgetType widgetType
} from '@shared/models/widget.models'; } from '@shared/models/widget.models';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
@ -58,6 +59,7 @@ export interface WidgetActionDialogData {
actionsData: WidgetActionsData; actionsData: WidgetActionsData;
action?: WidgetActionDescriptorInfo; action?: WidgetActionDescriptorInfo;
widgetType: widgetType; widgetType: widgetType;
additionalWidgetActionTypes?: WidgetActionType[];
} }
@Component({ @Component({

3
ui-ngx/src/app/modules/home/components/widget/config/basic/common/widget-actions-panel.component.ts

@ -122,7 +122,8 @@ export class WidgetActionsPanelComponent implements ControlValueAccessor, OnInit
widgetTitle: this.widgetConfigComponent.modelValue.widgetName, widgetTitle: this.widgetConfigComponent.modelValue.widgetName,
callbacks: this.widgetConfigComponent.widgetConfigCallbacks, callbacks: this.widgetConfigComponent.widgetConfigCallbacks,
actionsData, actionsData,
widgetType: this.widgetConfigComponent.widgetType widgetType: this.widgetConfigComponent.widgetType,
additionalWidgetActionTypes: this.widgetConfigComponent.modelValue.typeParameters.additionalWidgetActionTypes
} }
}).afterClosed().subscribe( }).afterClosed().subscribe(
(res) => { (res) => {

109
ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet/leaflet-tb.ts

@ -15,10 +15,14 @@
/// ///
import L, { TB } from 'leaflet'; import L, { TB } from 'leaflet';
import { guid } from '@core/utils'; import { guid, isNotEmptyStr } from '@core/utils';
import 'leaflet-providers'; import 'leaflet-providers';
import '@geoman-io/leaflet-geoman-free'; import '@geoman-io/leaflet-geoman-free';
import 'leaflet.markercluster'; import 'leaflet.markercluster';
import { MatIconRegistry } from '@angular/material/icon';
import { isSvgIcon, splitIconName } from '@shared/models/icon.models';
import { catchError, take } from 'rxjs/operators';
import { of } from 'rxjs';
L.MarkerCluster = L.MarkerCluster.mergeOptions({ pmIgnore: true }); L.MarkerCluster = L.MarkerCluster.mergeOptions({ pmIgnore: true });
@ -278,32 +282,105 @@ class GroupsControl extends SidebarPaneControl<TB.GroupsControlOptions> {
class TopToolbarButton { class TopToolbarButton {
private readonly button: JQuery<HTMLElement>; private readonly button: JQuery<HTMLElement>;
private _onClick: (e: MouseEvent) => void; private active = false;
private disabled = false;
private _onClick: (e: MouseEvent, button: TopToolbarButton) => void;
constructor(private readonly options: TB.TopToolbarButtonOptions) { constructor(private readonly options: TB.TopToolbarButtonOptions,
private readonly iconRegistry: MatIconRegistry) {
const iconElement = $('<div class="tb-control-button-icon"></div>'); const iconElement = $('<div class="tb-control-button-icon"></div>');
const setIcon = isNotEmptyStr(this.options.icon);
const setTitle = isNotEmptyStr(this.options.title);
this.button = $("<a>") this.button = $("<a>")
.attr('class', 'tb-control-button tb-control-text-button') .attr('class', 'tb-control-button tb-control-text-button')
.attr('href', '#') .attr('href', '#')
.attr('role', 'button'); .attr('role', 'button');
this.button.append(iconElement); if (setIcon) {
this.button.append(`<div class="tb-control-text">${this.options.title}</div>`); this.button.append(iconElement);
this.loadIcon(iconElement); this.loadIcon(iconElement);
}
if (setTitle) {
this.button.append(`<div class="tb-control-text">${this.options.title}</div>`);
}
this.button.css('--tb-map-control-color', this.options.color);
this.button.css('--tb-map-control-active-color', this.options.color);
this.button.css('--tb-map-control-hover-background-color', this.options.color);
if (setIcon && !setTitle) {
this.button.css('padding', 0);
} else if (!setIcon && setTitle) {
this.button.css('padding-left', '14px');
}
this.button.on('click', (e) => { this.button.on('click', (e) => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
if (this._onClick) { if (this._onClick) {
this._onClick(e.originalEvent); this._onClick(e.originalEvent, this);
} }
}); });
} }
onClick(onClick: (e: MouseEvent) => void): void { onClick(onClick: (e: MouseEvent, button: TopToolbarButton) => void): void {
this._onClick = onClick; this._onClick = onClick;
} }
private loadIcon(iconElement: JQuery<HTMLElement>) { private loadIcon(iconElement: JQuery<HTMLElement>) {
// this.options.icon iconElement.addClass(this.iconRegistry.getDefaultFontSetClass());
if (!isSvgIcon(this.options.icon)) {
iconElement.addClass('material-icon-font');
iconElement.text(this.options.icon);
return;
}
const [namespace, iconName] = splitIconName(this.options.icon);
this.iconRegistry
.getNamedSvgIcon(iconName, namespace)
.pipe(
take(1),
catchError((err: Error) => {
console.log(`Error retrieving icon ${namespace}:${iconName}! ${err.message}`)
return of(null);
})
)
.subscribe({
next: (svg) => {
iconElement.append(svg);
svg.style.height = '24px';
svg.style.width = '24px';
}
});
}
setActive(active: boolean): void {
if (this.active !== active) {
this.active = active;
if (this.active) {
L.DomUtil.addClass(this.button[0], 'active');
} else {
L.DomUtil.removeClass(this.button[0], 'active');
}
}
}
isActive(): boolean {
return this.active;
}
setDisabled(disabled: boolean): void {
if (this.disabled !== disabled) {
this.disabled = disabled;
if (this.disabled) {
L.DomUtil.addClass(this.button[0], 'leaflet-disabled');
this.button[0].setAttribute('aria-disabled', 'true');
} else {
L.DomUtil.removeClass(this.button[0], 'leaflet-disabled');
this.button[0].setAttribute('aria-disabled', 'false');
}
}
}
isDisabled(): boolean {
return this.disabled;
} }
getButtonElement(): JQuery<HTMLElement> { getButtonElement(): JQuery<HTMLElement> {
@ -382,20 +459,30 @@ class ToolbarButton {
class TopToolbarControl { class TopToolbarControl {
private readonly toolbarElement: JQuery<HTMLElement>; private readonly toolbarElement: JQuery<HTMLElement>;
private buttons: Array<TopToolbarButton> = [];
constructor(private readonly options: TB.TopToolbarControlOptions) { constructor(private readonly options: TB.TopToolbarControlOptions) {
const controlContainer = $('.leaflet-control-container', options.mapElement); const controlContainer = $('.leaflet-control-container', this.options.mapElement);
this.toolbarElement = $('<div class="tb-map-top-toolbar leaflet-top"></div>'); this.toolbarElement = $('<div class="tb-map-top-toolbar leaflet-top"></div>');
this.toolbarElement.appendTo(controlContainer); this.toolbarElement.appendTo(controlContainer);
} }
toolbarButton(options: TB.TopToolbarButtonOptions): TopToolbarButton { toolbarButton(options: TB.TopToolbarButtonOptions): TopToolbarButton {
const button = new TopToolbarButton(options); const button = new TopToolbarButton(options, this.options.iconRegistry);
const buttonContainer = $('<div class="leaflet-bar leaflet-control"></div>'); const buttonContainer = $('<div class="leaflet-bar leaflet-control"></div>');
button.getButtonElement().appendTo(buttonContainer); button.getButtonElement().appendTo(buttonContainer);
buttonContainer.appendTo(this.toolbarElement); buttonContainer.appendTo(this.toolbarElement);
this.buttons.push(button);
return button; return button;
} }
setDisabled(disabled: boolean): void {
this.buttons.forEach(button => {
if (!button.isActive()) {
button.setDisabled(disabled);
}
});
}
} }
class ToolbarControl extends L.Control<L.ControlOptions> { class ToolbarControl extends L.Control<L.ControlOptions> {

53
ui-ngx/src/app/modules/home/components/widget/lib/maps/map.scss

@ -37,30 +37,34 @@
border: 1px solid rgba(0,0,0,0.38); border: 1px solid rgba(0,0,0,0.38);
border-radius: 15px; border-radius: 15px;
box-shadow: 0 4px 6px 0 rgba(0, 0, 0, 0.32); box-shadow: 0 4px 6px 0 rgba(0, 0, 0, 0.32);
background: #fff; background-color: #fff;
position: relative; position: relative;
a { a {
color: rgba(0, 0, 0, 0.54); color: var(--tb-map-control-color, rgba(0, 0, 0, 0.54));
border-bottom: none; border-bottom: none;
position: relative; position: relative;
background: transparent; background-color: transparent;
&.leaflet-disabled { &.leaflet-disabled {
pointer-events: none; pointer-events: none;
color: rgba(0, 0, 0, 0.18); color: var(--tb-map-control-disable-color, rgba(0, 0, 0, 0.18));
> div { > div:not(.material-icon-font):not(.tb-control-text) {
background: rgba(0, 0, 0, 0.18); background-color: var(--tb-map-control-disable-color, rgba(0, 0, 0, 0.18));
svg {
fill: var(--tb-map-control-disable-color, rgba(0, 0, 0, 0.18));
}
} }
} }
&:not(.leaflet-disabled) { &:not(.leaflet-disabled) {
&:hover, &.active { &:hover, &.active {
border-bottom: none; border-bottom: none;
color: $tb-primary-color; // primary color color: var(--tb-map-control-active-color, $tb-primary-color); // primary color
&:before { &:before {
content: ""; content: "";
position: absolute; position: absolute;
inset: 0; inset: 0;
border-radius: 50%; border-radius: 50%;
background-color: rgba(0, 105, 92, 0.10); background-color: var(--tb-map-control-hover-background-color, rgba(0, 105, 92));
opacity: 0.1;
} }
} }
} }
@ -93,11 +97,17 @@
border-radius: 15px; border-radius: 15px;
} }
> div:not(.tb-control-text):not(.tb-close) { > div:not(.tb-control-text):not(.tb-close) {
background: $tb-primary-color; // primary color background-color: var(--tb-map-control-active-color, $tb-primary-color);
svg {
fill: var(--tb-map-control-active-color, $tb-primary-color);
}
} }
} }
> div { > div:not(.material-icon-font):not(.tb-control-text) {
background: rgba(0, 0, 0, 0.54); background-color: var(--tb-map-control-color, rgba(0, 0, 0, 0.54));
svg {
fill: var(--tb-map-control-color, rgba(0, 0, 0, 0.54));
}
} }
} }
> div { > div {
@ -141,7 +151,7 @@
} }
} }
} }
.tb-map-bottom-toolbar, .tb-map-top-toolbar { .tb-map-bottom-toolbar {
left: 0; left: 0;
right: 0; right: 0;
display: flex; display: flex;
@ -152,6 +162,25 @@
flex-direction: row; flex-direction: row;
} }
} }
.tb-map-top-toolbar {
left: 52px;
right: 52px;
display: flex;
flex-direction: row;
justify-content: center;
flex-wrap: wrap;
gap: 10px;
margin-top: 10px;
.leaflet-control {
margin-top: 0;
}
a.tb-control-button {
.tb-control-button-icon {
background-clip: text;
}
}
}
} }
.leaflet-map-pane:not(.leaflet-zoom-anim) { .leaflet-map-pane:not(.leaflet-zoom-anim) {
.leaflet-marker-icon { .leaflet-marker-icon {

289
ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts

@ -17,6 +17,7 @@
import { import {
additionalMapDataSourcesToDatasources, additionalMapDataSourcesToDatasources,
BaseMapSettings, BaseMapSettings,
CustomActionData,
DataKeyValuePair, DataKeyValuePair,
MapType, MapType,
mergeMapDatasources, mergeMapDatasources,
@ -41,7 +42,7 @@ import {
UnplacedMapDataItem, UnplacedMapDataItem,
} from '@home/components/widget/lib/maps/data-layer/map-data-layer'; } from '@home/components/widget/lib/maps/data-layer/map-data-layer';
import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models'; import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models';
import { FormattedData, WidgetAction, WidgetActionDescriptor, widgetType } from '@shared/models/widget.models'; import { FormattedData, MapItemType, WidgetAction, WidgetActionType, widgetType } from '@shared/models/widget.models';
import { EntityDataPageLink } from '@shared/models/query/query.models'; import { EntityDataPageLink } from '@shared/models/query/query.models';
import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe'; import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe';
import { TbMarkersDataLayer } from '@home/components/widget/lib/maps/data-layer/markers-data-layer'; import { TbMarkersDataLayer } from '@home/components/widget/lib/maps/data-layer/markers-data-layer';
@ -300,20 +301,7 @@ export abstract class TbMap<S extends BaseMapSettings> {
} }
}); });
this.addMarkerButton.setDisabled(true); this.addMarkerButton.setDisabled(true);
createColorMarkerShapeURI(this.getCtx().$injector.get(MatIconRegistry), this.getCtx().$injector.get(DomSanitizer), MarkerShape.markerShape1, tinycolor('rgba(255,255,255,0.75)')).subscribe( this.setPlaceMarkerStyle();
((iconUrl) => {
const icon = L.icon({
iconUrl,
iconSize: [40, 40],
iconAnchor: [20, 40]
});
this.map.pm.setGlobalOptions({
markerStyle: {
icon
}
});
})
);
} }
this.addPolygonDataLayers = addSupportedDataLayers.filter(dl => dl.dataLayerType() === MapDataLayerType.polygon); this.addPolygonDataLayers = addSupportedDataLayers.filter(dl => dl.dataLayerType() === MapDataLayerType.polygon);
if (this.addPolygonDataLayers.length) { if (this.addPolygonDataLayers.length) {
@ -351,86 +339,32 @@ export abstract class TbMap<S extends BaseMapSettings> {
} }
} }
private setupCustomActions() {
this.customActionsToolbar = L.TB.topToolbar({
mapElement: $(this.mapElement)
});
/*const customButton = this.customActionsToolbar.toolbarButton({
title: 'Super button',
icon: 'add'
});
this.customActionsToolbar.toolbarButton({
title: 'Super button 2',
icon: 'add'
});
customButton.onClick(e => {
console.log("Called!");
});*/
}
private placeMarker(e: MouseEvent, button: L.TB.ToolbarButton): void { private placeMarker(e: MouseEvent, button: L.TB.ToolbarButton): void {
this.placeItem(e, button, this.addMarkerDataLayers, this.placeItem(e, button, this.addMarkerDataLayers, (entity) => this.prepareDrawMode('Marker', {
(entity) => { placeMarker: this.ctx.translate.instant('widgets.maps.data-layer.marker.place-marker-hint-with-entity', {entityName: entity.entity.entityDisplayName})
this.map.pm.setLang('en', { }));
tooltips: {
placeMarker: this.ctx.translate.instant('widgets.maps.data-layer.marker.place-marker-hint', {entityName: entity.entity.entityDisplayName})
}
}, 'en');
this.map.pm.enableDraw('Marker');
// @ts-ignore
L.DomUtil.addClass(this.map.pm.Draw.Marker._hintMarker.getTooltip()._container, 'tb-place-item-label');
}
);
} }
private drawRectangle(e: MouseEvent, button: L.TB.ToolbarButton): void { private drawRectangle(e: MouseEvent, button: L.TB.ToolbarButton): void {
this.placeItem(e, button, this.addPolygonDataLayers, this.placeItem(e, button, this.addPolygonDataLayers, (entity) => this.prepareDrawMode('Rectangle', {
(entity) => { firstVertex: this.ctx.translate.instant('widgets.maps.data-layer.polygon.rectangle-place-first-point-hint-with-entity', {entityName: entity.entity.entityDisplayName}),
this.map.pm.setLang('en', { finishRect: this.ctx.translate.instant('widgets.maps.data-layer.polygon.finish-rectangle-hint-with-entity', {entityName: entity.entity.entityDisplayName})
tooltips: { }));
firstVertex: this.ctx.translate.instant('widgets.maps.data-layer.polygon.rectangle-place-first-point-hint', {entityName: entity.entity.entityDisplayName}),
finishRect: this.ctx.translate.instant('widgets.maps.data-layer.polygon.finish-rectangle-hint', {entityName: entity.entity.entityDisplayName})
}
}, 'en');
this.map.pm.enableDraw('Rectangle');
// @ts-ignore
L.DomUtil.addClass(this.map.pm.Draw.Rectangle._hintMarker.getTooltip()._container, 'tb-place-item-label');
}
);
} }
private drawPolygon(e: MouseEvent, button: L.TB.ToolbarButton): void { private drawPolygon(e: MouseEvent, button: L.TB.ToolbarButton): void {
this.placeItem(e, button, this.addPolygonDataLayers, this.placeItem(e, button, this.addPolygonDataLayers, (entity) => this.prepareDrawMode('Polygon', {
(entity) => { firstVertex: this.ctx.translate.instant('widgets.maps.data-layer.polygon.polygon-place-first-point-hint-with-entity', {entityName: entity.entity.entityDisplayName}),
this.map.pm.setLang('en', { continueLine: this.ctx.translate.instant('widgets.maps.data-layer.polygon.continue-polygon-hint-with-entity', {entityName: entity.entity.entityDisplayName}),
tooltips: { finishPoly: this.ctx.translate.instant('widgets.maps.data-layer.polygon.finish-polygon-hint-with-entity', {entityName: entity.entity.entityDisplayName})
firstVertex: this.ctx.translate.instant('widgets.maps.data-layer.polygon.polygon-place-first-point-hint', {entityName: entity.entity.entityDisplayName}), }));
continueLine: this.ctx.translate.instant('widgets.maps.data-layer.polygon.continue-polygon-hint', {entityName: entity.entity.entityDisplayName}),
finishPoly: this.ctx.translate.instant('widgets.maps.data-layer.polygon.finish-polygon-hint', {entityName: entity.entity.entityDisplayName})
}
}, 'en');
this.map.pm.enableDraw('Polygon');
// @ts-ignore
L.DomUtil.addClass(this.map.pm.Draw.Polygon._hintMarker.getTooltip()._container, 'tb-place-item-label');
}
);
} }
private drawCircle(e: MouseEvent, button: L.TB.ToolbarButton): void { private drawCircle(e: MouseEvent, button: L.TB.ToolbarButton): void {
this.placeItem(e, button, this.addCircleDataLayers, this.placeItem(e, button, this.addCircleDataLayers, (entity) => this.prepareDrawMode('Circle', {
(entity) => { startCircle: this.ctx.translate.instant('widgets.maps.data-layer.circle.place-circle-center-hint-with-entity', {entityName: entity.entity.entityDisplayName}),
this.map.pm.setLang('en', { finishCircle: this.ctx.translate.instant('widgets.maps.data-layer.circle.finish-circle-hint-with-entity', {entityName: entity.entity.entityDisplayName}),
tooltips: { }));
startCircle: this.ctx.translate.instant('widgets.maps.data-layer.circle.place-circle-center-hint', {entityName: entity.entity.entityDisplayName}),
finishCircle: this.ctx.translate.instant('widgets.maps.data-layer.circle.finish-circle-hint', {entityName: entity.entity.entityDisplayName}),
}
}, 'en');
this.map.pm.enableDraw('Circle');
// @ts-ignore
L.DomUtil.addClass(this.map.pm.Draw.Circle._hintMarker.getTooltip()._container, 'tb-place-item-label');
}
);
} }
private placeItem(e: MouseEvent, button: L.TB.ToolbarButton, dataLayers: TbMapDataLayer[], private placeItem(e: MouseEvent, button: L.TB.ToolbarButton, dataLayers: TbMapDataLayer[],
@ -444,21 +378,12 @@ export abstract class TbMap<S extends BaseMapSettings> {
}); });
this.selectEntityToPlace(e, items).subscribe((entity) => { this.selectEntityToPlace(e, items).subscribe((entity) => {
if (entity) { if (entity) {
const finishAdd = () => {
this.map.off('pm:create');
this.map.pm.disableDraw();
this.dataLayers.forEach(dl => dl.enableEditMode());
this.updatePlaceItemState();
this.editToolbar.close();
};
this.map.once('pm:create', (e) => { this.map.once('pm:create', (e) => {
entity.dataLayer.placeItem(entity, e.layer); entity.dataLayer.placeItem(entity, e.layer);
// @ts-ignore // @ts-ignore
e.layer._pmTempLayer = true; e.layer._pmTempLayer = true;
e.layer.remove(); e.layer.remove();
finishAdd(); this.finishAdd();
}); });
prepareDrawMode(entity); prepareDrawMode(entity);
@ -471,7 +396,7 @@ export abstract class TbMap<S extends BaseMapSettings> {
iconClass: 'tb-close', iconClass: 'tb-close',
title: this.ctx.translate.instant('action.cancel'), title: this.ctx.translate.instant('action.cancel'),
showText: true, showText: true,
click: finishAdd click: this.finishAdd
} }
], false); ], false);
} else { } else {
@ -507,6 +432,159 @@ export abstract class TbMap<S extends BaseMapSettings> {
} }
} }
private setupCustomActions() {
if (!this.settings.mapActionButtons) {
return;
}
this.customActionsToolbar = L.TB.topToolbar({
mapElement: $(this.mapElement),
iconRegistry: this.ctx.$injector.get(MatIconRegistry)
});
const mapActionButtons = this.settings.mapActionButtons;
if (mapActionButtons.length) {
const customTranslate = this.ctx.$injector.get(CustomTranslatePipe);
if (mapActionButtons.some(actionButton => actionButton.action.mapItemType === MapItemType.marker)) {
this.setPlaceMarkerStyle();
}
mapActionButtons.forEach(actionButton => {
const actionButtonConfig = {
icon: actionButton.icon,
color: actionButton.color,
title: customTranslate.transform(actionButton.label)
};
const toolbarButton = this.customActionsToolbar.toolbarButton(actionButtonConfig);
if (actionButton.action.type !== WidgetActionType.placeMapItem) {
toolbarButton.onClick((e) => this.ctx.actionsApi.handleWidgetAction(e, actionButton.action));
} else {
switch (actionButton.action.mapItemType) {
case MapItemType.marker:
toolbarButton.onClick((e, button) => this.createMarker(e, {button, action: actionButton.action}));
break;
case MapItemType.polygon:
toolbarButton.onClick((e, button) => this.createPolygon(e, {button, action: actionButton.action}));
break;
case MapItemType.rectangle:
toolbarButton.onClick((e, button) => this.createRectangle(e, {button, action: actionButton.action}));
break;
case MapItemType.circle:
toolbarButton.onClick((e, button) => this.createCircle(e, {button, action: actionButton.action}));
break;
}
}
});
}
}
private createMarker(e: MouseEvent, actionData: CustomActionData) {
this.createItem(e, actionData, () => this.prepareDrawMode('Marker', {
placeMarker: this.ctx.translate.instant('widgets.maps.data-layer.marker.place-marker-hint')
}));
}
private createRectangle(e: MouseEvent, actionData: CustomActionData): void {
this.createItem(e, actionData, () => this.prepareDrawMode('Rectangle', {
firstVertex: this.ctx.translate.instant('widgets.maps.data-layer.polygon.rectangle-place-first-point-hint'),
finishRect: this.ctx.translate.instant('widgets.maps.data-layer.polygon.finish-rectangle-hint')
}));
}
private createPolygon(e: MouseEvent, actionData: CustomActionData): void {
this.createItem(e, actionData, () => this.prepareDrawMode('Polygon', {
firstVertex: this.ctx.translate.instant('widgets.maps.data-layer.polygon.polygon-place-first-point-hint'),
continueLine: this.ctx.translate.instant('widgets.maps.data-layer.polygon.continue-polygon-hint'),
finishPoly: this.ctx.translate.instant('widgets.maps.data-layer.polygon.finish-polygon-hint')
}));
}
private createCircle(e: MouseEvent, actionData: CustomActionData): void {
this.createItem(e, actionData, () => this.prepareDrawMode('Circle', {
startCircle: this.ctx.translate.instant('widgets.maps.data-layer.circle.place-circle-center-hint'),
finishCircle: this.ctx.translate.instant('widgets.maps.data-layer.circle.finish-circle-hint')
}));
}
private createItem(e: MouseEvent, actionData: CustomActionData, prepareDrawMode: () => void) {
if (this.isPlacingItem) {
return;
}
this.updatePlaceItemState(actionData.button);
this.map.once('pm:create', (e) => {
this.ctx.actionsApi.handleWidgetAction(e as any, actionData.action, null, null, {
coordinates: convertLayerToCoordinates(actionData.action.mapItemType, e.layer),
layer: e.layer
});
// @ts-ignore
e.layer._pmTempLayer = true;
e.layer.remove();
this.finishAdd();
});
prepareDrawMode();
this.dataLayers.forEach(dl => dl.disableEditMode());
this.editToolbar.open([
{
id: 'cancel',
iconClass: 'tb-close',
title: this.ctx.translate.instant('action.cancel'),
showText: true,
click: this.finishAdd
}
], false);
const convertLayerToCoordinates = (type: MapItemType, layer: L.Layer): {x: number; y: number} | TbPolygonRawCoordinates | TbCircleData => {
switch (type) {
case MapItemType.marker:
if (layer instanceof L.Marker) {
return this.latLngToLocationData(layer.getLatLng());
}
return null;
case MapItemType.polygon:
if (layer instanceof L.Polygon) {
let coordinates: any = layer.getLatLngs();
if (coordinates.length === 1) {
coordinates = coordinates[0];
}
return this.coordinatesToPolygonData(coordinates);
}
return null;
case MapItemType.rectangle:
if (layer instanceof L.Rectangle) {
const bounds = layer.getBounds();
return this.coordinatesToPolygonData([bounds.getNorthWest(), bounds.getSouthEast()])
}
return null;
case MapItemType.circle:
if (layer instanceof L.Circle) {
return this.coordinatesToCircleData(layer.getLatLng(), layer.getRadius());
}
return null;
}
}
}
private finishAdd = () => {
this.map.off('pm:create');
this.map.pm.disableDraw();
this.dataLayers.forEach(dl => dl.enableEditMode());
this.updatePlaceItemState();
this.editToolbar.close();
}
private prepareDrawMode(shape: 'Marker' | 'Rectangle' | 'Polygon' | 'Circle', tooltipsTranslation: Record<string, string>) {
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');
}
private updatePlaceItemState(addButton?: L.TB.ToolbarButton): void { private updatePlaceItemState(addButton?: L.TB.ToolbarButton): void {
if (addButton) { if (addButton) {
this.deselectItem(false, true); this.deselectItem(false, true);
@ -624,6 +702,7 @@ export abstract class TbMap<S extends BaseMapSettings> {
if (this.addCircleButton && this.addCircleButton !== this.currentAddButton) { if (this.addCircleButton && this.addCircleButton !== this.currentAddButton) {
this.addCircleButton.setDisabled(true); this.addCircleButton.setDisabled(true);
} }
this.customActionsToolbar.setDisabled(true);
} else { } else {
if (this.addMarkerButton) { if (this.addMarkerButton) {
this.addMarkerButton.setDisabled(!this.addMarkerDataLayers.some(dl => dl.isEnabled() && dl.hasUnplacedItems())); this.addMarkerButton.setDisabled(!this.addMarkerDataLayers.some(dl => dl.isEnabled() && dl.hasUnplacedItems()));
@ -637,9 +716,27 @@ export abstract class TbMap<S extends BaseMapSettings> {
if (this.addCircleButton) { if (this.addCircleButton) {
this.addCircleButton.setDisabled(!this.addCircleDataLayers.some(dl => dl.isEnabled() && dl.hasUnplacedItems())); this.addCircleButton.setDisabled(!this.addCircleDataLayers.some(dl => dl.isEnabled() && dl.hasUnplacedItems()));
} }
this.customActionsToolbar.setDisabled(false);
} }
} }
private setPlaceMarkerStyle() {
createColorMarkerShapeURI(this.getCtx().$injector.get(MatIconRegistry), this.getCtx().$injector.get(DomSanitizer), MarkerShape.markerShape1, tinycolor('rgba(255,255,255,0.75)')).subscribe(
((iconUrl) => {
const icon = L.icon({
iconUrl,
iconSize: [40, 40],
iconAnchor: [20, 40]
});
this.map.pm.setGlobalOptions({
markerStyle: {
icon
}
});
})
);
}
protected abstract defaultSettings(): S; protected abstract defaultSettings(): S;
protected abstract createMap(): Observable<L.Map>; protected abstract createMap(): Observable<L.Map>;

25
ui-ngx/src/app/modules/home/components/widget/lib/maps/models/map.models.ts

@ -525,6 +525,13 @@ export const mapZoomActionTranslationMap = new Map<MapZoomAction, string>(
] ]
); );
export interface MapActionButtonSettings {
label?: string;
icon?: string;
color: string;
action: WidgetAction;
}
export interface BaseMapSettings { export interface BaseMapSettings {
mapType: MapType; mapType: MapType;
markers: MarkersDataLayerSettings[]; markers: MarkersDataLayerSettings[];
@ -539,6 +546,7 @@ export interface BaseMapSettings {
defaultZoomLevel: number; defaultZoomLevel: number;
minZoomLevel: number; minZoomLevel: number;
mapPageSize: number; mapPageSize: number;
mapActionButtons: MapActionButtonSettings[];
} }
export const DEFAULT_MAP_PAGE_SIZE = 16384; export const DEFAULT_MAP_PAGE_SIZE = 16384;
@ -557,9 +565,19 @@ export const defaultBaseMapSettings: BaseMapSettings = {
defaultCenterPosition: '0,0', defaultCenterPosition: '0,0',
defaultZoomLevel: null, defaultZoomLevel: null,
minZoomLevel: 16, minZoomLevel: 16,
mapPageSize: DEFAULT_MAP_PAGE_SIZE mapPageSize: DEFAULT_MAP_PAGE_SIZE,
mapActionButtons: []
}; };
export const defaultMapActionButtonSettings: MapActionButtonSettings = {
label: '',
icon: 'add',
color: '#0000008a',
action: {
type: WidgetActionType.doNothing
}
}
export enum MapProvider { export enum MapProvider {
openstreet = 'openstreet', openstreet = 'openstreet',
google = 'google', google = 'google',
@ -892,6 +910,11 @@ export interface MarkerIconInfo {
size: [number, number]; size: [number, number];
} }
export interface CustomActionData {
button: L.TB.TopToolbarButton;
action: WidgetAction;
}
export type MapStringFunction = (data: FormattedData<TbMapDatasource>, export type MapStringFunction = (data: FormattedData<TbMapDatasource>,
dsData: FormattedData<TbMapDatasource>[]) => string; dsData: FormattedData<TbMapDatasource>[]) => string;

3
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings-panel.component.html

@ -23,7 +23,8 @@
[callbacks]="callbacks" [callbacks]="callbacks"
[widgetType]="widgetType" [widgetType]="widgetType"
[withName]="withName" [withName]="withName"
[actionNames]="actionNames"> [actionNames]="actionNames"
[additionalWidgetActionTypes]="additionalWidgetActionTypes">
</tb-widget-action> </tb-widget-action>
</div> </div>
<div class="tb-action-settings-panel-buttons"> <div class="tb-action-settings-panel-buttons">

5
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings-panel.component.ts

@ -17,7 +17,7 @@
import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
import { TbPopoverComponent } from '@shared/components/popover.component'; import { TbPopoverComponent } from '@shared/components/popover.component';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { WidgetAction, widgetType } from '@shared/models/widget.models'; import { WidgetAction, WidgetActionType, widgetType } from '@shared/models/widget.models';
import { WidgetActionCallbacks } from '@home/components/widget/action/manage-widget-actions.component.models'; import { WidgetActionCallbacks } from '@home/components/widget/action/manage-widget-actions.component.models';
import { coerceBoolean } from '@shared/decorators/coercion'; import { coerceBoolean } from '@shared/decorators/coercion';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
@ -53,6 +53,9 @@ export class WidgetActionSettingsPanelComponent implements OnInit {
@Input() @Input()
applyTitle = this.translate.instant('action.apply'); applyTitle = this.translate.instant('action.apply');
@Input()
additionalWidgetActionTypes: WidgetActionType[];
@Output() @Output()
widgetActionApplied = new EventEmitter<WidgetAction>(); widgetActionApplied = new EventEmitter<WidgetAction>();

13
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action-settings.component.ts

@ -29,7 +29,12 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { MatButton } from '@angular/material/button'; import { MatButton } from '@angular/material/button';
import { TbPopoverService } from '@shared/components/popover.service'; import { TbPopoverService } from '@shared/components/popover.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { WidgetAction, widgetActionTypeTranslationMap, widgetType } from '@shared/models/widget.models'; import {
WidgetAction,
WidgetActionType,
widgetActionTypeTranslationMap,
widgetType
} from '@shared/models/widget.models';
import { WidgetActionCallbacks } from '@home/components/widget/action/manage-widget-actions.component.models'; import { WidgetActionCallbacks } from '@home/components/widget/action/manage-widget-actions.component.models';
import { import {
WidgetActionSettingsPanelComponent WidgetActionSettingsPanelComponent
@ -65,6 +70,9 @@ export class WidgetActionSettingsComponent implements OnInit, ControlValueAccess
@Input() @Input()
disabled = false; disabled = false;
@Input()
additionalWidgetActionTypes: WidgetActionType[];
modelValue: WidgetAction; modelValue: WidgetAction;
displayValue: string; displayValue: string;
@ -110,7 +118,8 @@ export class WidgetActionSettingsComponent implements OnInit, ControlValueAccess
widgetAction: this.modelValue, widgetAction: this.modelValue,
panelTitle: this.panelTitle, panelTitle: this.panelTitle,
widgetType: this.widgetType, widgetType: this.widgetType,
callbacks: this.callbacks callbacks: this.callbacks,
additionalWidgetActionTypes: this.additionalWidgetActionTypes
}; };
const widgetActionSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, const widgetActionSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, WidgetActionSettingsPanelComponent, this.viewContainerRef, WidgetActionSettingsPanelComponent,

16
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.html

@ -258,7 +258,21 @@
helpId="widget/action/custom_action_fn" helpId="widget/action/custom_action_fn"
></tb-js-func> ></tb-js-func>
</ng-template> </ng-template>
<ng-template [ngSwitchCase]="widgetActionType.customPretty"> <ng-template [ngSwitchCase]="widgetActionType.placeMapItem">
<div class="tb-form-row">
<div class="fixed-title-width">{{ 'widget-action.map-item-type' | translate }}</div>
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-select placeholder="{{ 'widget-config.set' | translate }}" formControlName="mapItemType">
@for(type of mapItemTypes; track type) {
<mat-option [value]="type">{{ mapItemTypeTranslationMap.get(type) | translate }}</mat-option>
}
</mat-select>
</mat-form-field>
</div>
</ng-template>
<ng-template [ngSwitchCase]="widgetActionFormGroup.get('type').value === widgetActionType.customPretty
|| widgetActionFormGroup.get('type').value === widgetActionType.placeMapItem
? widgetActionFormGroup.get('type').value : ''">
<tb-custom-action-pretty-editor <tb-custom-action-pretty-editor
formControlName="customAction"> formControlName="customAction">
</tb-custom-action-pretty-editor> </tb-custom-action-pretty-editor>

42
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.ts

@ -15,17 +15,21 @@
/// ///
import { import {
ControlValueAccessor, FormControl, ControlValueAccessor,
FormControl,
NG_VALIDATORS, NG_VALIDATORS,
NG_VALUE_ACCESSOR, NG_VALUE_ACCESSOR,
UntypedFormBuilder, UntypedFormBuilder,
UntypedFormControl, UntypedFormControl,
UntypedFormGroup, UntypedFormGroup,
Validator, ValidatorFn, Validator,
ValidatorFn,
Validators Validators
} from '@angular/forms'; } from '@angular/forms';
import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core'; import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core';
import { import {
MapItemType,
mapItemTypeTranslationMap,
WidgetAction, WidgetAction,
WidgetActionType, WidgetActionType,
widgetActionTypes, widgetActionTypes,
@ -97,10 +101,25 @@ export class WidgetActionComponent implements ControlValueAccessor, OnInit, Vali
@Input() @Input()
actionNames: string[]; actionNames: string[];
@Input()
set additionalWidgetActionTypes(value: WidgetActionType[]) {
if (this.widgetActionFormGroup && !widgetActionTypes.includes(this.widgetActionFormGroup.get('type').value)) {
this.widgetActionFormGroup.get('type').setValue(WidgetActionType.doNothing);
}
if (value?.length) {
this.widgetActionTypes = widgetActionTypes.concat(value);
} else {
this.widgetActionTypes = widgetActionTypes;
}
}
widgetActionTypes = widgetActionTypes; widgetActionTypes = widgetActionTypes;
widgetActionTypeTranslations = widgetActionTypeTranslationMap; widgetActionTypeTranslations = widgetActionTypeTranslationMap;
widgetActionType = WidgetActionType; widgetActionType = WidgetActionType;
mapItemTypes = Object.values(MapItemType) as MapItemType[];
mapItemTypeTranslationMap = mapItemTypeTranslationMap;
allStateDisplayTypes = stateDisplayTypes; allStateDisplayTypes = stateDisplayTypes;
allPopoverPlacements = PopoverPlacements; allPopoverPlacements = PopoverPlacements;
@ -171,6 +190,9 @@ export class WidgetActionComponent implements ControlValueAccessor, OnInit, Vali
).subscribe(() => { ).subscribe(() => {
this.widgetActionUpdated(); this.widgetActionUpdated();
}); });
if (this.additionalWidgetActionTypes) {
this.widgetActionTypes = this.widgetActionTypes.concat(this.additionalWidgetActionTypes);
}
} }
writeValue(widgetAction?: WidgetAction): void { writeValue(widgetAction?: WidgetAction): void {
@ -307,6 +329,16 @@ export class WidgetActionComponent implements ControlValueAccessor, OnInit, Vali
this.fb.control(action ? action.url : null, [Validators.required]) this.fb.control(action ? action.url : null, [Validators.required])
); );
break; break;
case WidgetActionType.placeMapItem:
this.actionTypeFormGroup.addControl(
'mapItemType',
this.fb.control(action?.mapItemType ?? MapItemType.marker, [Validators.required])
);
this.actionTypeFormGroup.addControl(
'customAction',
this.fb.control(toCustomAction(action), [Validators.required])
);
break;
} }
} }
this.actionTypeFormGroupSubscriptions.push( this.actionTypeFormGroupSubscriptions.push(
@ -497,6 +529,12 @@ export class WidgetActionComponent implements ControlValueAccessor, OnInit, Vali
let result: WidgetAction; let result: WidgetAction;
if (type === WidgetActionType.customPretty) { if (type === WidgetActionType.customPretty) {
result = {...this.widgetActionFormGroup.value, ...this.actionTypeFormGroup.get('customAction').value}; result = {...this.widgetActionFormGroup.value, ...this.actionTypeFormGroup.get('customAction').value};
} else if (type === WidgetActionType.placeMapItem) {
result = {
...this.widgetActionFormGroup.value,
...this.actionTypeFormGroup.get('customAction').value,
mapItemType: this.actionTypeFormGroup.get('mapItemType').value
};
} else { } else {
result = {...this.widgetActionFormGroup.value, ...this.actionTypeFormGroup.value}; result = {...this.widgetActionFormGroup.value, ...this.actionTypeFormGroup.value};
} }

47
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-action-button-row.component.html

@ -0,0 +1,47 @@
<!--
Copyright © 2016-2024 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.
-->
<div [formGroup]="mapActionButton" class="tb-form-table-row">
<mat-form-field class="tb-inline-field tb-label-field min-w-32" style="flex: 1 1 40%" appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="label" placeholder="{{ 'widget-config.set' | translate }}">
<mat-icon matSuffix
matTooltipPosition="above"
matTooltipClass="tb-error-tooltip"
matTooltip="{{ 'widgets.maps.map-action.button-requires' | translate }}"
*ngIf="mapActionButton.hasError('invalidButtonConfig')"
class="tb-error">
warning
</mat-icon>
</mat-form-field>
<tb-material-icon-select formControlName="icon" asBoxInput iconClearButton [color]="this.mapActionButton.get('color').value"></tb-material-icon-select>
<tb-color-input formControlName="color" asBoxInput required></tb-color-input>
<tb-widget-action-settings class="min-w-32" style="flex: 1 1 60%"
formControlName="action"
panelTitle="{{ 'widgets.maps.map-action.map-action-button' | translate }}"
[widgetType]="widgetType.latest"
[additionalWidgetActionTypes]="additionalWidgetActionTypes">
</tb-widget-action-settings>
<div class="tb-form-table-row-cell-buttons w-10 min-w-10">
<button type="button"
mat-icon-button
(click)="buttonRemoved.emit()"
matTooltip="{{ 'widgets.maps.map-action.remove-action-button' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>

100
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-action-button-row.component.ts

@ -0,0 +1,100 @@
///
/// Copyright © 2016-2024 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 { Component, EventEmitter, forwardRef, Output } from '@angular/core';
import {
ControlValueAccessor,
FormBuilder,
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator
} from '@angular/forms';
import { MapActionButtonSettings } from '@home/components/widget/lib/maps/models/map.models';
import { WidgetAction, WidgetActionType, widgetType } from '@shared/models/widget.models';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { isEmptyStr } from '@core/utils';
@Component({
selector: 'tb-map-action-button-row',
templateUrl: 'map-action-button-row.component.html',
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MapActionButtonRowComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => MapActionButtonRowComponent),
multi: true
}]
})
export class MapActionButtonRowComponent implements ControlValueAccessor, Validator {
@Output()
buttonRemoved = new EventEmitter();
mapActionButton = this.fb.group({
label: [''],
icon: [''],
color: [''],
action: this.fb.control<WidgetAction>(null)
}, {validators: this.validateButtonConfig()});
additionalWidgetActionTypes = [WidgetActionType.placeMapItem];
readonly widgetType = widgetType;
private propagateChange = (_val: any) => {};
constructor(private fb: FormBuilder) {
this.mapActionButton.valueChanges.pipe(
takeUntilDestroyed()
).subscribe(value => this.propagateChange(value))
}
registerOnChange(fn: any) {
this.propagateChange = fn;
}
registerOnTouched(_fn: any): void { }
setDisabledState(isDisabled: boolean) {
if (isDisabled) {
this.mapActionButton.disable({emitEvent: false});
} else {
this.mapActionButton.enable({emitEvent: false});
}
}
validate(): ValidationErrors | null {
return this.mapActionButton.valid ? null : {
mapButtonAction: false
};
}
writeValue(value: MapActionButtonSettings) {
this.mapActionButton.patchValue(value, {emitEvent: false});
}
private validateButtonConfig() {
return (c: FormGroup) => {
return !c.value.icon && isEmptyStr(c.value.label) ? {
invalidButtonConfig: true
} : null;
};
}
}

63
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-action-buttons-settings.component.html

@ -0,0 +1,63 @@
<!--
Copyright © 2016-2024 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.
-->
<section class="tb-form-panel">
<mat-expansion-panel class="tb-settings" expanded>
<mat-expansion-panel-header>
{{ 'widgets.maps.map-action.map-action-buttons' | translate }}
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="tb-form-table overflow-y-auto">
<div class="tb-form-table-header no-padding-right">
<div class="tb-form-table-header-cell min-w-32" style="flex: 1 1 40%" translate>widgets.maps.map-action.label</div>
<div class="tb-form-table-header-cell w-10 min-w-10" translate>widgets.maps.map-action.icon</div>
<div class="tb-form-table-header-cell w-10 min-w-10" translate>widgets.maps.map-action.color</div>
<div class="tb-form-table-header-cell min-w-32" style="flex: 1 1 60%" translate>widgets.maps.map-action.action</div>
<div class="tb-form-table-header-cell w-20 min-w-20 xs:w-10 xs:min-w-10"></div>
</div>
<div class="tb-form-table-body" cdkDropList cdkDropListOrientation="vertical"
[cdkDropListDisabled]="!dragEnabled"
(cdkDropListDropped)="buttonDrop($event)">
@for (mapAction of mapActionButtonsForm.controls; track mapAction) {
<div cdkDrag [cdkDragDisabled]="!dragEnabled" class="tb-draggable-form-table-row">
<tb-map-action-button-row class="flex-1"
[formControl]="mapAction"
(buttonRemoved)="removeButton($event)">
</tb-map-action-button-row>
<div class="tb-form-table-row-cell-buttons">
<button class="xs:!hidden"
[class.tb-hidden]="!dragEnabled"
mat-icon-button
type="button"
cdkDragHandle
matTooltip="{{ 'action.drag' | translate }}"
matTooltipPosition="above">
<mat-icon>drag_indicator</mat-icon>
</button>
</div>
</div>
} @empty {
<span class="tb-prompt flex items-center justify-center">{{ 'widgets.maps.map-action.no-action-buttons-configured' | translate }}</span>
}
</div>
</div>
<button type="button" mat-stroked-button color="primary" class="max-w-fit" (click)="addButton()">
{{ 'widgets.maps.map-action.add-button' | translate }}
</button>
</ng-template>
</mat-expansion-panel>
</section>

107
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-action-buttons-settings.component.ts

@ -0,0 +1,107 @@
///
/// Copyright © 2016-2024 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 { Component, forwardRef } from '@angular/core';
import {
ControlValueAccessor,
FormBuilder,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator
} from '@angular/forms';
import {
defaultMapActionButtonSettings,
MapActionButtonSettings
} from '@home/components/widget/lib/maps/models/map.models';
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'tb-map-action-button-settings',
templateUrl: './map-action-buttons-settings.component.html',
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MapActionButtonsSettingsComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => MapActionButtonsSettingsComponent),
multi: true
}]
})
export class MapActionButtonsSettingsComponent implements ControlValueAccessor, Validator {
mapActionButtonsForm = this.fb.array<MapActionButtonSettings>([]);
private propagateChange = (_val: any) => {};
constructor(private fb: FormBuilder) {
this.mapActionButtonsForm.valueChanges.pipe(
takeUntilDestroyed()
).subscribe(value => this.propagateChange(value));
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(_fn: any): void { }
setDisabledState(isDisabled: boolean): void {
if (isDisabled) {
this.mapActionButtonsForm.disable({emitEvent: false});
} else {
this.mapActionButtonsForm.enable({emitEvent: false});
}
}
validate(): ValidationErrors | null {
return this.mapActionButtonsForm.valid ? null : {
mapActionButtons: false
};
}
writeValue(buttons: MapActionButtonSettings[] = []) {
if (buttons?.length === this.mapActionButtonsForm.length) {
this.mapActionButtonsForm.patchValue(buttons, {emitEvent: false});
} else {
this.mapActionButtonsForm.clear({emitEvent: false});
buttons.forEach(
button => this.mapActionButtonsForm.push(this.fb.control(button), {emitEvent: false})
);
}
}
get dragEnabled(): boolean {
return this.mapActionButtonsForm.length > 1;
}
buttonDrop(event: CdkDragDrop<string[]>) {
const actionButton = this.mapActionButtonsForm.at(event.previousIndex);
this.mapActionButtonsForm.removeAt(event.previousIndex, {emitEvent: false});
this.mapActionButtonsForm.insert(event.currentIndex, actionButton);
}
addButton() {
this.mapActionButtonsForm.push(this.fb.control(defaultMapActionButtonSettings));
}
removeButton(index: number) {
this.mapActionButtonsForm.removeAt(index);
}
}

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.html

@ -93,6 +93,8 @@
</mat-chip-listbox> </mat-chip-listbox>
</div> </div>
</div> </div>
<tb-map-action-button-settings formControlName="mapActionButtons">
</tb-map-action-button-settings>
<div class="tb-form-panel"> <div class="tb-form-panel">
<div class="tb-form-panel-title"> <div class="tb-form-panel-title">
{{ 'widgets.maps.common.common-map-settings' | translate }} {{ 'widgets.maps.common.common-map-settings' | translate }}

3
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/map/map-settings.component.ts

@ -135,7 +135,8 @@ export class MapSettingsComponent implements OnInit, ControlValueAccessor, Valid
useDefaultCenterPosition: [null, []], useDefaultCenterPosition: [null, []],
defaultCenterPosition: [null, []], defaultCenterPosition: [null, []],
defaultZoomLevel: [null, [Validators.min(0), Validators.max(20)]], defaultZoomLevel: [null, [Validators.min(0), Validators.max(20)]],
mapPageSize: [null, [Validators.min(1), Validators.required]] mapPageSize: [null, [Validators.min(1), Validators.required]],
mapActionButtons: [null]
}); });
this.mapSettingsFormGroup.valueChanges.pipe( this.mapSettingsFormGroup.valueChanges.pipe(
takeUntilDestroyed(this.destroyRef) takeUntilDestroyed(this.destroyRef)

8
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts

@ -236,6 +236,12 @@ import {
import { import {
MapTooltipTagActionsComponent MapTooltipTagActionsComponent
} from '@home/components/widget/lib/settings/common/map/map-tooltip-tag-actions.component'; } from '@home/components/widget/lib/settings/common/map/map-tooltip-tag-actions.component';
import {
MapActionButtonsSettingsComponent
} from '@home/components/widget/lib/settings/common/map/map-action-buttons-settings.component';
import {
MapActionButtonRowComponent
} from '@home/components/widget/lib/settings/common/map/map-action-button-row.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -313,6 +319,8 @@ import {
DataLayerColorSettingsComponent, DataLayerColorSettingsComponent,
DataLayerColorSettingsPanelComponent, DataLayerColorSettingsPanelComponent,
MapTooltipTagActionsComponent, MapTooltipTagActionsComponent,
MapActionButtonsSettingsComponent,
MapActionButtonRowComponent,
DataLayerPatternSettingsComponent, DataLayerPatternSettingsComponent,
MarkerShapeSettingsComponent, MarkerShapeSettingsComponent,
MarkerShapesComponent, MarkerShapesComponent,

10
ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts

@ -40,11 +40,12 @@ import {
migrateWidgetTypeToDynamicForms, migrateWidgetTypeToDynamicForms,
Widget, Widget,
widgetActionSources, widgetActionSources,
WidgetActionType,
WidgetControllerDescriptor, WidgetControllerDescriptor,
WidgetType WidgetType
} from '@shared/models/widget.models'; } from '@shared/models/widget.models';
import { catchError, map, mergeMap, switchMap, tap } from 'rxjs/operators'; import { catchError, map, mergeMap, switchMap, tap } from 'rxjs/operators';
import { isFunction, isUndefined } from '@core/utils'; import { isDefinedAndNotNull, isFunction, isUndefined } from '@core/utils';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { DynamicWidgetComponent } from '@home/components/widget/dynamic-widget.component'; import { DynamicWidgetComponent } from '@home/components/widget/dynamic-widget.component';
import { WidgetComponentsModule } from '@home/components/widget/widget-components.module'; import { WidgetComponentsModule } from '@home/components/widget/widget-components.module';
@ -654,6 +655,13 @@ export class WidgetComponentService {
if (isUndefined(result.typeParameters.targetDeviceOptional)) { if (isUndefined(result.typeParameters.targetDeviceOptional)) {
result.typeParameters.targetDeviceOptional = false; result.typeParameters.targetDeviceOptional = false;
} }
if (isDefinedAndNotNull(result.typeParameters.additionalWidgetActionTypes)) {
if (Array.isArray(result.typeParameters.additionalWidgetActionTypes)) {
result.typeParameters.additionalWidgetActionTypes = result.typeParameters.additionalWidgetActionTypes.filter(type => WidgetActionType[type]);
} else {
result.typeParameters.additionalWidgetActionTypes = null;
}
}
if (isFunction(widgetTypeInstance.actionSources)) { if (isFunction(widgetTypeInstance.actionSources)) {
result.actionSources = widgetTypeInstance.actionSources(); result.actionSources = widgetTypeInstance.actionSources();
} else { } else {

1
ui-ngx/src/app/modules/home/components/widget/widget-config.component.html

@ -164,6 +164,7 @@
[callbacks]="widgetConfigCallbacks" [callbacks]="widgetConfigCallbacks"
[widgetType] = "modelValue.widgetType" [widgetType] = "modelValue.widgetType"
[actionSources]="modelValue.actionSources" [actionSources]="modelValue.actionSources"
[additionalWidgetActionTypes]="modelValue.typeParameters.additionalWidgetActionTypes"
formControlName="actions"> formControlName="actions">
</tb-manage-widget-actions> </tb-manage-widget-actions>
</div> </div>

1
ui-ngx/src/app/modules/home/components/widget/widget.component.ts

@ -1149,6 +1149,7 @@ export class WidgetComponent extends PageComponent implements OnInit, OnChanges,
) )
} }
break; break;
case WidgetActionType.placeMapItem:
case WidgetActionType.customPretty: case WidgetActionType.customPretty:
const customPrettyFunction = descriptor.customFunction; const customPrettyFunction = descriptor.customFunction;
const customHtml = descriptor.customHtml; const customHtml = descriptor.customHtml;

27
ui-ngx/src/app/shared/models/widget.models.ts

@ -192,6 +192,7 @@ export interface WidgetTypeParameters {
dataKeySettingsFunction?: DataKeySettingsFunction; dataKeySettingsFunction?: DataKeySettingsFunction;
displayRpcMessageToast?: boolean; displayRpcMessageToast?: boolean;
targetDeviceOptional?: boolean; targetDeviceOptional?: boolean;
additionalWidgetActionTypes?: WidgetActionType[];
} }
export interface WidgetControllerDescriptor { export interface WidgetControllerDescriptor {
@ -576,7 +577,8 @@ export enum WidgetActionType {
custom = 'custom', custom = 'custom',
customPretty = 'customPretty', customPretty = 'customPretty',
mobileAction = 'mobileAction', mobileAction = 'mobileAction',
openURL = 'openURL' openURL = 'openURL',
placeMapItem = 'placeMapItem'
} }
export enum WidgetMobileActionType { export enum WidgetMobileActionType {
@ -590,7 +592,15 @@ export enum WidgetMobileActionType {
takeScreenshot = 'takeScreenshot' takeScreenshot = 'takeScreenshot'
} }
export const widgetActionTypes = Object.keys(WidgetActionType) as WidgetActionType[]; export enum MapItemType {
marker = 'marker',
polygon = 'polygon',
rectangle = 'rectangle',
circle = 'circle'
}
export const widgetActionTypes = Object.keys(WidgetActionType)
.filter(value => value !== WidgetActionType.placeMapItem) as WidgetActionType[];
export const widgetActionTypeTranslationMap = new Map<WidgetActionType, string>( export const widgetActionTypeTranslationMap = new Map<WidgetActionType, string>(
[ [
@ -601,7 +611,8 @@ export const widgetActionTypeTranslationMap = new Map<WidgetActionType, string>(
[ WidgetActionType.custom, 'widget-action.custom' ], [ WidgetActionType.custom, 'widget-action.custom' ],
[ WidgetActionType.customPretty, 'widget-action.custom-pretty' ], [ WidgetActionType.customPretty, 'widget-action.custom-pretty' ],
[ WidgetActionType.mobileAction, 'widget-action.mobile-action' ], [ WidgetActionType.mobileAction, 'widget-action.mobile-action' ],
[ WidgetActionType.openURL, 'widget-action.open-URL' ] [ WidgetActionType.openURL, 'widget-action.open-URL' ],
[ WidgetActionType.placeMapItem, 'widget-action.place-map-item' ],
] ]
); );
@ -618,6 +629,15 @@ export const widgetMobileActionTypeTranslationMap = new Map<WidgetMobileActionTy
] ]
); );
export const mapItemTypeTranslationMap = new Map<MapItemType, string>(
[
[ MapItemType.marker, 'widget-action.map-item.marker' ],
[ MapItemType.polygon, 'widget-action.map-item.polygon' ],
[ MapItemType.rectangle, 'widget-action.map-item.rectangle' ],
[ MapItemType.circle, 'widget-action.map-item.circle' ],
]
)
export interface MobileLaunchResult { export interface MobileLaunchResult {
launched: boolean; launched: boolean;
} }
@ -715,6 +735,7 @@ export interface WidgetAction extends CustomActionDescriptor {
stateEntityParamName?: string; stateEntityParamName?: string;
mobileAction?: WidgetMobileActionDescriptor; mobileAction?: WidgetMobileActionDescriptor;
url?: string; url?: string;
mapItemType?: MapItemType;
} }
export interface WidgetActionDescriptor extends WidgetAction { export interface WidgetActionDescriptor extends WidgetAction {

46
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -6426,7 +6426,15 @@
"take-screenshot": "Take screenshot" "take-screenshot": "Take screenshot"
}, },
"custom-action-function": "Custom action function", "custom-action-function": "Custom action function",
"custom-pretty-function": "Custom action (with HTML template) function" "custom-pretty-function": "Custom action (with HTML template) function",
"map-item-type": "Map item type",
"map-item": {
"marker": "Marker",
"polygon": "Polygon",
"rectangle": "Rectangle",
"circle": "Circle"
},
"place-map-item": "Place map item"
}, },
"widgets-bundle": { "widgets-bundle": {
"current": "Current bundle", "current": "Current bundle",
@ -7698,6 +7706,18 @@
"zoom-double-click": "Double click", "zoom-double-click": "Double click",
"zoom-control-buttons": "Control buttons" "zoom-control-buttons": "Control buttons"
}, },
"map-action": {
"map-action-buttons": "Map action buttons",
"label": "Label",
"icon": "Icon",
"color": "Color",
"action": "Action",
"add-button": "Add button",
"no-action-buttons-configured": "No action buttons configured",
"remove-action-button": "Remove action button",
"map-action-button": "Map action button",
"button-requires": "Button requires label or icon"
},
"common": { "common": {
"common-map-settings": "Common map settings", "common-map-settings": "Common map settings",
"fit-map-bounds": "Fit map bounds to cover all markers", "fit-map-bounds": "Fit map bounds to cover all markers",
@ -7856,7 +7876,8 @@
"edit": "Edit marker", "edit": "Edit marker",
"remove-marker-for": "Remove marker for '{{entityName}}'", "remove-marker-for": "Remove marker for '{{entityName}}'",
"place-marker": "Place marker", "place-marker": "Place marker",
"place-marker-hint": "Click to place '{{entityName}}' entity" "place-marker-hint": "Click to place marker",
"place-marker-hint-with-entity": "Click to place '{{entityName}}' entity"
}, },
"polygon": { "polygon": {
"polygon-key": "Polygon key", "polygon-key": "Polygon key",
@ -7874,11 +7895,16 @@
"polygon-place-first-point-cut-hint": "Click to place first point", "polygon-place-first-point-cut-hint": "Click to place first point",
"continue-polygon-cut-hint": "Click to continue drawing", "continue-polygon-cut-hint": "Click to continue drawing",
"finish-polygon-cut-hint": "Click first marker to finish and save", "finish-polygon-cut-hint": "Click first marker to finish and save",
"polygon-place-first-point-hint": "Polygon for '{{entityName}}': click to place first point", "polygon-place-first-point-hint": "Polygon: click to place first point",
"continue-polygon-hint": "Polygon for '{{entityName}}': click to continue drawing", "polygon-place-first-point-hint-with-entity": "Polygon for '{{entityName}}': click to place first point",
"finish-polygon-hint": "Polygon for '{{entityName}}': click first marker to finish and save", "continue-polygon-hint": "Polygon: click to continue drawing",
"rectangle-place-first-point-hint": "Rectangle for '{{entityName}}': click to place first point", "continue-polygon-hint-with-entity": "Polygon for '{{entityName}}': click to continue drawing",
"finish-rectangle-hint": "Rectangle for '{{entityName}}': click to finish and save" "finish-polygon-hint": "Polygon: click first marker to finish drawing",
"finish-polygon-hint-with-entity": "Polygon for '{{entityName}}': click first marker to finish and save",
"rectangle-place-first-point-hint": "Rectangle: click to place first point",
"rectangle-place-first-point-hint-with-entity": "Rectangle for '{{entityName}}': click to place first point",
"finish-rectangle-hint": "Rectangle: click to finish drawing",
"finish-rectangle-hint-with-entity": "Rectangle for '{{entityName}}': click to finish and save"
}, },
"circle": { "circle": {
"circle-key": "Circle key", "circle-key": "Circle key",
@ -7890,8 +7916,10 @@
"edit": "Edit circle", "edit": "Edit circle",
"remove-circle-for": "Remove circle for '{{entityName}}'", "remove-circle-for": "Remove circle for '{{entityName}}'",
"draw-circle": "Draw circle", "draw-circle": "Draw circle",
"place-circle-center-hint": "Circle for '{{entityName}}': click to place circle center", "place-circle-center-hint-with-entity": "Circle for '{{entityName}}': click to place circle center",
"finish-circle-hint": "Circle for '{{entityName}}': click to finish and save circle" "place-circle-center-hint": "Circle: click to place circle center",
"finish-circle-hint-with-entity": "Circle for '{{entityName}}': click to finish and save circle",
"finish-circle-hint": "Circle: click to finish drawing"
}, },
"select-entity": "Select entity", "select-entity": "Select entity",
"select-entity-hint": "Hint: after selection click at the map to set position" "select-entity-hint": "Hint: after selection click at the map to set position"

13
ui-ngx/src/typings/leaflet-extend-tb.d.ts

@ -17,6 +17,7 @@
import { FormattedData } from '@shared/models/widget.models'; import { FormattedData } from '@shared/models/widget.models';
import L, { Control, ControlOptions } from 'leaflet'; import L, { Control, ControlOptions } from 'leaflet';
import { TbMapDatasource } from '@home/components/widget/lib/maps/models/map.models'; import { TbMapDatasource } from '@home/components/widget/lib/maps/models/map.models';
import { MatIconRegistry } from '@angular/material/icon';
// redeclare module, maintains compatibility with @types/leaflet // redeclare module, maintains compatibility with @types/leaflet
declare module 'leaflet' { declare module 'leaflet' {
@ -91,22 +92,28 @@ declare module 'leaflet' {
interface TopToolbarButtonOptions { interface TopToolbarButtonOptions {
icon: string; icon: string;
iconColor?: string; color?: string;
title: string; title: string;
} }
class TopToolbarButton { class TopToolbarButton {
constructor(options: TopToolbarButtonOptions); constructor(options: TopToolbarButtonOptions, iconRegistry: MatIconRegistry);
onClick(onClick: (e: MouseEvent) => void): void; onClick(onClick: (e: MouseEvent, button: TopToolbarButton) => void): void;
setActive(active: boolean): void;
isActive(): boolean;
setDisabled(disabled: boolean): void;
isDisabled(): boolean;
} }
interface TopToolbarControlOptions { interface TopToolbarControlOptions {
mapElement: JQuery<HTMLElement>; mapElement: JQuery<HTMLElement>;
iconRegistry: MatIconRegistry;
} }
class TopToolbarControl { class TopToolbarControl {
constructor(options: TopToolbarControlOptions); constructor(options: TopToolbarControlOptions);
toolbarButton(options: TopToolbarButtonOptions): TopToolbarButton; toolbarButton(options: TopToolbarButtonOptions): TopToolbarButton;
setDisabled(disabled: boolean): void;
} }
interface ToolbarButtonOptions { interface ToolbarButtonOptions {

Loading…
Cancel
Save