Browse Source

UI: Maps polygons edit mode.

pull/12723/head
Igor Kulikov 1 year ago
parent
commit
cec50eac80
  1. 23
      ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/circles-data-layer.ts
  2. 35
      ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts
  3. 4
      ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/markers-data-layer.ts
  4. 170
      ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polygons-data-layer.ts
  5. 57
      ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet/leaflet-tb.ts
  6. 12
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map.scss
  7. 35
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map.ts
  8. 14
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  9. 11
      ui-ngx/src/typings/leaflet-extend-tb.d.ts

23
ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/circles-data-layer.ts

@ -111,6 +111,29 @@ class TbCircleDataLayerItem extends TbDataLayerItem<CirclesDataLayerSettings, Tb
this.circle.off('pm:dragend');
}
protected onSelected(): L.TB.ToolbarButtonOptions[] {
if (this.dataLayer.isEditEnabled()) {
this.circle.on('pm:markerdragstart', () => this.editing = true);
this.circle.on('pm:markerdragend', () => this.editing = false);
this.circle.on('pm:edit', (e) => this.saveCircleCoordinates());
this.circle.pm.enable();
}
return [];
}
protected onDeselected(): void {
if (this.dataLayer.isEditEnabled()) {
this.circle.pm.disable();
this.circle.off('pm:markerdragstart');
this.circle.off('pm:markerdragend');
this.circle.off('pm:edit');
}
}
protected removeDataItemTitle(): string {
return this.dataLayer.getCtx().translate.instant('widgets.maps.data-layer.circle.remove-circle-for', {entityName: this.data.entityName});
}
protected removeDataItem(): void {
this.dataLayer.saveCircleCoordinates(this.data, null, null);
}

35
ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/map-data-layer.ts

