@ -0,0 +1,60 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2025 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<div class="tb-marker-icon-shapes-panel"> |
|||
<div class="tb-marker-icon-shapes-title" translate>widgets.maps.data-layer.marker.marker-icon</div> |
|||
<tb-material-icons [selectedIcon]="icon" [iconClearButton]="false" [showTitle]="false" |
|||
[popover]="popover" (iconSelected)="selectIcon($event)"></tb-material-icons> |
|||
<div class="tb-marker-icon-shapes-title" translate>widgets.maps.data-layer.marker.marker-appearance</div> |
|||
<div class="flex flex-row flex-wrap gap-3"> |
|||
<ng-container *ngFor="let containerInfo of iconContainers"> |
|||
@if (containerInfo.iconContainer === iconContainer) { |
|||
<button class="tb-select-shape-button" |
|||
mat-raised-button |
|||
(click)="selectIconContainer(containerInfo.iconContainer)" |
|||
type="button"> |
|||
<div class="tb-marker-shape" matButtonIcon [innerHTML]="containerInfo.html$ | async"> |
|||
</div> |
|||
</button> |
|||
} @else { |
|||
<button class="tb-select-shape-button" |
|||
mat-button |
|||
(click)="selectIconContainer(containerInfo.iconContainer)" |
|||
type="button"> |
|||
<div class="tb-marker-shape" matButtonIcon [innerHTML]="containerInfo.html$ | async"> |
|||
</div> |
|||
</button> |
|||
} |
|||
</ng-container> |
|||
</div> |
|||
<div class="tb-marker-icon-shapes-panel-buttons"> |
|||
<span class="flex-1"></span> |
|||
<button mat-button |
|||
color="primary" |
|||
type="button" |
|||
(click)="cancel()"> |
|||
{{ 'action.cancel' | translate }} |
|||
</button> |
|||
<button mat-raised-button |
|||
color="primary" |
|||
type="button" |
|||
(click)="apply()" |
|||
[disabled]="!dirty"> |
|||
{{ 'action.apply' | translate }} |
|||
</button> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,47 @@ |
|||
/** |
|||
* Copyright © 2016-2025 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
.tb-marker-icon-shapes-panel { |
|||
width: 100%; |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 16px; |
|||
.tb-marker-icon-shapes-title { |
|||
font-size: 16px; |
|||
font-weight: 500; |
|||
line-height: 24px; |
|||
letter-spacing: 0.25px; |
|||
color: rgba(0, 0, 0, 0.87); |
|||
} |
|||
button.mat-mdc-button-base.tb-select-shape-button { |
|||
width: 42px; |
|||
min-width: 42px; |
|||
height: 42px; |
|||
padding: 4px; |
|||
div.tb-marker-shape { |
|||
width: 34px; |
|||
height: 34px; |
|||
object-fit: contain; |
|||
} |
|||
} |
|||
.tb-marker-icon-shapes-panel-buttons { |
|||
height: 40px; |
|||
display: flex; |
|||
flex-direction: row; |
|||
gap: 16px; |
|||
justify-content: flex-end; |
|||
align-items: flex-end; |
|||
} |
|||
} |
|||
@ -0,0 +1,127 @@ |
|||
///
|
|||
/// Copyright © 2016-2025 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; |
|||
import { PageComponent } from '@shared/components/page.component'; |
|||
import { TbPopoverComponent } from '@shared/components/popover.component'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { |
|||
createColorMarkerIconElement, |
|||
MarkerIconContainer, markerIconContainers, |
|||
tripMarkerIconContainers |
|||
} from '@shared/models/widget/maps/marker-shape.models'; |
|||
import { Observable } from 'rxjs'; |
|||
import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; |
|||
import { MatIconRegistry } from '@angular/material/icon'; |
|||
import tinycolor from 'tinycolor2'; |
|||
import { map, share } from 'rxjs/operators'; |
|||
import { coerceBoolean } from '@shared/decorators/coercion'; |
|||
|
|||
export interface MarkerIconInfo { |
|||
iconContainer?: MarkerIconContainer; |
|||
icon: string; |
|||
} |
|||
|
|||
interface MarkerIconContainerInfo { |
|||
iconContainer: MarkerIconContainer; |
|||
html$: Observable<SafeHtml>; |
|||
} |
|||
|
|||
@Component({ |
|||
selector: 'tb-marker-icon-shapes', |
|||
templateUrl: './marker-icon-shapes.component.html', |
|||
providers: [], |
|||
styleUrls: ['./marker-icon-shapes.component.scss'], |
|||
encapsulation: ViewEncapsulation.None |
|||
}) |
|||
export class MarkerIconShapesComponent extends PageComponent implements OnInit { |
|||
|
|||
@Input() |
|||
icon: string; |
|||
|
|||
@Input() |
|||
iconContainer: MarkerIconContainer; |
|||
|
|||
@Input() |
|||
color: string; |
|||
|
|||
@Input() |
|||
@coerceBoolean() |
|||
trip = false; |
|||
|
|||
@Input() |
|||
popover: TbPopoverComponent<MarkerIconShapesComponent>; |
|||
|
|||
@Output() |
|||
markerIconSelected = new EventEmitter<MarkerIconInfo>(); |
|||
|
|||
dirty = false; |
|||
|
|||
iconContainers: MarkerIconContainerInfo[]; |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
private iconRegistry: MatIconRegistry, |
|||
private domSanitizer: DomSanitizer) { |
|||
super(store); |
|||
} |
|||
|
|||
ngOnInit(): void { |
|||
this.updateIconContainers(); |
|||
} |
|||
|
|||
cancel() { |
|||
this.popover?.hide(); |
|||
} |
|||
|
|||
selectIcon(icon: string) { |
|||
if (this.icon !== icon) { |
|||
this.icon = icon; |
|||
this.dirty = true; |
|||
this.updateIconContainers(); |
|||
} |
|||
} |
|||
|
|||
selectIconContainer(iconContainer: MarkerIconContainer) { |
|||
if (this.iconContainer !== iconContainer) { |
|||
this.iconContainer = iconContainer; |
|||
this.dirty = true; |
|||
} |
|||
} |
|||
|
|||
apply() { |
|||
const iconInfo: MarkerIconInfo = { |
|||
iconContainer: this.iconContainer, |
|||
icon: this.icon |
|||
}; |
|||
this.markerIconSelected.emit(iconInfo); |
|||
} |
|||
|
|||
private updateIconContainers() { |
|||
const containersList = [...(this.trip ? tripMarkerIconContainers : markerIconContainers),null]; |
|||
this.iconContainers = containersList.map((iconContainer) => { |
|||
return { |
|||
iconContainer, |
|||
html$: createColorMarkerIconElement(this.iconRegistry, this.domSanitizer, iconContainer, this.icon, tinycolor(this.color), this.trip).pipe( |
|||
map((element) => { |
|||
return this.domSanitizer.bypassSecurityTrustHtml(element.outerHTML); |
|||
}), |
|||
share() |
|||
) |
|||
}; |
|||
}); |
|||
} |
|||
} |
|||
|
After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 955 B |