UI: Improved timewindow position and clear code in alarm filter config

This commit is contained in:
Vladyslav_Prykhodko 2023-08-29 15:00:01 +03:00
parent bff3297c56
commit f0fbc784b4
6 changed files with 43 additions and 60 deletions

View File

@ -17,7 +17,7 @@
// eslint-disable-next-line @typescript-eslint/triple-slash-reference // eslint-disable-next-line @typescript-eslint/triple-slash-reference
/// <reference path="../../../../src/typings/rawloader.typings.d.ts" /> /// <reference path="../../../../src/typings/rawloader.typings.d.ts" />
import { ElementRef, Inject, Injectable, NgZone } from '@angular/core'; import { Inject, Injectable, NgZone } from '@angular/core';
import { WINDOW } from '@core/services/window.service'; import { WINDOW } from '@core/services/window.service';
import { ExceptionData } from '@app/shared/models/error.models'; import { ExceptionData } from '@app/shared/models/error.models';
import { import {
@ -45,7 +45,7 @@ import { alarmFields, alarmSeverityTranslations, alarmStatusTranslations } from
import { materialColors } from '@app/shared/models/material.models'; import { materialColors } from '@app/shared/models/material.models';
import { WidgetInfo } from '@home/models/widget-component.models'; import { WidgetInfo } from '@home/models/widget-component.models';
import jsonSchemaDefaults from 'json-schema-defaults'; import jsonSchemaDefaults from 'json-schema-defaults';
import { fromEvent, Observable, Subscription } from 'rxjs'; import { Observable } from 'rxjs';
import { publishReplay, refCount } from 'rxjs/operators'; import { publishReplay, refCount } from 'rxjs/operators';
import { WidgetContext } from '@app/modules/home/models/widget-component.models'; import { WidgetContext } from '@app/modules/home/models/widget-component.models';
import { import {
@ -57,8 +57,6 @@ import {
import { EntityId } from '@shared/models/id/entity-id'; import { EntityId } from '@shared/models/id/entity-id';
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
import { entityTypeTranslations } from '@shared/models/entity-type.models'; import { entityTypeTranslations } from '@shared/models/entity-type.models';
import { OverlayRef } from '@angular/cdk/overlay';
import { ResizeObserver } from '@juggle/resize-observer';
const i18nRegExp = new RegExp(`{${i18nPrefix}:[^{}]+}`, 'g'); const i18nRegExp = new RegExp(`{${i18nPrefix}:[^{}]+}`, 'g');
@ -530,24 +528,4 @@ export class UtilsService {
return base64toObj(b64Encoded); return base64toObj(b64Encoded);
} }
public updateOverlayMaxHeigth(overlay: OverlayRef, observeElementRef?: ElementRef): Subscription {
const observeElement = observeElementRef ? observeElementRef.nativeElement : overlay.overlayElement.children[0];
const setMaxHeigthOverlay = () => {
const top = observeElement.getBoundingClientRect().top;
const viewport = window.innerHeight;
overlay.updateSize({
maxHeight: viewport - top
});
};
const observer = new ResizeObserver(() => {
setMaxHeigthOverlay();
observer.unobserve(observeElement);
});
observer.observe(observeElement);
return fromEvent(window, 'resize').subscribe(() => setMaxHeigthOverlay());
}
} }

View File

@ -31,7 +31,7 @@ import {
import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { AlarmFilterConfig, alarmFilterConfigEquals } from '@shared/models/query/query.models'; import { AlarmFilterConfig, alarmFilterConfigEquals } from '@shared/models/query/query.models';
import { coerceBoolean } from '@shared/decorators/coercion'; import { coerceBoolean } from '@shared/decorators/coercion';
import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; import { Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal'; import { TemplatePortal } from '@angular/cdk/portal';
import { import {
AlarmAssigneeOption, AlarmAssigneeOption,
@ -44,8 +44,8 @@ import { MatChipInputEvent } from '@angular/material/chips';
import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { deepClone } from '@core/utils'; import { deepClone } from '@core/utils';
import { Subscription } from 'rxjs'; import { fromEvent, Subscription } from 'rxjs';
import { UtilsService } from '@core/services/utils.service'; import { POSITION_MAP } from '@shared/components/popover.models';
export const ALARM_FILTER_CONFIG_DATA = new InjectionToken<any>('AlarmFilterConfigData'); export const ALARM_FILTER_CONFIG_DATA = new InjectionToken<any>('AlarmFilterConfigData');
@ -128,7 +128,6 @@ export class AlarmFilterConfigComponent implements OnInit, OnDestroy, ControlVal
private translate: TranslateService, private translate: TranslateService,
private overlay: Overlay, private overlay: Overlay,
private nativeElement: ElementRef, private nativeElement: ElementRef,
private utils: UtilsService,
private viewContainerRef: ViewContainerRef) { private viewContainerRef: ViewContainerRef) {
} }
@ -202,14 +201,9 @@ export class AlarmFilterConfigComponent implements OnInit, OnDestroy, ControlVal
minWidth: '' minWidth: ''
}); });
config.hasBackdrop = true; config.hasBackdrop = true;
const connectedPosition: ConnectedPosition = { config.positionStrategy = this.overlay.position()
originX: 'start', .flexibleConnectedTo(this.nativeElement)
originY: 'bottom', .withPositions([POSITION_MAP.bottomLeft]);
overlayX: 'start',
overlayY: 'top'
};
config.positionStrategy = this.overlay.position().flexibleConnectedTo(this.nativeElement)
.withPositions([connectedPosition]);
this.alarmFilterOverlayRef = this.overlay.create(config); this.alarmFilterOverlayRef = this.overlay.create(config);
this.alarmFilterOverlayRef.backdropClick().subscribe(() => { this.alarmFilterOverlayRef.backdropClick().subscribe(() => {
@ -217,7 +211,9 @@ export class AlarmFilterConfigComponent implements OnInit, OnDestroy, ControlVal
}); });
this.alarmFilterOverlayRef.attach(new TemplatePortal(this.alarmFilterPanel, this.alarmFilterOverlayRef.attach(new TemplatePortal(this.alarmFilterPanel,
this.viewContainerRef)); this.viewContainerRef));
this.resizeWindows = this.utils.updateOverlayMaxHeigth(this.alarmFilterOverlayRef); this.resizeWindows = fromEvent(window, 'resize').subscribe(() => {
this.alarmFilterOverlayRef.updatePosition();
});
} }
cancel() { cancel() {

View File

@ -42,7 +42,7 @@ import cssjs from '@core/css/css';
import { sortItems } from '@shared/models/page/page-link'; import { sortItems } from '@shared/models/page/page-link';
import { Direction } from '@shared/models/page/sort-order'; import { Direction } from '@shared/models/page/sort-order';
import { CollectionViewer, DataSource, SelectionModel } from '@angular/cdk/collections'; import { CollectionViewer, DataSource, SelectionModel } from '@angular/cdk/collections';
import { BehaviorSubject, forkJoin, merge, Observable, Subject, Subscription } from 'rxjs'; import { BehaviorSubject, forkJoin, fromEvent, merge, Observable, Subject, Subscription } from 'rxjs';
import { emptyPageData, PageData } from '@shared/models/page/page-data'; import { emptyPageData, PageData } from '@shared/models/page/page-data';
import { debounceTime, distinctUntilChanged, map, take, takeUntil, tap } from 'rxjs/operators'; import { debounceTime, distinctUntilChanged, map, take, takeUntil, tap } from 'rxjs/operators';
import { MatPaginator } from '@angular/material/paginator'; import { MatPaginator } from '@angular/material/paginator';
@ -123,6 +123,7 @@ import {
} from '@home/components/alarm/alarm-filter-config.component'; } from '@home/components/alarm/alarm-filter-config.component';
import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import { FormBuilder } from '@angular/forms'; import { FormBuilder } from '@angular/forms';
import { DEFAULT_OVERLAY_POSITIONS } from '@shared/components/popover.models';
interface AlarmsTableWidgetSettings extends TableWidgetSettings { interface AlarmsTableWidgetSettings extends TableWidgetSettings {
alarmsTitle: string; alarmsTitle: string;
@ -572,14 +573,9 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
height: 'fit-content', height: 'fit-content',
maxHeight: '75vh' maxHeight: '75vh'
}); });
const connectedPosition: ConnectedPosition = { config.positionStrategy = this.overlay.position()
originX: 'end', .flexibleConnectedTo(target as HTMLElement)
originY: 'bottom', .withPositions(DEFAULT_OVERLAY_POSITIONS);
overlayX: 'end',
overlayY: 'top'
};
config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement)
.withPositions([connectedPosition]);
const overlayRef = this.overlay.create(config); const overlayRef = this.overlay.create(config);
overlayRef.backdropClick().subscribe(() => { overlayRef.backdropClick().subscribe(() => {
@ -614,10 +610,12 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
const componentRef = overlayRef.attach(new ComponentPortal(AlarmFilterConfigComponent, const componentRef = overlayRef.attach(new ComponentPortal(AlarmFilterConfigComponent,
this.viewContainerRef, injector)); this.viewContainerRef, injector));
const resizeWindows = this.utils.updateOverlayMaxHeigth(overlayRef, componentRef.location); const resizeWindows$ = fromEvent(window, 'resize').subscribe(() => {
overlayRef.updatePosition();
});
componentRef.onDestroy(() => { componentRef.onDestroy(() => {
resizeWindows.unsubscribe(); resizeWindows$.unsubscribe();
if (componentRef.instance.panelResult) { if (componentRef.instance.panelResult) {
const result = componentRef.instance.panelResult; const result = componentRef.instance.panelResult;
const alarmFilter = this.entityService.resolveAlarmFilter(result, false); const alarmFilter = this.entityService.resolveAlarmFilter(result, false);

View File

@ -71,6 +71,9 @@ export const POSITION_MAP: { [key: string]: ConnectionPositionPair } = {
export const DEFAULT_POPOVER_POSITIONS = [POSITION_MAP.top, POSITION_MAP.right, POSITION_MAP.bottom, POSITION_MAP.left]; export const DEFAULT_POPOVER_POSITIONS = [POSITION_MAP.top, POSITION_MAP.right, POSITION_MAP.bottom, POSITION_MAP.left];
export const DEFAULT_OVERLAY_POSITIONS = [POSITION_MAP.bottomLeft, POSITION_MAP.bottomRight, POSITION_MAP.topLeft,
POSITION_MAP.topRight, POSITION_MAP.left, POSITION_MAP.right];
export function getPlacementName(position: ConnectedOverlayPositionChange): PopoverPlacement | undefined { export function getPlacementName(position: ConnectedOverlayPositionChange): PopoverPlacement | undefined {
for (const placement in POSITION_MAP) { for (const placement in POSITION_MAP) {
if ( if (

View File

@ -17,6 +17,7 @@
:host { :host {
min-width: 355px; min-width: 355px;
display: block;
@media #{$mat-xs} { @media #{$mat-xs} {
min-width: 0; min-width: 0;

View File

@ -18,9 +18,13 @@ import {
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
ElementRef, ElementRef,
forwardRef, HostBinding, forwardRef,
HostBinding,
Injector, Injector,
Input, OnChanges, OnInit, SimpleChanges, Input,
OnChanges,
OnInit,
SimpleChanges,
StaticProvider, StaticProvider,
ViewContainerRef ViewContainerRef
} from '@angular/core'; } from '@angular/core';
@ -47,15 +51,18 @@ import { TimeService } from '@core/services/time.service';
import { TooltipPosition } from '@angular/material/tooltip'; import { TooltipPosition } from '@angular/material/tooltip';
import { deepClone, isDefinedAndNotNull } from '@core/utils'; import { deepClone, isDefinedAndNotNull } from '@core/utils';
import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; import { Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal'; import { ComponentPortal } from '@angular/cdk/portal';
import { coerceBoolean } from '@shared/decorators/coercion'; import { coerceBoolean } from '@shared/decorators/coercion';
import { import {
ComponentStyle, ComponentStyle,
defaultTimewindowStyle, iconStyle, defaultTimewindowStyle,
iconStyle,
textStyle, textStyle,
TimewindowStyle TimewindowStyle
} from '@shared/models/widget-settings.models'; } from '@shared/models/widget-settings.models';
import { DEFAULT_OVERLAY_POSITIONS } from '@shared/components/popover.models';
import { fromEvent } from 'rxjs';
// @dynamic // @dynamic
@Component({ @Component({
@ -223,14 +230,10 @@ export class TimewindowComponent implements ControlValueAccessor, OnInit, OnChan
maxHeight: '80vh', maxHeight: '80vh',
height: 'min-content' height: 'min-content'
}); });
const connectedPosition: ConnectedPosition = {
originX: 'start', config.positionStrategy = this.overlay.position()
originY: 'bottom', .flexibleConnectedTo(this.nativeElement)
overlayX: 'start', .withPositions(DEFAULT_OVERLAY_POSITIONS);
overlayY: 'top'
};
config.positionStrategy = this.overlay.position().flexibleConnectedTo(this.nativeElement)
.withPositions([connectedPosition]);
const overlayRef = this.overlay.create(config); const overlayRef = this.overlay.create(config);
overlayRef.backdropClick().subscribe(() => { overlayRef.backdropClick().subscribe(() => {
@ -257,7 +260,11 @@ export class TimewindowComponent implements ControlValueAccessor, OnInit, OnChan
const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); const injector = Injector.create({parent: this.viewContainerRef.injector, providers});
const componentRef = overlayRef.attach(new ComponentPortal(TimewindowPanelComponent, const componentRef = overlayRef.attach(new ComponentPortal(TimewindowPanelComponent,
this.viewContainerRef, injector)); this.viewContainerRef, injector));
const resizeWindows$ = fromEvent(window, 'resize').subscribe(() => {
overlayRef.updatePosition();
});
componentRef.onDestroy(() => { componentRef.onDestroy(() => {
resizeWindows$.unsubscribe();
if (componentRef.instance.result) { if (componentRef.instance.result) {
this.innerValue = componentRef.instance.result; this.innerValue = componentRef.instance.result;
this.timewindowDisabled = this.isTimewindowDisabled(); this.timewindowDisabled = this.isTimewindowDisabled();