UI: Add support long tap in iOS device (show widget/dashboard context menu)

This commit is contained in:
Vladyslav_Prykhodko 2024-09-24 13:13:29 +03:00
parent a9670b6830
commit e87ef55634
4 changed files with 52 additions and 14 deletions

View File

@ -25,6 +25,7 @@ import { HttpErrorResponse } from '@angular/common/http';
import { TranslateService } from '@ngx-translate/core';
import { serverErrorCodesTranslations } from '@shared/models/constants';
import { SubscriptionEntityInfo } from '@core/api/widget-api.models';
import Timeout = NodeJS.Timeout;
const varsRegex = /\${([^}]*)}/g;
@ -894,3 +895,30 @@ export const camelCase = (str: string): string => {
export const convertKeysToCamelCase = (obj: Record<string, any>): Record<string, any> => {
return _.mapKeys(obj, (value, key) => _.camelCase(key));
};
export const isIOSDevice = (): boolean =>
/iPhone|iPad|iPod/i.test(navigator.userAgent) || (navigator.userAgent.includes('Mac') && 'ontouchend' in document);
export const onLongPress = (element: HTMLElement, callback: (event: TouchEvent) => void) => {
let timeoutId: Timeout;
$(element).on('touchstart', (e) => {
timeoutId = setTimeout(() => {
timeoutId = null;
e.stopPropagation();
callback(e.originalEvent);
}, 500);
});
$(element).on('contextmenu', (e) => e.preventDefault());
$(element).on('touchend', (e) => {
if (timeoutId) {
clearTimeout(timeoutId);
}
});
$(element).on('touchmove', (e) => {
if (timeoutId) {
clearTimeout(timeoutId);
}
});
};

View File

@ -22,7 +22,7 @@
<mat-spinner color="warn" mode="indeterminate" diameter="100">
</mat-spinner>
</div>
<div id="gridster-parent"
<div id="gridster-parent" #gridsterParent
fxFlex class="tb-dashboard-content" [class.autofill-height]="isAutofillHeight()"
[class.center-vertical]="centerVertical"
[class.center-horizontal]="centerHorizontal"

View File

@ -19,6 +19,7 @@ import {
ChangeDetectionStrategy,
Component,
DoCheck,
ElementRef,
Input,
IterableDiffers,
KeyValueDiffers,
@ -45,7 +46,7 @@ import {
} from '../../models/dashboard-component.models';
import { ReplaySubject, Subject, Subscription } from 'rxjs';
import { WidgetLayout, WidgetLayouts } from '@shared/models/dashboard.models';
import { animatedScroll, deepClone, isDefined } from '@app/core/utils';
import { animatedScroll, deepClone, isDefined, isIOSDevice, onLongPress } from '@app/core/utils';
import { BreakpointObserver } from '@angular/cdk/layout';
import { MediaBreakpoints } from '@shared/models/constants';
import { IAliasController, IStateController } from '@app/core/api/widget-api.models';
@ -186,18 +187,19 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
isMobileSize = false;
@ViewChild('gridster', {static: true}) gridster: GridsterComponent;
@ViewChild('gridsterParent', {static: true, read: ElementRef}) gridsterParent: ElementRef<HTMLElement>;
@ViewChild('dashboardMenuTrigger', {static: true}) dashboardMenuTrigger: MatMenuTrigger;
dashboardMenuPosition = { x: '0px', y: '0px' };
dashboardContextMenuEvent: MouseEvent;
dashboardContextMenuEvent: MouseEvent | TouchEvent;
@ViewChild('widgetMenuTrigger', {static: true}) widgetMenuTrigger: MatMenuTrigger;
widgetMenuPosition = { x: '0px', y: '0px' };
widgetContextMenuEvent: MouseEvent;
widgetContextMenuEvent: MouseEvent | TouchEvent;
dashboardWidgets = new DashboardWidgets(this,
this.differs.find([]).create<Widget>((_, item) => item),
@ -281,6 +283,10 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
);
this.updateWidgets();
if (isIOSDevice()) {
onLongPress(this.gridsterParent.nativeElement, (event) => this.openDashboardContextMenu(event));
}
}
ngOnDestroy(): void {
@ -406,30 +412,30 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
}
}
openDashboardContextMenu($event: MouseEvent) {
openDashboardContextMenu($event: MouseEvent | TouchEvent) {
if (this.callbacks && this.callbacks.prepareDashboardContextMenu) {
const items = this.callbacks.prepareDashboardContextMenu($event);
if (items && items.length) {
$event.preventDefault();
$event.stopPropagation();
this.dashboardContextMenuEvent = $event;
this.dashboardMenuPosition.x = $event.clientX + 'px';
this.dashboardMenuPosition.y = $event.clientY + 'px';
this.dashboardMenuPosition.x = ('touches' in $event ? $event.changedTouches[0].clientX : $event.clientX) + 'px';
this.dashboardMenuPosition.y = ('touches' in $event ? $event.changedTouches[0].clientY : $event.clientY) + 'px';
this.dashboardMenuTrigger.menuData = { items };
this.dashboardMenuTrigger.openMenu();
}
}
}
private openWidgetContextMenu($event: MouseEvent, widget: DashboardWidget) {
private openWidgetContextMenu($event: MouseEvent | TouchEvent, widget: DashboardWidget) {
if (this.callbacks && this.callbacks.prepareWidgetContextMenu) {
const items = this.callbacks.prepareWidgetContextMenu($event, widget.widget, widget.isReference);
if (items && items.length) {
$event.preventDefault();
$event.stopPropagation();
this.widgetContextMenuEvent = $event;
this.widgetMenuPosition.x = $event.clientX + 'px';
this.widgetMenuPosition.y = $event.clientY + 'px';
this.widgetMenuPosition.x = ('touches' in $event ? $event.changedTouches[0].clientX : $event.clientX) + 'px';
this.widgetMenuPosition.y = ('touches' in $event ? $event.changedTouches[0].clientY : $event.clientY) + 'px';
this.widgetMenuTrigger.menuData = { items, widget: widget.widget };
this.widgetMenuTrigger.openMenu();
}

View File

@ -39,7 +39,7 @@ import { DashboardWidget, DashboardWidgets } from '@home/models/dashboard-compon
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { SafeStyle } from '@angular/platform-browser';
import { isNotEmptyStr } from '@core/utils';
import { isIOSDevice, isNotEmptyStr, onLongPress } from '@core/utils';
import { GridsterItemComponent } from 'angular-gridster2';
import { UtilsService } from '@core/services/utils.service';
import { from } from 'rxjs';
@ -57,7 +57,7 @@ export enum WidgetComponentActionType {
}
export class WidgetComponentAction {
event: MouseEvent;
event: MouseEvent | TouchEvent;
actionType: WidgetComponentActionType;
}
@ -151,7 +151,11 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O
}
$(this.gridsterItem.el).on('mousedown', (e) => this.onMouseDown(e.originalEvent));
$(this.gridsterItem.el).on('click', (e) => this.onClicked(e.originalEvent));
if (isIOSDevice()) {
onLongPress(this.gridsterItem.el, (event) => this.onContextMenu(event));
} else {
$(this.gridsterItem.el).on('contextmenu', (e) => this.onContextMenu(e.originalEvent));
}
const dashboardContentElement = this.widget.widgetContext.dashboardContentElement;
if (dashboardContentElement) {
this.initEditWidgetActionTooltip(dashboardContentElement);
@ -213,7 +217,7 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O
});
}
onContextMenu(event: MouseEvent) {
onContextMenu(event: MouseEvent | TouchEvent) {
this.widgetComponentAction.emit({
event,
actionType: WidgetComponentActionType.CONTEXT_MENU