UI: Refactoring setupCustomActions in map widget

This commit is contained in:
Vladyslav_Prykhodko 2025-03-04 17:23:53 +02:00
parent 26a6bbbdee
commit c40d8e8797
6 changed files with 65 additions and 54 deletions

View File

@ -90,6 +90,13 @@ export interface IWidgetUtils {
getEntityDetailsPageURL: (id: string, entityType: EntityType) => string; getEntityDetailsPageURL: (id: string, entityType: EntityType) => string;
} }
export interface PlaceMapItemActionData {
action: WidgetAction;
additionalParams?: any;
afterPlaceItemCallback: ($event: Event, descriptor: WidgetAction, entityId?: EntityId, entityName?: string,
additionalParams?: any, entityLabel?: string) => void;
}
export interface WidgetActionsApi { export interface WidgetActionsApi {
actionDescriptorsBySourceId: {[sourceId: string]: Array<WidgetActionDescriptor>}; actionDescriptorsBySourceId: {[sourceId: string]: Array<WidgetActionDescriptor>};
getActionDescriptors: (actionSourceId: string) => Array<WidgetActionDescriptor>; getActionDescriptors: (actionSourceId: string) => Array<WidgetActionDescriptor>;
@ -106,6 +113,7 @@ export interface WidgetActionsApi {
hideDashboardToolbar?: boolean, preferredPlacement?: PopoverPlacement, hideDashboardToolbar?: boolean, preferredPlacement?: PopoverPlacement,
hideOnClickOutside?: boolean, popoverWidth?: string, hideOnClickOutside?: boolean, popoverWidth?: string,
popoverHeight?: string, popoverStyle?: { [klass: string]: any }) => void; popoverHeight?: string, popoverStyle?: { [klass: string]: any }) => void;
placeMapItem: (action: PlaceMapItemActionData) => void;
} }
export interface AliasInfo { export interface AliasInfo {

View File

@ -64,7 +64,7 @@ export class MapWidgetComponent implements OnInit, OnDestroy {
overlayStyle: ComponentStyle = {}; overlayStyle: ComponentStyle = {};
padding: string; padding: string;
map: TbMap<MapSetting>; private map: TbMap<MapSetting>;
constructor(public widgetComponent: WidgetComponent, constructor(public widgetComponent: WidgetComponent,
private imagePipe: ImagePipe, private imagePipe: ImagePipe,

View File

@ -17,7 +17,6 @@
import { import {
additionalMapDataSourcesToDatasources, additionalMapDataSourcesToDatasources,
BaseMapSettings, BaseMapSettings,
CustomActionData,
DataKeyValuePair, DataKeyValuePair,
MapBooleanFunction, MapBooleanFunction,
mapDataLayerTypes, mapDataLayerTypes,
@ -49,8 +48,8 @@ import {
TbLatestMapDataLayer, TbLatestMapDataLayer,
UnplacedMapDataItem, UnplacedMapDataItem,
} from '@home/components/widget/lib/maps/data-layer/latest-map-data-layer'; } from '@home/components/widget/lib/maps/data-layer/latest-map-data-layer';
import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models'; import { IWidgetSubscription, PlaceMapItemActionData, WidgetSubscriptionOptions } from '@core/api/widget-api.models';
import { FormattedData, MapItemType, WidgetAction, 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';
@ -64,10 +63,9 @@ import {
SelectMapEntityPanelComponent SelectMapEntityPanelComponent
} from '@home/components/widget/lib/maps/panels/select-map-entity-panel.component'; } from '@home/components/widget/lib/maps/panels/select-map-entity-panel.component';
import { TbPopoverComponent } from '@shared/components/popover.component'; import { TbPopoverComponent } from '@shared/components/popover.component';
import { createColorMarkerShapeURI, MarkerShape } from '@home/components/widget/lib/maps/models/marker-shape.models'; import { createPlaceItemIcon } from '@home/components/widget/lib/maps/models/marker-shape.models';
import { MatIconRegistry } from '@angular/material/icon'; import { MatIconRegistry } from '@angular/material/icon';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';
import tinycolor from 'tinycolor2';
import { MapTimelinePanelComponent } from '@home/components/widget/lib/maps/panels/map-timeline-panel.component'; import { MapTimelinePanelComponent } from '@home/components/widget/lib/maps/panels/map-timeline-panel.component';
import { ComponentRef } from '@angular/core'; import { ComponentRef } from '@angular/core';
import { TbTripsDataLayer } from '@home/components/widget/lib/maps/data-layer/trips-data-layer'; import { TbTripsDataLayer } from '@home/components/widget/lib/maps/data-layer/trips-data-layer';
@ -138,6 +136,7 @@ export abstract class TbMap<S extends BaseMapSettings> {
protected constructor(protected ctx: WidgetContext, protected constructor(protected ctx: WidgetContext,
protected inputSettings: DeepPartial<S>, protected inputSettings: DeepPartial<S>,
protected containerElement: HTMLElement) { protected containerElement: HTMLElement) {
this.ctx.actionsApi.placeMapItem = this.placeMapItem.bind(this);
this.settings = mergeDeepIgnoreArray({} as S, this.defaultSettings(), this.inputSettings as S); this.settings = mergeDeepIgnoreArray({} as S, this.defaultSettings(), this.inputSettings as S);
$(containerElement).empty(); $(containerElement).empty();
@ -550,36 +549,40 @@ export abstract class TbMap<S extends BaseMapSettings> {
} }
private setupCustomActions() { private setupCustomActions() {
if (!this.settings.mapActionButtons) { const widgetHeaderActions = this.ctx.actionsApi.getActionDescriptors('headerButton');
const mapActionButtons = this.settings.mapActionButtons;
const hasMarkerAction =
mapActionButtons?.some(actionButton => actionButton.action.mapItemType === MapItemType.marker) ||
widgetHeaderActions.some(action => action.type === WidgetActionType.placeMapItem && action.mapItemType === MapItemType.marker);
if (hasMarkerAction) {
this.setPlaceMarkerStyle();
}
if (!mapActionButtons?.length) {
return; return;
} }
this.customActionsToolbar = L.TB.topToolbar({ this.customActionsToolbar = L.TB.topToolbar({
mapElement: $(this.mapElement), mapElement: $(this.mapElement),
iconRegistry: this.ctx.$injector.get(MatIconRegistry) iconRegistry: this.ctx.$injector.get(MatIconRegistry)
}); });
const mapActionButtons = this.settings.mapActionButtons; const customTranslate = this.ctx.$injector.get(CustomTranslatePipe);
if (mapActionButtons.length) { mapActionButtons.forEach(actionButton => {
const customTranslate = this.ctx.$injector.get(CustomTranslatePipe); const actionButtonConfig = {
icon: actionButton.icon,
if (mapActionButtons.some(actionButton => actionButton.action.mapItemType === MapItemType.marker)) { color: actionButton.color,
this.setPlaceMarkerStyle(); title: customTranslate.transform(actionButton.label)
} };
const toolbarButton = this.customActionsToolbar.toolbarButton(actionButtonConfig);
mapActionButtons.forEach(actionButton => { toolbarButton.onClick((e, button) => this.ctx.actionsApi.handleWidgetAction(e, actionButton.action, null, null, {button}));
const actionButtonConfig = { });
icon: actionButton.icon,
color: actionButton.color,
title: customTranslate.transform(actionButton.label)
};
const toolbarButton = this.customActionsToolbar.toolbarButton(actionButtonConfig);
toolbarButton.onClick((e, button) => this.ctx.actionsApi.handleWidgetAction(e, actionButton.action, null, null, {button}));
});
}
} }
public placeMapItem(actionData: CustomActionData): void { public placeMapItem(actionData: PlaceMapItemActionData): void {
switch (actionData.action.mapItemType) { switch (actionData.action.mapItemType) {
case MapItemType.marker: case MapItemType.marker:
this.createMarker(actionData); this.createMarker(actionData);
@ -596,20 +599,20 @@ export abstract class TbMap<S extends BaseMapSettings> {
} }
} }
private createMarker(actionData: CustomActionData) { private createMarker(actionData: PlaceMapItemActionData) {
this.createItem(actionData, () => this.prepareDrawMode('Marker', { this.createItem(actionData, () => this.prepareDrawMode('Marker', {
placeMarker: this.ctx.translate.instant('widgets.maps.data-layer.marker.place-marker-hint') placeMarker: this.ctx.translate.instant('widgets.maps.data-layer.marker.place-marker-hint')
})); }));
} }
private createRectangle(actionData: CustomActionData): void { private createRectangle(actionData: PlaceMapItemActionData): void {
this.createItem(actionData, () => this.prepareDrawMode('Rectangle', { this.createItem(actionData, () => this.prepareDrawMode('Rectangle', {
firstVertex: this.ctx.translate.instant('widgets.maps.data-layer.polygon.rectangle-place-first-point-hint'), 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') finishRect: this.ctx.translate.instant('widgets.maps.data-layer.polygon.finish-rectangle-hint')
})); }));
} }
private createPolygon(actionData: CustomActionData): void { private createPolygon(actionData: PlaceMapItemActionData): void {
this.createItem(actionData, () => this.prepareDrawMode('Polygon', { this.createItem(actionData, () => this.prepareDrawMode('Polygon', {
firstVertex: this.ctx.translate.instant('widgets.maps.data-layer.polygon.polygon-place-first-point-hint'), 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'), continueLine: this.ctx.translate.instant('widgets.maps.data-layer.polygon.continue-polygon-hint'),
@ -617,24 +620,24 @@ export abstract class TbMap<S extends BaseMapSettings> {
})); }));
} }
private createCircle(actionData: CustomActionData): void { private createCircle(actionData: PlaceMapItemActionData): void {
this.createItem(actionData, () => this.prepareDrawMode('Circle', { this.createItem(actionData, () => this.prepareDrawMode('Circle', {
startCircle: this.ctx.translate.instant('widgets.maps.data-layer.circle.place-circle-center-hint'), 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') finishCircle: this.ctx.translate.instant('widgets.maps.data-layer.circle.finish-circle-hint')
})); }));
} }
private createItem(actionData: CustomActionData, prepareDrawMode: () => void) { private createItem(actionData: PlaceMapItemActionData, prepareDrawMode: () => void) {
if (this.isPlacingItem) { if (this.isPlacingItem) {
return; return;
} }
this.updatePlaceItemState(actionData.button, true); this.updatePlaceItemState(actionData.additionalParams?.button, true);
this.map.once('pm:create', (e) => { this.map.once('pm:create', (e) => {
actionData.afterPlaceItemCallback(e as any, actionData.action, null, null, { actionData.afterPlaceItemCallback(e as any, actionData.action, null, null, {
coordinates: convertLayerToCoordinates(actionData.action.mapItemType, e.layer), coordinates: convertLayerToCoordinates(actionData.action.mapItemType, e.layer),
layer: e.layer, layer: e.layer,
button: actionData.button button: actionData.additionalParams?.button
}); });
// @ts-ignore // @ts-ignore
@ -916,7 +919,7 @@ export abstract class TbMap<S extends BaseMapSettings> {
} }
private setPlaceMarkerStyle() { private setPlaceMarkerStyle() {
createColorMarkerShapeURI(this.getCtx().$injector.get(MatIconRegistry), this.getCtx().$injector.get(DomSanitizer), MarkerShape.markerShape1, tinycolor('rgba(255,255,255,0.75)')).subscribe( createPlaceItemIcon(this.getCtx().$injector.get(MatIconRegistry), this.getCtx().$injector.get(DomSanitizer)).subscribe(
((iconUrl) => { ((iconUrl) => {
const icon = L.icon({ const icon = L.icon({
iconUrl, iconUrl,

View File

@ -41,7 +41,6 @@ import { map } from 'rxjs/operators';
import { ImagePipe } from '@shared/pipe/image.pipe'; import { ImagePipe } from '@shared/pipe/image.pipe';
import { MarkerShape } from '@home/components/widget/lib/maps/models/marker-shape.models'; import { MarkerShape } from '@home/components/widget/lib/maps/models/marker-shape.models';
import { DateFormatSettings, simpleDateFormat } from '@shared/models/widget-settings.models'; import { DateFormatSettings, simpleDateFormat } from '@shared/models/widget-settings.models';
import { EntityId } from '@shared/models/id/entity-id';
export enum MapType { export enum MapType {
geoMap = 'geoMap', geoMap = 'geoMap',
@ -1035,13 +1034,6 @@ export interface MarkerIconInfo {
size: [number, number]; size: [number, number];
} }
export interface CustomActionData {
button?: L.TB.TopToolbarButton;
action: WidgetAction;
afterPlaceItemCallback: ($event: Event, descriptor: WidgetAction, entityId?: EntityId, entityName?: string,
additionalParams?: any, entityLabel?: string) => void
}
export type MapStringFunction = (data: FormattedData<TbMapDatasource>, export type MapStringFunction = (data: FormattedData<TbMapDatasource>,
dsData: FormattedData<TbMapDatasource>[]) => string; dsData: FormattedData<TbMapDatasource>[]) => string;

View File

@ -17,10 +17,10 @@
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import { MatIconRegistry } from '@angular/material/icon'; import { MatIconRegistry } from '@angular/material/icon';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';
import { Observable, of, switchMap } from 'rxjs'; import { Observable, of, shareReplay, switchMap } from 'rxjs';
import { catchError, map, take } from 'rxjs/operators'; import { catchError, map, take } from 'rxjs/operators';
import { isSvgIcon, splitIconName } from '@shared/models/icon.models'; import { isSvgIcon, splitIconName } from '@shared/models/icon.models';
import { Element, Text, G } from '@svgdotjs/svg.js'; import { Element, G, Text } from '@svgdotjs/svg.js';
export enum MarkerShape { export enum MarkerShape {
markerShape1 = 'markerShape1', markerShape1 = 'markerShape1',
@ -203,3 +203,14 @@ export const createColorMarkerIconElement = (iconRegistry: MatIconRegistry, domS
); );
} }
let placeItemIconURI$: Observable<string>;
export const createPlaceItemIcon= (iconRegistry: MatIconRegistry, domSanitizer: DomSanitizer): Observable<string> => {
if (placeItemIconURI$) {
return placeItemIconURI$;
}
placeItemIconURI$ = createColorMarkerShapeURI(iconRegistry, domSanitizer, MarkerShape.markerShape1, tinycolor('rgba(255,255,255,0.75)')).pipe(
shareReplay({refCount: true, bufferSize: 1})
);
return placeItemIconURI$;
}

View File

@ -125,7 +125,6 @@ import { IModulesMap } from '@modules/common/modules-map.models';
import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
import { CompiledTbFunction, compileTbFunction, isNotEmptyTbFunction } from '@shared/models/js-function.models'; import { CompiledTbFunction, compileTbFunction, isNotEmptyTbFunction } from '@shared/models/js-function.models';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import type { MapWidgetComponent } from '@home/components/widget/lib/maps/map-widget.component';
@Component({ @Component({
selector: 'tb-widget', selector: 'tb-widget',
@ -274,7 +273,8 @@ export class WidgetComponent extends PageComponent implements OnInit, OnChanges,
click: this.click.bind(this), click: this.click.bind(this),
getActiveEntityInfo: this.getActiveEntityInfo.bind(this), getActiveEntityInfo: this.getActiveEntityInfo.bind(this),
openDashboardStateInSeparateDialog: this.openDashboardStateInSeparateDialog.bind(this), openDashboardStateInSeparateDialog: this.openDashboardStateInSeparateDialog.bind(this),
openDashboardStateInPopover: this.openDashboardStateInPopover.bind(this) openDashboardStateInPopover: this.openDashboardStateInPopover.bind(this),
placeMapItem: () => {}
}; };
this.widgetContext.customHeaderActions = []; this.widgetContext.customHeaderActions = [];
@ -1151,14 +1151,11 @@ export class WidgetComponent extends PageComponent implements OnInit, OnChanges,
} }
break; break;
case WidgetActionType.placeMapItem: case WidgetActionType.placeMapItem:
const mapWidget: MapWidgetComponent = this.widgetContext.$scope.mapWidget this.widgetContext.actionsApi.placeMapItem({
if (mapWidget) { action: descriptor,
mapWidget.map.placeMapItem({ afterPlaceItemCallback: this.executeCustomPrettyAction.bind(this),
action: descriptor, additionalParams: additionalParams
afterPlaceItemCallback: this.executeCustomPrettyAction.bind(this), });
button: additionalParams?.button
});
}
break; break;
case WidgetActionType.customPretty: case WidgetActionType.customPretty:
this.executeCustomPrettyAction($event, descriptor, entityId, entityName, additionalParams, entityLabel); this.executeCustomPrettyAction($event, descriptor, entityId, entityName, additionalParams, entityLabel);