UI: Map settings - responsive layout.

This commit is contained in:
Igor Kulikov 2025-03-10 16:12:05 +02:00
parent 7e0ad92a75
commit 6c99fd4fba
16 changed files with 213 additions and 60 deletions

View File

@ -201,6 +201,7 @@ export abstract class TbMap<S extends BaseMapSettings> {
parentElement.removeChild(content);
parentElement.style.display = 'none';
this.containerElement.append(content);
this.timeLineComponent.panelElement = content as Element;
}
const setup = [this.doSetupControls()];
if (this.timeline && this.settings.tripTimeline.snapToRealLocation) {

View File

@ -15,7 +15,7 @@
limitations under the License.
-->
<div class="tb-map-timeline-panel">
<div class="tb-map-timeline-panel" [class.col]="column">
@if(hasData) {
<mat-slider
[disabled]="disabled"

View File

@ -18,6 +18,13 @@
padding: 4px 16px 8px;
display: flex;
flex-direction: column;
&.col {
height: 156px;
.tb-timeline-controls {
flex-direction: column;
gap: 8px;
}
}
.tb-timeline-controls {
display: flex;
flex-direction: row;

View File

@ -16,11 +16,13 @@
import {
ChangeDetectorRef,
Component, DestroyRef,
Component,
DestroyRef,
ElementRef,
EventEmitter,
Injector,
Input,
OnDestroy,
OnInit,
Output,
ViewEncapsulation
@ -37,7 +39,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
styleUrls: ['./map-timeline-panel.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class MapTimelinePanelComponent implements OnInit {
export class MapTimelinePanelComponent implements OnInit, OnDestroy {
@Input()
settings: TripTimelineSettings;
@ -93,12 +95,26 @@ export class MapTimelinePanelComponent implements OnInit {
return !!this.currentTimeValue && this.currentTimeValue !== Infinity;
}
set panelElement(element: Element) {
this.panelElementVal = element;
this.panelResize$ = new ResizeObserver(() => {
this.resize();
});
this.panelResize$.observe(element);
}
get panelElement(): Element {
return this.panelElementVal;
}
@Input()
anchors: number[] = [];
@Output()
timeChanged = new EventEmitter<number>();
column = false;
timestampFormat: DateFormatProcessor;
minTimeIndex = 0;
@ -112,6 +128,9 @@ export class MapTimelinePanelComponent implements OnInit {
private maxValue: number;
private currentTimeValue: number = null;
private panelResize$: ResizeObserver;
private panelElementVal: Element;
constructor(public element: ElementRef<HTMLElement>,
private cd: ChangeDetectorRef,
private destroyRef: DestroyRef,
@ -126,6 +145,12 @@ export class MapTimelinePanelComponent implements OnInit {
this.speed = this.settings.speedOptions[0];
}
ngOnDestroy() {
if (this.panelResize$) {
this.panelResize$.disconnect();
}
}
public onIndexChange(index: number) {
this.index = index;
this.updateCurrentTime();
@ -208,6 +233,15 @@ export class MapTimelinePanelComponent implements OnInit {
}
}
private resize(): void {
const width = this.panelElement.getBoundingClientRect().width;
const column = width <= 400;
if (this.column !== column) {
this.column = column;
this.cd.markForCheck();
}
}
private updateCurrentTime() {
const newTime = this.minValue + this.index * this.settings.timeStep;
if (this.currentTime !== newTime) {

View File

@ -21,7 +21,7 @@
[disabled]="!patternSettingsFormGroup.get('show').value">
<mat-expansion-panel-header class="flex flex-row flex-wrap">
<mat-panel-title>
<div class="flex flex-1 flex-row items-center justify-between">
<div class="flex flex-1 flex-row items-center justify-between xs:flex-col xs:items-start xs:gap-3">
<mat-slide-toggle class="mat-slide flex items-stretch justify-center" formControlName="show" (click)="$event.stopPropagation()">
{{ patternTitle ? patternTitle : ((patternType === 'label' ? 'widgets.maps.data-layer.label' : 'widgets.maps.data-layer.tooltip') | translate) }}
</mat-slide-toggle>
@ -64,19 +64,23 @@
{{ 'widgets.maps.data-layer.auto-close-tooltips' | translate }}
</mat-slide-toggle>
</div>
<div *ngIf="hasTooltipOffset" class="tb-form-row space-between column-xs">
<div *ngIf="hasTooltipOffset" class="tb-form-row space-between column-lt-md">
<div translate>widgets.maps.data-layer.tooltip-offset</div>
<div class="flex flex-row items-center justify-start gap-2">
<div class="tb-small-label" translate>widgets.maps.data-layer.tooltip-offset-horizontal</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="offsetX"
type="number" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<div class="tb-small-label" translate>widgets.maps.data-layer.tooltip-offset-vertical</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="offsetY"
type="number" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<div class="flex flex-row items-center justify-start gap-2 xs:flex-1 xs:flex-col xs:items-stretch">
<div class="flex flex-row items-center justify-start gap-2 xs:flex-1">
<div class="tb-small-label xs:min-w-20" translate>widgets.maps.data-layer.tooltip-offset-horizontal</div>
<mat-form-field appearance="outline" class="number xs:flex-1" subscriptSizing="dynamic">
<input matInput formControlName="offsetX"
type="number" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="flex flex-row items-center justify-start gap-2 xs:flex-1">
<div class="tb-small-label xs:min-w-20" translate>widgets.maps.data-layer.tooltip-offset-vertical</div>
<mat-form-field appearance="outline" class="number xs:flex-1" subscriptSizing="dynamic">
<input matInput formControlName="offsetY"
type="number" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
</div>
</div>
<tb-map-tooltip-tag-actions-panel

View File

@ -27,7 +27,7 @@
</mat-toolbar>
<div mat-dialog-content class="tb-form-panel no-border no-padding" [formGroup]="dataLayerFormGroup">
<div class="tb-form-panel">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-row items-center justify-between xs:flex-col xs:items-start xs:gap-3">
<div class="tb-form-panel-title">{{ 'widget-config.datasource' | translate }}</div>
<tb-toggle-select formControlName="dsType">
<tb-toggle-option *ngFor="let type of datasourceTypes" [value]="type">{{ datasourceTypesTranslations.get(type) | translate }}</tb-toggle-option>
@ -71,7 +71,7 @@
<div class="flex flex-col">
<div *ngIf="['trips', 'markers'].includes(dataLayerType)" class="flex flex-row gap-3">
<tb-data-key-input
class="flex-1"
class="min-w-0 flex-1"
inlineField="false"
appearance="outline"
subscriptSizing="fixed"
@ -91,7 +91,7 @@
formControlName="xKey">
</tb-data-key-input>
<tb-data-key-input
class="flex-1"
class="min-w-0 flex-1"
inlineField="false"
appearance="outline"
subscriptSizing="fixed"
@ -181,7 +181,7 @@
[disabled]="dataLayerType === 'trips' && !dataLayerFormGroup.get('showMarker').value">
<mat-expansion-panel-header class="flex flex-row flex-wrap">
<mat-panel-title>
<div class="flex flex-1 flex-row items-center justify-between">
<div class="flex flex-1 flex-row items-center justify-between xs:flex-col xs:items-start xs:gap-3">
@if (dataLayerType === 'markers') {
<div class="tb-form-panel-title">{{ 'widgets.maps.data-layer.marker.marker' | translate }}</div>
} @else {
@ -198,11 +198,11 @@
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div *ngIf="dataLayerFormGroup.get('markerType').value === MarkerType.shape" class="tb-form-row space-between">
<div *ngIf="dataLayerFormGroup.get('markerType').value === MarkerType.shape" class="tb-form-row space-between column-xs">
<div translate>widgets.maps.data-layer.marker.shape</div>
<tb-marker-shape-settings formControlName="markerShape" [trip]="dataLayerType === 'trips'" [markerType]="MarkerType.shape"></tb-marker-shape-settings>
</div>
<div *ngIf="dataLayerFormGroup.get('markerType').value === MarkerType.icon" class="tb-form-row space-between">
<div *ngIf="dataLayerFormGroup.get('markerType').value === MarkerType.icon" class="tb-form-row space-between column-xs">
<div translate>widgets.maps.data-layer.marker.icon</div>
<tb-marker-shape-settings formControlName="markerIcon" [trip]="dataLayerType === 'trips'" [markerType]="MarkerType.icon"></tb-marker-shape-settings>
</div>
@ -210,7 +210,7 @@
<div translate>widgets.maps.data-layer.marker.image</div>
<tb-marker-image-settings formControlName="markerImage"></tb-marker-image-settings>
</div>
<div class="tb-form-row space-between column-xs">
<div class="tb-form-row space-between column-lt-md">
<div translate>widgets.maps.data-layer.marker.marker-offset</div>
<div class="flex flex-row items-center justify-start gap-2">
<div class="tb-small-label" translate>widgets.maps.data-layer.marker.offset-horizontal</div>
@ -225,7 +225,7 @@
</mat-form-field>
</div>
</div>
<div *ngIf="dataLayerType === 'trips'" class="tb-form-row space-between">
<div *ngIf="dataLayerType === 'trips'" class="tb-form-row space-between column-xs">
<mat-slide-toggle class="mat-slide" formControlName="rotateMarker">
{{ 'widgets.maps.data-layer.marker.rotate-marker' | translate }}
</mat-slide-toggle>
@ -289,9 +289,9 @@
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="tb-form-row">
<div class="fixed-title-width" translate>widgets.maps.data-layer.path.decorator-symbol</div>
<div class="flex flex-1 flex-row items-center justify-start gap-2">
<div class="tb-form-row space-between column-xs">
<div translate>widgets.maps.data-layer.path.decorator-symbol</div>
<div class="flex flex-1 flex-row items-center justify-start gap-2 lt-md:flex-col lt-md:items-stretch">
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="pathDecoratorSymbol">
<mat-option *ngFor="let symbol of pathDecoratorSymbols" [value]="symbol">
@ -299,34 +299,42 @@
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput type="number" min="0" formControlName="pathDecoratorSymbolSize" placeholder="{{ 'widget-config.set' | translate }}">
<span matSuffix>px</span>
</mat-form-field>
<tb-color-input asBoxInput
colorClearButton
formControlName="pathDecoratorSymbolColor">
</tb-color-input>
<div class="flex flex-1 flex-row items-center justify-start gap-2">
<mat-form-field appearance="outline" class="number flex-1" subscriptSizing="dynamic">
<input matInput type="number" min="0" formControlName="pathDecoratorSymbolSize" placeholder="{{ 'widget-config.set' | translate }}">
<span matSuffix>px</span>
</mat-form-field>
<tb-color-input asBoxInput
colorClearButton
formControlName="pathDecoratorSymbolColor">
</tb-color-input>
</div>
</div>
</div>
<div class="tb-form-row space-between">
<div class="tb-form-row space-between column-xs">
<div translate>widgets.maps.data-layer.path.decorator-arrangement</div>
<div class="flex flex-row items-center justify-start gap-2">
<div class="tb-small-label" translate>widgets.maps.data-layer.path.decorator-offset</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="pathDecoratorOffset" type="number" min="0" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix>px</div>
</mat-form-field>
<div class="tb-small-label" translate>widgets.maps.data-layer.path.decorator-end-offset</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="pathEndDecoratorOffset" type="number" min="0" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix>px</div>
</mat-form-field>
<div class="tb-small-label" translate>widgets.maps.data-layer.path.decorator-repeat</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="pathDecoratorRepeat" type="number" min="0" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix>px</div>
</mat-form-field>
<div class="flex flex-1 flex-row items-center justify-start gap-2 lt-md:flex-col lt-md:items-stretch">
<div class="flex flex-1 flex-row items-center justify-start gap-2">
<div class="tb-small-label lt-md:min-w-11" translate>widgets.maps.data-layer.path.decorator-offset</div>
<mat-form-field appearance="outline" class="number flex-1" subscriptSizing="dynamic">
<input matInput formControlName="pathDecoratorOffset" type="number" min="0" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix>px</div>
</mat-form-field>
</div>
<div class="flex flex-1 flex-row items-center justify-start gap-2">
<div class="tb-small-label lt-md:min-w-11" translate>widgets.maps.data-layer.path.decorator-end-offset</div>
<mat-form-field appearance="outline" class="number flex-1" subscriptSizing="dynamic">
<input matInput formControlName="pathEndDecoratorOffset" type="number" min="0" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix>px</div>
</mat-form-field>
</div>
<div class="flex flex-1 flex-row items-center justify-start gap-2">
<div class="tb-small-label lt-md:min-w-11" translate>widgets.maps.data-layer.path.decorator-repeat</div>
<mat-form-field appearance="outline" class="number flex-1" subscriptSizing="dynamic">
<input matInput formControlName="pathDecoratorRepeat" type="number" min="0" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix>px</div>
</mat-form-field>
</div>
</div>
</div>
</ng-template>

View File

@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import '../../../../../../../../../scss/constants';
.tb-form-table-row.tb-map-data-layer-row {
.tb-source-field {
@ -26,18 +28,47 @@
.tb-x-pos-field {
flex: 1 1 25%;
min-width: 0;
}
.tb-y-pos-field {
flex: 1 1 25%;
min-width: 0;
}
.tb-key-field {
flex: 1 1 50%;
min-width: 0;
}
.tb-remove-button {
width: 40px;
min-width: 40px;
}
@media #{$mat-lt-lg} {
.tb-source-field {
flex-direction: column;
flex: 1 1 30%;
}
.tb-x-pos-field, .tb-y-pos-field {
flex: 1 1 35%;
}
.tb-key-field {
flex: 1 1 70%;
}
}
@media screen and (min-width: 450px) and (max-width: 599px) {
.tb-source-field {
flex-direction: row;
}
}
@media #{$mat-xs} {
.tb-x-pos-field, .tb-y-pos-field {
display: none;
}
.tb-key-field {
display: none;
}
}
}

View File

@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import '../../../../../../../../../scss/constants';
.tb-map-data-layers {
.tb-form-table-header-cell {
&.tb-source-header {
@ -31,6 +33,25 @@
width: 80px;
min-width: 80px;
}
@media #{$mat-lt-lg} {
&.tb-source-header {
flex: 1 1 30%;
}
&.tb-x-pos-header, &.tb-y-pos-header {
flex: 1 1 35%;
}
&.tb-key-header {
flex: 1 1 70%;
}
}
@media #{$mat-xs} {
&.tb-x-pos-header, &.tb-y-pos-header {
display: none;
}
&.tb-key-header {
display: none;
}
}
}
.tb-form-table-body {

View File

@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import '../../../../../../../../../scss/constants';
.tb-form-table-row.tb-map-data-source-row {
.tb-source-field {
@ -26,10 +28,29 @@
.tb-data-keys-field {
flex: 1 1 50%;
min-width: 0;
}
.tb-remove-button {
width: 40px;
min-width: 40px;
}
@media #{$mat-lt-lg} {
.tb-source-field {
flex-direction: column;
flex: 1 1 30%;
}
.tb-data-keys-field {
flex: 1 1 70%;
}
@media #{$mat-lt-md} {
.tb-source-field {
flex: 1 1 50%;
}
.tb-data-keys-field {
flex: 1 1 50%;
}
}
}
}

View File

@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import '../../../../../../../../../scss/constants';
.tb-map-data-sources {
.tb-form-table-header-cell {
&.tb-source-header {
@ -25,6 +27,22 @@
width: 40px;
min-width: 40px;
}
@media #{$mat-lt-lg} {
&.tb-source-header {
flex: 1 1 30%;
}
&.tb-data-keys-header {
flex: 1 1 70%;
}
@media #{$mat-lt-md} {
&.tb-source-header {
flex: 1 1 50%;
}
&.tb-data-keys-header {
flex: 1 1 50%;
}
}
}
}
.tb-form-table-body {

View File

@ -27,6 +27,9 @@
.tb-layer-field {
flex: 1 1 33.33%;
@media #{$mat-xs} {
display: none;
}
}
.tb-remove-button {

View File

@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import '../../../../../../../../../scss/constants';
.tb-map-layers {
.tb-form-table-header-cell {
&.tb-label-header {
@ -23,6 +25,9 @@
}
&.tb-layer-header {
flex: 1 1 33.33%;
@media #{$mat-xs} {
display: none;
}
}
&.tb-actions-header {
width: 120px;

View File

@ -35,7 +35,7 @@
</tb-image-map-source-settings>
</div>
<div class="tb-form-panel">
<div class="flex flex-row items-center justify-between">
<div class="flex flex-row items-center justify-between xs:flex-col xs:items-start xs:gap-3">
<div class="tb-form-panel-title">
{{ 'widgets.maps.overlays.overlays' | translate }}
</div>

View File

@ -15,10 +15,10 @@
limitations under the License.
-->
<div class="tb-form-row gap-4">
<div class="tb-form-row gap-4 column-xs">
<div translate>widgets.maps.data-layer.tooltip-tag-actions</div>
<div class="flex flex-1 flex-row items-center justify-end gap-4">
<mat-chip-listbox>
<div class="flex min-w-0 flex-1 flex-row items-center justify-end gap-4 xs:justify-between">
<mat-chip-listbox class="min-w-0">
<mat-chip *ngFor="let action of this.actionsFormGroup.get('actions').value; let $index = index;"
class="tb-tag-action-chip"
[removable]="!disabled" (removed)="removeAction($index)">

View File

@ -31,13 +31,13 @@
{{ 'widgets.maps.data-layer.marker.clustering.zoom-on-cluster-click' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row space-between">
<div class="tb-form-row space-between column-xs">
<div translate>widgets.maps.data-layer.marker.clustering.max-zoom</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput type="number" min="0" max="18" step="1" formControlName="maxZoom" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<div class="tb-form-row space-between column-xs">
<div translate>widgets.maps.data-layer.marker.clustering.max-radius</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput type="number" min="0" formControlName="maxClusterRadius" placeholder="{{ 'widget-config.set' | translate }}">

View File

@ -35,7 +35,7 @@
<span matSuffix>ms</span>
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<div class="tb-form-row space-between column-xs">
<div class="fixed-title-width" translate>widgets.maps.timeline.speed-options</div>
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="speedOptions" multiple>
@ -45,7 +45,7 @@
</mat-select>
</mat-form-field>
</div>
<div class="tb-form-row">
<div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTimestamp">
{{ 'widgets.maps.timeline.timestamp' | translate }}
</mat-slide-toggle>