UI: Add support long tap in iOS device (show widget/dashboard context menu)
This commit is contained in:
parent
a9670b6830
commit
e87ef55634
@ -25,6 +25,7 @@ import { HttpErrorResponse } from '@angular/common/http';
|
|||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { serverErrorCodesTranslations } from '@shared/models/constants';
|
import { serverErrorCodesTranslations } from '@shared/models/constants';
|
||||||
import { SubscriptionEntityInfo } from '@core/api/widget-api.models';
|
import { SubscriptionEntityInfo } from '@core/api/widget-api.models';
|
||||||
|
import Timeout = NodeJS.Timeout;
|
||||||
|
|
||||||
const varsRegex = /\${([^}]*)}/g;
|
const varsRegex = /\${([^}]*)}/g;
|
||||||
|
|
||||||
@ -894,3 +895,30 @@ export const camelCase = (str: string): string => {
|
|||||||
export const convertKeysToCamelCase = (obj: Record<string, any>): Record<string, any> => {
|
export const convertKeysToCamelCase = (obj: Record<string, any>): Record<string, any> => {
|
||||||
return _.mapKeys(obj, (value, key) => _.camelCase(key));
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@ -22,7 +22,7 @@
|
|||||||
<mat-spinner color="warn" mode="indeterminate" diameter="100">
|
<mat-spinner color="warn" mode="indeterminate" diameter="100">
|
||||||
</mat-spinner>
|
</mat-spinner>
|
||||||
</div>
|
</div>
|
||||||
<div id="gridster-parent"
|
<div id="gridster-parent" #gridsterParent
|
||||||
fxFlex class="tb-dashboard-content" [class.autofill-height]="isAutofillHeight()"
|
fxFlex class="tb-dashboard-content" [class.autofill-height]="isAutofillHeight()"
|
||||||
[class.center-vertical]="centerVertical"
|
[class.center-vertical]="centerVertical"
|
||||||
[class.center-horizontal]="centerHorizontal"
|
[class.center-horizontal]="centerHorizontal"
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import {
|
|||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
DoCheck,
|
DoCheck,
|
||||||
|
ElementRef,
|
||||||
Input,
|
Input,
|
||||||
IterableDiffers,
|
IterableDiffers,
|
||||||
KeyValueDiffers,
|
KeyValueDiffers,
|
||||||
@ -45,7 +46,7 @@ import {
|
|||||||
} from '../../models/dashboard-component.models';
|
} from '../../models/dashboard-component.models';
|
||||||
import { ReplaySubject, Subject, Subscription } from 'rxjs';
|
import { ReplaySubject, Subject, Subscription } from 'rxjs';
|
||||||
import { WidgetLayout, WidgetLayouts } from '@shared/models/dashboard.models';
|
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 { BreakpointObserver } from '@angular/cdk/layout';
|
||||||
import { MediaBreakpoints } from '@shared/models/constants';
|
import { MediaBreakpoints } from '@shared/models/constants';
|
||||||
import { IAliasController, IStateController } from '@app/core/api/widget-api.models';
|
import { IAliasController, IStateController } from '@app/core/api/widget-api.models';
|
||||||
@ -186,18 +187,19 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
|
|||||||
isMobileSize = false;
|
isMobileSize = false;
|
||||||
|
|
||||||
@ViewChild('gridster', {static: true}) gridster: GridsterComponent;
|
@ViewChild('gridster', {static: true}) gridster: GridsterComponent;
|
||||||
|
@ViewChild('gridsterParent', {static: true, read: ElementRef}) gridsterParent: ElementRef<HTMLElement>;
|
||||||
|
|
||||||
@ViewChild('dashboardMenuTrigger', {static: true}) dashboardMenuTrigger: MatMenuTrigger;
|
@ViewChild('dashboardMenuTrigger', {static: true}) dashboardMenuTrigger: MatMenuTrigger;
|
||||||
|
|
||||||
dashboardMenuPosition = { x: '0px', y: '0px' };
|
dashboardMenuPosition = { x: '0px', y: '0px' };
|
||||||
|
|
||||||
dashboardContextMenuEvent: MouseEvent;
|
dashboardContextMenuEvent: MouseEvent | TouchEvent;
|
||||||
|
|
||||||
@ViewChild('widgetMenuTrigger', {static: true}) widgetMenuTrigger: MatMenuTrigger;
|
@ViewChild('widgetMenuTrigger', {static: true}) widgetMenuTrigger: MatMenuTrigger;
|
||||||
|
|
||||||
widgetMenuPosition = { x: '0px', y: '0px' };
|
widgetMenuPosition = { x: '0px', y: '0px' };
|
||||||
|
|
||||||
widgetContextMenuEvent: MouseEvent;
|
widgetContextMenuEvent: MouseEvent | TouchEvent;
|
||||||
|
|
||||||
dashboardWidgets = new DashboardWidgets(this,
|
dashboardWidgets = new DashboardWidgets(this,
|
||||||
this.differs.find([]).create<Widget>((_, item) => item),
|
this.differs.find([]).create<Widget>((_, item) => item),
|
||||||
@ -281,6 +283,10 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.updateWidgets();
|
this.updateWidgets();
|
||||||
|
|
||||||
|
if (isIOSDevice()) {
|
||||||
|
onLongPress(this.gridsterParent.nativeElement, (event) => this.openDashboardContextMenu(event));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
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) {
|
if (this.callbacks && this.callbacks.prepareDashboardContextMenu) {
|
||||||
const items = this.callbacks.prepareDashboardContextMenu($event);
|
const items = this.callbacks.prepareDashboardContextMenu($event);
|
||||||
if (items && items.length) {
|
if (items && items.length) {
|
||||||
$event.preventDefault();
|
$event.preventDefault();
|
||||||
$event.stopPropagation();
|
$event.stopPropagation();
|
||||||
this.dashboardContextMenuEvent = $event;
|
this.dashboardContextMenuEvent = $event;
|
||||||
this.dashboardMenuPosition.x = $event.clientX + 'px';
|
this.dashboardMenuPosition.x = ('touches' in $event ? $event.changedTouches[0].clientX : $event.clientX) + 'px';
|
||||||
this.dashboardMenuPosition.y = $event.clientY + 'px';
|
this.dashboardMenuPosition.y = ('touches' in $event ? $event.changedTouches[0].clientY : $event.clientY) + 'px';
|
||||||
this.dashboardMenuTrigger.menuData = { items };
|
this.dashboardMenuTrigger.menuData = { items };
|
||||||
this.dashboardMenuTrigger.openMenu();
|
this.dashboardMenuTrigger.openMenu();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private openWidgetContextMenu($event: MouseEvent, widget: DashboardWidget) {
|
private openWidgetContextMenu($event: MouseEvent | TouchEvent, widget: DashboardWidget) {
|
||||||
if (this.callbacks && this.callbacks.prepareWidgetContextMenu) {
|
if (this.callbacks && this.callbacks.prepareWidgetContextMenu) {
|
||||||
const items = this.callbacks.prepareWidgetContextMenu($event, widget.widget, widget.isReference);
|
const items = this.callbacks.prepareWidgetContextMenu($event, widget.widget, widget.isReference);
|
||||||
if (items && items.length) {
|
if (items && items.length) {
|
||||||
$event.preventDefault();
|
$event.preventDefault();
|
||||||
$event.stopPropagation();
|
$event.stopPropagation();
|
||||||
this.widgetContextMenuEvent = $event;
|
this.widgetContextMenuEvent = $event;
|
||||||
this.widgetMenuPosition.x = $event.clientX + 'px';
|
this.widgetMenuPosition.x = ('touches' in $event ? $event.changedTouches[0].clientX : $event.clientX) + 'px';
|
||||||
this.widgetMenuPosition.y = $event.clientY + 'px';
|
this.widgetMenuPosition.y = ('touches' in $event ? $event.changedTouches[0].clientY : $event.clientY) + 'px';
|
||||||
this.widgetMenuTrigger.menuData = { items, widget: widget.widget };
|
this.widgetMenuTrigger.menuData = { items, widget: widget.widget };
|
||||||
this.widgetMenuTrigger.openMenu();
|
this.widgetMenuTrigger.openMenu();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,7 +39,7 @@ import { DashboardWidget, DashboardWidgets } from '@home/models/dashboard-compon
|
|||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { AppState } from '@core/core.state';
|
import { AppState } from '@core/core.state';
|
||||||
import { SafeStyle } from '@angular/platform-browser';
|
import { SafeStyle } from '@angular/platform-browser';
|
||||||
import { isNotEmptyStr } from '@core/utils';
|
import { isIOSDevice, isNotEmptyStr, onLongPress } from '@core/utils';
|
||||||
import { GridsterItemComponent } from 'angular-gridster2';
|
import { GridsterItemComponent } from 'angular-gridster2';
|
||||||
import { UtilsService } from '@core/services/utils.service';
|
import { UtilsService } from '@core/services/utils.service';
|
||||||
import { from } from 'rxjs';
|
import { from } from 'rxjs';
|
||||||
@ -57,7 +57,7 @@ export enum WidgetComponentActionType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class WidgetComponentAction {
|
export class WidgetComponentAction {
|
||||||
event: MouseEvent;
|
event: MouseEvent | TouchEvent;
|
||||||
actionType: WidgetComponentActionType;
|
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('mousedown', (e) => this.onMouseDown(e.originalEvent));
|
||||||
$(this.gridsterItem.el).on('click', (e) => this.onClicked(e.originalEvent));
|
$(this.gridsterItem.el).on('click', (e) => this.onClicked(e.originalEvent));
|
||||||
$(this.gridsterItem.el).on('contextmenu', (e) => this.onContextMenu(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;
|
const dashboardContentElement = this.widget.widgetContext.dashboardContentElement;
|
||||||
if (dashboardContentElement) {
|
if (dashboardContentElement) {
|
||||||
this.initEditWidgetActionTooltip(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({
|
this.widgetComponentAction.emit({
|
||||||
event,
|
event,
|
||||||
actionType: WidgetComponentActionType.CONTEXT_MENU
|
actionType: WidgetComponentActionType.CONTEXT_MENU
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user