@ -52,6 +52,7 @@ export abstract class TbDataLayerItem<S extends MapDataLayerSettings = MapDataLa
protected tooltip: L.Popup;
protected data: FormattedData<TbMapDatasource>;
protected selected = false;
protected removed = false;
protected constructor(data: FormattedData<TbMapDatasource>,
dsData: FormattedData<TbMapDatasource>[],
@ -143,11 +144,13 @@ export abstract class TbDataLayerItem<S extends MapDataLayerSettings = MapDataLa
public select(): L.TB.ToolbarButtonOptions[] {
if (!this.selected) {
this.selected = true;
this.disableEdit();
this.updateSelectedState();
const buttons: L.TB.ToolbarButtonOptions[] = [];
const buttons = this.onSelected();
if (this.dataLayer.isRemoveEnabled()) {
buttons.push({
title: this.dataLayer.getCtx().translate.instant('action.remove'),
id: 'remove',
title: this.removeDataItemTitle(),
click: () => {
this.removeDataItem();
},
@ -160,12 +163,19 @@ export abstract class TbDataLayerItem<S extends MapDataLayerSettings = MapDataLa
}
}
public deselect() {
public deselect(cancel = false): boolean {
if (this.selected) {
this.selected = false;
this.layer.closePopup();
this.updateSelectedState();
if (this.canDeselect(cancel)) {
this.selected = false;
this.layer.closePopup();
this.updateSelectedState();
this.onDeselected();
this.editModeUpdated();
} else {
return false;
}
}
return true;
}
public isSelected() {
@ -187,6 +197,7 @@ export abstract class TbDataLayerItem<S extends MapDataLayerSettings = MapDataLa
}
public remove() {
this.removed = true;
if (this.selected) {
this.dataLayer.getMap().deselectItem();
}
@ -227,6 +238,18 @@ export abstract class TbDataLayerItem<S extends MapDataLayerSettings = MapDataLa
}
}
protected canDeselect(cancel = false): boolean {
return true;
}
protected onSelected(): L.TB.ToolbarButtonOptions[] {
return [];
}
protected onDeselected(): void {}
protected abstract removeDataItemTitle(): string;
protected abstract removeDataItem(): void;
private createTooltip(datasource: TbMapDatasource) {

4
ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/markers-data-layer.ts

@ -172,6 +172,10 @@ class TbMarkerDataLayerItem extends TbDataLayerItem<MarkersDataLayerSettings, Tb
}
}
protected removeDataItemTitle(): string {
return this.dataLayer.getCtx().translate.instant('widgets.maps.data-layer.marker.remove-marker-for', {entityName: this.data.entityName});
}
protected removeDataItem(): void {
this.dataLayer.saveMarkerLocation(this.data, null);
}

170
ui-ngx/src/app/modules/home/components/widget/lib/maps/data-layer/polygons-data-layer.ts

@ -121,6 +121,77 @@ class TbPolygonDataLayerItem extends TbDataLayerItem<PolygonsDataLayerSettings,
this.polygon.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.polygon.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 {
if (!this.removed) {
const map = this.dataLayer.getMap().getMap();
if (map.pm.globalCutModeEnabled()) {
if (cancel) {
this.disablePolygonCutMode();
}
return false;
} else if (this.polygon.pm.rotateEnabled()) {
if (cancel) {
this.disablePolygonRotateMode();
}
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(): void {
this.dataLayer.savePolygonCoordinates(this.data, null);
}
@ -129,6 +200,105 @@ class TbPolygonDataLayerItem extends TbDataLayerItem<PolygonsDataLayerSettings,
return this.editing;
}
private enablePolygonEditMode() {
this.polygon.on('pm:markerdragstart', () => this.editing = true);
this.polygon.on('pm:markerdragend', () => this.editing = false);
this.polygon.on('pm:edit', (e) => this.savePolygonCoordinates());
this.polygon.pm.enable();
const map = this.dataLayer.getMap();
map.getEditToolbar().getButton('remove')?.setDisabled(false);
}
private disablePolygonEditMode() {
this.polygon.pm.disable();
this.polygon.off('pm:markerdragstart');
this.polygon.off('pm:markerdragend');
this.polygon.off('pm:edit');
const map = this.dataLayer.getMap();
map.getEditToolbar().getButton('remove')?.setDisabled(true);
}
private enablePolygonCutMode(cutButton?: L.TB.ToolbarButton) {
this.polygonContainer.closePopup();
this.editing = true;
this.polygon.options.bubblingMouseEvents = true;
this.polygon.once('pm:cut', (e) => {
if (this.polygon instanceof L.Rectangle) {
this.polygonContainer.removeLayer(this.polygon);
// @ts-ignore
this.polygon = L.polygon(e.layer.getLatLngs(), {
...this.polygonStyle,
snapIgnore: !this.dataLayer.isSnappable(),
bubblingMouseEvents: false
});
this.polygon.addTo(this.polygonContainer);
} else {
// @ts-ignore
this.polygon.setLatLngs(e.layer.getLatLngs());
}
// @ts-ignore
e.layer._pmTempLayer = true;
e.layer.remove();
this.polygonContainer.removeLayer(this.polygon);
// @ts-ignore
this.polygon._pmTempLayer = false;
this.polygon.addTo(this.polygonContainer);
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.firstVertex-cut'),
continueLine: this.getDataLayer().getCtx().translate.instant('widgets.maps.data-layer.polygon.continueLine-cut'),
finishPoly: this.getDataLayer().getCtx().translate.instant('widgets.maps.data-layer.polygon.finishPoly-cut')
}
}, 'en');
map.pm.enableGlobalCutMode({
// @ts-ignore
layersToCut: [this.polygon]
});
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.polygon.options.bubblingMouseEvents = false;
this.polygon.off('pm:cut');
const map = this.dataLayer.getMap().getMap();
map.pm.disableGlobalCutMode();
cutButton?.setActive(false);
}
private enablePolygonRotateMode(rotateButton?: L.TB.ToolbarButton) {
this.polygonContainer.closePopup();
this.editing = true;
this.polygon.on('pm:rotateend', (e) => {
this.savePolygonCoordinates();
});
this.polygon.pm.enableRotate();
rotateButton?.setActive(true);
this.polygon.on('pm:rotatedisable', (e) => {
this.disablePolygonRotateMode(rotateButton);
this.enablePolygonEditMode();
});
}
private disablePolygonRotateMode(rotateButton?: L.TB.ToolbarButton) {
this.editing = false;
this.polygon.pm.disableRotate();
this.polygon.off('pm:rotateend');
this.polygon.off('pm:rotatedisable');
rotateButton?.setActive(false);
}
private savePolygonCoordinates() {
let coordinates: TbPolygonCoordinates = this.polygon.getLatLngs();
if (coordinates.length === 1) {

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

@ -277,10 +277,14 @@ class GroupsControl extends SidebarPaneControl<TB.GroupsControlOptions> {
}
class ToolbarButton extends L.Control<TB.ToolbarButtonOptions> {
private readonly id: string;
private readonly button: JQuery<HTMLElement>;
private active = false;
private disabled = false;
constructor(options: TB.ToolbarButtonOptions) {
super(options);
this.id = options.id;
this.button = $("<a>")
.attr('class', 'tb-control-button')
.attr('href', '#')
@ -295,14 +299,51 @@ class ToolbarButton extends L.Control<TB.ToolbarButtonOptions> {
});
}
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;
}
addToToolbar(toolbar: BottomToolbarControl): void {
this.button.appendTo(toolbar.container);
}
getId(): string {
return this.id;
}
}
class BottomToolbarControl extends L.Control<TB.BottomToolbarControlOptions> {
private readonly buttonContainer: JQuery<HTMLElement>;
private toolbarButtons: ToolbarButton[] = [];
container: HTMLElement;
@ -320,10 +361,17 @@ class BottomToolbarControl extends L.Control<TB.BottomToolbarControlOptions> {
return this;
}
getButton(id: string): ToolbarButton {
return this.toolbarButtons.find(b => b.getId() === id);
}
open(buttons: TB.ToolbarButtonOptions[]): void {
this.toolbarButtons.length = 0;
buttons.forEach(buttonOption => {
const button = new ToolbarButton(buttonOption);
this.toolbarButtons.push(button);
button.addToToolbar(this);
});
@ -343,9 +391,12 @@ class BottomToolbarControl extends L.Control<TB.BottomToolbarControlOptions> {
}
close(): void {
this.buttonContainer.empty();
if (this.options.onClose) {
this.options.onClose();
if (this.options.onClose()) {
this.buttonContainer.empty();
}
} else {
this.buttonContainer.empty();
}
}

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

@ -55,10 +55,14 @@ $map-element-selected-color: #307FE5;
position: relative;
background: transparent;
&.leaflet-disabled {
pointer-events: none;
color: rgba(0, 0, 0, 0.18);
> div {
background: rgba(0, 0, 0, 0.18);
}
}
&:not(.leaflet-disabled) {
&:hover {
&:hover, &.active {
border-bottom: none;
color: $tb-primary-color; // primary color
&:before {
@ -115,6 +119,12 @@ $map-element-selected-color: #307FE5;
&.tb-remove {
mask-image: url('data:image/svg+xml,<svg width="20" height="20" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><path d="M4.5 14.25C4.5 14.6478 4.65804 15.0294 4.93934 15.3107C5.22064 15.592 5.60218 15.75 6 15.75H12C12.3978 15.75 12.7794 15.592 13.0607 15.3107C13.342 15.0294 13.5 14.6478 13.5 14.25V5.25H4.5V14.25ZM6 6.75H12V14.25H6V6.75ZM11.625 3L10.875 2.25H7.125L6.375 3H3.75V4.5H14.25V3H11.625Z"/></svg>');
}
&.tb-cut {
mask-image: url('data:image/svg+xml,<svg width="18" height="18" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M13.25 1.25L8.75 5.75L10.25 7.25L15.5 2V1.25M8 8.375C7.90054 8.375 7.80516 8.33549 7.73483 8.26517C7.66451 8.19484 7.625 8.09946 7.625 8C7.625 7.90054 7.66451 7.80516 7.73483 7.73483C7.80516 7.66451 7.90054 7.625 8 7.625C8.09946 7.625 8.19484 7.66451 8.26517 7.73483C8.33549 7.80516 8.375 7.90054 8.375 8C8.375 8.09946 8.33549 8.19484 8.26517 8.26517C8.19484 8.33549 8.09946 8.375 8 8.375ZM3.5 14C3.10218 14 2.72064 13.842 2.43934 13.5607C2.15804 13.2794 2 12.8978 2 12.5C2 11.6675 2.675 11 3.5 11C3.89782 11 4.27936 11.158 4.56066 11.4393C4.84196 11.7206 5 12.1022 5 12.5C5 13.3325 4.325 14 3.5 14ZM3.5 5C3.10218 5 2.72064 4.84196 2.43934 4.56066C2.15804 4.27936 2 3.89782 2 3.5C2 2.6675 2.675 2 3.5 2C3.89782 2 4.27936 2.15804 4.56066 2.43934C4.84196 2.72064 5 3.10218 5 3.5C5 4.3325 4.325 5 3.5 5ZM6.23 4.73C6.4025 4.355 6.5 3.9425 6.5 3.5C6.5 2.70435 6.18393 1.94129 5.62132 1.37868C5.05871 0.816071 4.29565 0.5 3.5 0.5C2.70435 0.5 1.94129 0.816071 1.37868 1.37868C0.816071 1.94129 0.5 2.70435 0.5 3.5C0.5 4.29565 0.816071 5.05871 1.37868 5.62132C1.94129 6.18393 2.70435 6.5 3.5 6.5C3.9425 6.5 4.355 6.4025 4.73 6.23L6.5 8L4.73 9.77C4.355 9.5975 3.9425 9.5 3.5 9.5C2.70435 9.5 1.94129 9.81607 1.37868 10.3787C0.816071 10.9413 0.5 11.7044 0.5 12.5C0.5 13.2956 0.816071 14.0587 1.37868 14.6213C1.94129 15.1839 2.70435 15.5 3.5 15.5C4.29565 15.5 5.05871 15.1839 5.62132 14.6213C6.18393 14.0587 6.5 13.2956 6.5 12.5C6.5 12.0575 6.4025 11.645 6.23 11.27L8 9.5L13.25 14.75H15.5V14L6.23 4.73Z"/></svg>');
}
&.tb-rotate {
mask-image: url('data:image/svg+xml,<svg width="16" height="16" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M1.77 1.7625C2.8575 0.675 4.35 0 6.0075 0C9.3225 0 12 2.685 12 6C12 9.315 9.3225 12 6.0075 12C3.21 12 0.8775 10.0875 0.21 7.5H1.77C2.385 9.2475 4.05 10.5 6.0075 10.5C8.49 10.5 10.5075 8.4825 10.5075 6C10.5075 3.5175 8.49 1.5 6.0075 1.5C4.7625 1.5 3.6525 2.0175 2.8425 2.835L5.2575 5.25H0.00749922V0L1.77 1.7625Z"/></svg>');
}
&.tb-close {
background: #D12730;
mask-image: url('data:image/svg+xml,<svg width="20" height="20" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><path d="M14.25 4.8075L13.1925 3.75L9 7.9425L4.8075 3.75L3.75 4.8075L7.9425 9L3.75 13.1925L4.8075 14.25L9 10.0575L13.1925 14.25L14.25 13.1925L10.0575 9L14.25 4.8075Z"/></svg>');

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

@ -246,7 +246,7 @@ export abstract class TbMap<S extends BaseMapSettings> {
mapElement: $(this.mapElement),
closeTitle: this.ctx.translate.instant('action.cancel'),
onClose: () => {
this.deselectItem();
return this.deselectItem(true);
}
}).addTo(this.map);
@ -454,28 +454,39 @@ export abstract class TbMap<S extends BaseMapSettings> {
}
}
public selectItem(item: TbDataLayerItem): void {
public selectItem(item: TbDataLayerItem, cancel = false): boolean {
let deselected = true;
if (this.selectedDataItem) {
this.selectedDataItem.deselect();
this.selectedDataItem = null;
this.editToolbar.close();
deselected = this.selectedDataItem.deselect(cancel);
if (deselected) {
this.selectedDataItem = null;
this.editToolbar.close();
}
}
this.selectedDataItem = item;
if (this.selectedDataItem) {
const buttons = this.selectedDataItem.select();
this.editToolbar.open(buttons);
this.createdControlButtonTooltip(this.editToolbar.container, 'top');
if (deselected) {
this.selectedDataItem = item;
if (this.selectedDataItem) {
const buttons = this.selectedDataItem.select();
this.editToolbar.open(buttons);
this.createdControlButtonTooltip(this.editToolbar.container, 'top');
}
}
this.ignoreUpdateBounds = !!this.selectedDataItem;
return deselected;
}
public deselectItem(): void {
this.selectItem(null);
public deselectItem(cancel = false): boolean {
return this.selectItem(null, cancel);
}
public getSelectedDataItem(): TbDataLayerItem {
return this.selectedDataItem;
}
public getEditToolbar(): L.TB.BottomToolbarControl {
return this.editToolbar;
}
public saveItemData(datasource: TbMapDatasource, data: DataKeyValuePair[]): Observable<any> {
const attributeService = this.ctx.$injector.get(AttributeService);
const attributes: AttributeData[] = [];

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

@ -7716,7 +7716,8 @@
"use-cluster-marker-color-function": "Use cluster markers color function",
"marker-color-function": "Marker color function"
},
"edit": "Edit marker"
"edit": "Edit marker",
"remove-marker-for": "Remove marker for '{{entityName}}'"
},
"polygon": {
"polygon-key": "Polygon key",
@ -7725,7 +7726,13 @@
"add-polygon": "Add polygon",
"polygon-configuration": "Polygon configuration",
"remove-polygon": "Remove polygon",
"edit": "Edit polygon"
"edit": "Edit polygon",
"remove-polygon-for": "Remove polygon for '{{entityName}}'",
"cut": "Cut polygon area",
"rotate": "Rotate polygon",
"firstVertex-cut": "Click to place first point",
"continueLine-cut": "Click to continue drawing",
"finishPoly-cut": "Click first marker to finish and save"
},
"circle": {
"circle-key": "Circle key",
@ -7734,7 +7741,8 @@
"add-circle": "Add circle",
"circle-configuration": "Circle configuration",
"remove-circle": "Remove circle",
"edit": "Edit circle"
"edit": "Edit circle",
"remove-circle-for": "Remove circle for '{{entityName}}'"
}
},
"select-entity": "Select entity",

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

@ -89,7 +89,8 @@ declare module 'leaflet' {
constructor(options: GroupsControlOptions);
}
interface ToolbarButtonOptions extends ControlOptions{
interface ToolbarButtonOptions extends ControlOptions {
id: string;
title: string;
click: (e: MouseEvent, button: ToolbarButton) => void;
iconClass: string;
@ -97,17 +98,21 @@ declare module 'leaflet' {
class ToolbarButton extends Control<ToolbarButtonOptions>{
constructor(options: ToolbarButtonOptions);
addToToolbar(toolbar: BottomToolbarControl): void;
setActive(active: boolean): void;
isActive(): boolean;
setDisabled(disabled: boolean): void;
isDisabled(): boolean;
}
interface BottomToolbarControlOptions extends ControlOptions {
mapElement: JQuery<HTMLElement>;
closeTitle: string;
onClose: () => void;
onClose: () => boolean;
}
class BottomToolbarControl extends Control<BottomToolbarControlOptions> {
constructor(options: BottomToolbarControlOptions);
getButton(id: string): ToolbarButton | undefined;
open(buttons: ToolbarButtonOptions[]): void;
close(): void;
container: HTMLElement;

Loading…
Cancel
Save