Merge pull request #11735 from vvlladd28/improvement/dashboard/ios-support-context-menu
Add support long tap in iOS device (show widget/dashboard contexе menu)
This commit is contained in:
commit
21fb4afb24
@ -59,7 +59,7 @@
|
||||
"flot.curvedlines": "https://github.com/MichaelZinsmaier/CurvedLines.git#master",
|
||||
"font-awesome": "^4.7.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"jquery": "^3.6.3",
|
||||
"jquery": "^3.7.1",
|
||||
"jquery.terminal": "^2.35.3",
|
||||
"js-beautify": "1.14.7",
|
||||
"json-schema-defaults": "^0.4.0",
|
||||
@ -127,7 +127,7 @@
|
||||
"@types/flowjs": "^2.13.9",
|
||||
"@types/jasmine": "~3.10.2",
|
||||
"@types/jasminewd2": "^2.0.10",
|
||||
"@types/jquery": "^3.5.16",
|
||||
"@types/jquery": "^3.5.30",
|
||||
"@types/js-beautify": "^1.13.3",
|
||||
"@types/leaflet": "1.8.0",
|
||||
"@types/leaflet-polylinedecorator": "1.6.4",
|
||||
|
||||
@ -32,6 +32,7 @@ import { AuthService } from '@core/auth/auth.service';
|
||||
import { svgIcons, svgIconsUrl } from '@shared/models/icon.models';
|
||||
import { ActionSettingsChangeLanguage } from '@core/settings/settings.actions';
|
||||
import { SETTINGS_KEY } from '@core/settings/settings.effects';
|
||||
import { initCustomJQueryEvents } from '@shared/models/jquery-event.models';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-root',
|
||||
@ -74,6 +75,8 @@ export class AppComponent implements OnInit {
|
||||
|
||||
this.setupTranslate();
|
||||
this.setupAuth();
|
||||
|
||||
initCustomJQueryEvents();
|
||||
}
|
||||
|
||||
setupTranslate() {
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
[class.center-vertical]="centerVertical"
|
||||
[class.center-horizontal]="centerHorizontal"
|
||||
(mousedown)="onDashboardMouseDown($event)"
|
||||
(contextmenu)="openDashboardContextMenu($event)">
|
||||
(tbcontextmenu)="openDashboardContextMenu($event)">
|
||||
<div #dashboardMenuTrigger="matMenuTrigger" style="visibility: hidden; position: fixed"
|
||||
[style.left]="dashboardMenuPosition.x"
|
||||
[style.top]="dashboardMenuPosition.y"
|
||||
|
||||
@ -58,6 +58,7 @@ import { WidgetComponentAction, WidgetComponentActionType } from '@home/componen
|
||||
import { TbPopoverComponent } from '@shared/components/popover.component';
|
||||
import { displayGrids } from 'angular-gridster2/lib/gridsterConfig.interface';
|
||||
import { coerceBoolean } from '@shared/decorators/coercion';
|
||||
import { TbContextMenuEvent } from '@shared/models/jquery-event.models';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-dashboard',
|
||||
@ -187,13 +188,13 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
|
||||
|
||||
dashboardMenuPosition = { x: '0px', y: '0px' };
|
||||
|
||||
dashboardContextMenuEvent: MouseEvent;
|
||||
dashboardContextMenuEvent: TbContextMenuEvent;
|
||||
|
||||
@ViewChild('widgetMenuTrigger', {static: true}) widgetMenuTrigger: MatMenuTrigger;
|
||||
|
||||
widgetMenuPosition = { x: '0px', y: '0px' };
|
||||
|
||||
widgetContextMenuEvent: MouseEvent;
|
||||
widgetContextMenuEvent: TbContextMenuEvent;
|
||||
|
||||
dashboardWidgets = new DashboardWidgets(this,
|
||||
this.differs.find([]).create<Widget>((_, item) => item),
|
||||
@ -395,7 +396,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
|
||||
}
|
||||
}
|
||||
|
||||
openDashboardContextMenu($event: MouseEvent) {
|
||||
openDashboardContextMenu($event: TbContextMenuEvent) {
|
||||
if (this.callbacks && this.callbacks.prepareDashboardContextMenu) {
|
||||
const items = this.callbacks.prepareDashboardContextMenu($event);
|
||||
if (items && items.length) {
|
||||
@ -410,7 +411,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
|
||||
}
|
||||
}
|
||||
|
||||
private openWidgetContextMenu($event: MouseEvent, widget: DashboardWidget) {
|
||||
private openWidgetContextMenu($event: TbContextMenuEvent, widget: DashboardWidget) {
|
||||
if (this.callbacks && this.callbacks.prepareWidgetContextMenu) {
|
||||
const items = this.callbacks.prepareWidgetContextMenu($event, widget.widget, widget.isReference);
|
||||
if (items && items.length) {
|
||||
|
||||
@ -45,6 +45,7 @@ import { UtilsService } from '@core/services/utils.service';
|
||||
import { from } from 'rxjs';
|
||||
import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
|
||||
import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance;
|
||||
import { TbContextMenuEvent } from '@shared/models/jquery-event.models';
|
||||
|
||||
export enum WidgetComponentActionType {
|
||||
MOUSE_DOWN,
|
||||
@ -57,7 +58,7 @@ export enum WidgetComponentActionType {
|
||||
}
|
||||
|
||||
export class WidgetComponentAction {
|
||||
event: MouseEvent;
|
||||
event: MouseEvent | TbContextMenuEvent;
|
||||
actionType: WidgetComponentActionType;
|
||||
}
|
||||
|
||||
@ -151,7 +152,7 @@ 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));
|
||||
$(this.gridsterItem.el).on('contextmenu', (e) => this.onContextMenu(e.originalEvent));
|
||||
$(this.gridsterItem.el).on('tbcontextmenu', (e: TbContextMenuEvent) => this.onContextMenu(e));
|
||||
const dashboardContentElement = this.widget.widgetContext.dashboardContentElement;
|
||||
if (dashboardContentElement) {
|
||||
this.initEditWidgetActionTooltip(dashboardContentElement);
|
||||
@ -180,6 +181,9 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O
|
||||
if (this.editWidgetActionsTooltip && !this.editWidgetActionsTooltip.status().destroyed) {
|
||||
this.editWidgetActionsTooltip.destroy();
|
||||
}
|
||||
$(this.gridsterItem.el).off('mousedown');
|
||||
$(this.gridsterItem.el).off('click');
|
||||
$(this.gridsterItem.el).off('tbcontextmenu');
|
||||
}
|
||||
|
||||
isHighlighted(widget: DashboardWidget) {
|
||||
@ -219,7 +223,7 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O
|
||||
});
|
||||
}
|
||||
|
||||
onContextMenu(event: MouseEvent) {
|
||||
onContextMenu(event: TbContextMenuEvent) {
|
||||
if (event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
@ -185,7 +185,7 @@
|
||||
matTooltipPosition="above">
|
||||
<mat-icon>{{ isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>
|
||||
</button>
|
||||
<div class="tb-absolute-fill tb-rulechain-graph" (contextmenu)="openRuleChainContextMenu($event)">
|
||||
<div class="tb-absolute-fill tb-rulechain-graph" (tbcontextmenu)="openRuleChainContextMenu($event)">
|
||||
<div #ruleChainMenuTrigger="matMenuTrigger" style="visibility: hidden; position: fixed"
|
||||
[style.left]="ruleChainMenuPosition.x"
|
||||
[style.top]="ruleChainMenuPosition.y"
|
||||
|
||||
@ -94,6 +94,7 @@ import { VersionControlComponent } from '@home/components/vc/version-control.com
|
||||
import { ComponentClusteringMode } from '@shared/models/component-descriptor.models';
|
||||
import { MatDrawer } from '@angular/material/sidenav';
|
||||
import { HttpStatusCode } from '@angular/common/http';
|
||||
import { TbContextMenuEvent } from '@shared/models/jquery-event.models';
|
||||
import Timeout = NodeJS.Timeout;
|
||||
|
||||
@Component({
|
||||
@ -131,7 +132,7 @@ export class RuleChainPageComponent extends PageComponent
|
||||
|
||||
ruleChainMenuPosition = { x: '0px', y: '0px' };
|
||||
|
||||
contextMenuEvent: MouseEvent;
|
||||
contextMenuEvent: TbContextMenuEvent;
|
||||
|
||||
ruleNodeTypeDescriptorsMap = ruleNodeTypeDescriptors;
|
||||
ruleNodeTypesLibraryArray = ruleNodeTypesLibrary;
|
||||
@ -657,7 +658,7 @@ export class RuleChainPageComponent extends PageComponent
|
||||
this.validate();
|
||||
}
|
||||
|
||||
openRuleChainContextMenu($event: MouseEvent) {
|
||||
openRuleChainContextMenu($event: TbContextMenuEvent) {
|
||||
if (this.ruleChainCanvas.modelService && !$event.ctrlKey && !$event.metaKey) {
|
||||
const x = $event.clientX;
|
||||
const y = $event.clientY;
|
||||
|
||||
35
ui-ngx/src/app/shared/directives/context-menu.directive.ts
Normal file
35
ui-ngx/src/app/shared/directives/context-menu.directive.ts
Normal file
@ -0,0 +1,35 @@
|
||||
///
|
||||
/// Copyright © 2016-2024 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 { Directive, ElementRef, EventEmitter, OnDestroy, Output } from '@angular/core';
|
||||
import { TbContextMenuEvent } from '@shared/models/jquery-event.models';
|
||||
|
||||
@Directive({
|
||||
selector: '[tbcontextmenu]'
|
||||
})
|
||||
export class ContextMenuDirective implements OnDestroy {
|
||||
|
||||
@Output()
|
||||
tbcontextmenu = new EventEmitter<TbContextMenuEvent>();
|
||||
|
||||
constructor(private el: ElementRef) {
|
||||
$(this.el.nativeElement).on('tbcontextmenu', (e: TbContextMenuEvent) => this.tbcontextmenu.emit(e));
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
$(this.el.nativeElement).off('tbcontextmenu');
|
||||
}
|
||||
}
|
||||
@ -16,3 +16,4 @@
|
||||
|
||||
export * from './truncate-with-tooltip.directive';
|
||||
export * from './ellipsis-chip-list.directive';
|
||||
export * from './context-menu.directive';
|
||||
|
||||
80
ui-ngx/src/app/shared/models/jquery-event.models.ts
Normal file
80
ui-ngx/src/app/shared/models/jquery-event.models.ts
Normal file
@ -0,0 +1,80 @@
|
||||
///
|
||||
/// Copyright © 2016-2024 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 Timeout = NodeJS.Timeout;
|
||||
|
||||
export interface TbContextMenuEvent extends Event {
|
||||
clientX: number;
|
||||
clientY: number;
|
||||
ctrlKey: boolean;
|
||||
metaKey: boolean;
|
||||
}
|
||||
|
||||
const isIOSDevice = (): boolean =>
|
||||
/iPhone|iPad|iPod/i.test(navigator.userAgent) || (navigator.userAgent.includes('Mac') && 'ontouchend' in document);
|
||||
|
||||
export const initCustomJQueryEvents = () => {
|
||||
$.event.special.tbcontextmenu = {
|
||||
setup(this: HTMLElement) {
|
||||
const el = $(this);
|
||||
if (isIOSDevice()) {
|
||||
let timeoutId: Timeout;
|
||||
|
||||
el.on('touchstart', (e) => {
|
||||
e.stopPropagation();
|
||||
timeoutId = setTimeout(() => {
|
||||
timeoutId = null;
|
||||
e.stopPropagation();
|
||||
const touch = e.originalEvent.changedTouches[0];
|
||||
const event = $.Event('tbcontextmenu', {
|
||||
clientX: touch.clientX,
|
||||
clientY: touch.clientY,
|
||||
ctrlKey: false,
|
||||
metaKey: false
|
||||
});
|
||||
el.trigger(event, e);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
el.on('touchend touchmove', () => {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
el.on('contextmenu', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const event = $.Event('tbcontextmenu', {
|
||||
clientX: e.originalEvent.clientX,
|
||||
clientY: e.originalEvent.clientY,
|
||||
ctrlKey: e.originalEvent.ctrlKey,
|
||||
metaKey: e.originalEvent.metaKey,
|
||||
});
|
||||
el.trigger(event, e);
|
||||
});
|
||||
}
|
||||
},
|
||||
teardown(this: HTMLElement) {
|
||||
const el = $(this);
|
||||
if (isIOSDevice()) {
|
||||
el.off('touchstart touchend touchmove');
|
||||
} else {
|
||||
el.off('contextmenu');
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
@ -68,6 +68,7 @@ import { NgxHmCarouselModule } from 'ngx-hm-carousel';
|
||||
import { EditorModule, TINYMCE_SCRIPT_SRC } from '@tinymce/tinymce-angular';
|
||||
import { UserMenuComponent } from '@shared/components/user-menu.component';
|
||||
import { TruncateWithTooltipDirective } from '@shared/directives/truncate-with-tooltip.directive';
|
||||
import { ContextMenuDirective } from '@shared/directives/context-menu.directive';
|
||||
import { NospacePipe } from '@shared/pipe/nospace.pipe';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { TbCheckboxComponent } from '@shared/components/tb-checkbox.component';
|
||||
@ -371,6 +372,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
|
||||
LedLightComponent,
|
||||
MarkdownEditorComponent,
|
||||
TruncateWithTooltipDirective,
|
||||
ContextMenuDirective,
|
||||
NospacePipe,
|
||||
MillisecondsToTimeStringPipe,
|
||||
EnumToArrayPipe,
|
||||
@ -633,6 +635,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
|
||||
LedLightComponent,
|
||||
MarkdownEditorComponent,
|
||||
TruncateWithTooltipDirective,
|
||||
ContextMenuDirective,
|
||||
NospacePipe,
|
||||
MillisecondsToTimeStringPipe,
|
||||
EnumToArrayPipe,
|
||||
|
||||
3
ui-ngx/src/typings/jquery.typings.d.ts
vendored
3
ui-ngx/src/typings/jquery.typings.d.ts
vendored
@ -14,6 +14,9 @@
|
||||
/// limitations under the License.
|
||||
///
|
||||
|
||||
import { TbContextMenuEvent } from '@shared/models/jquery-event.models';
|
||||
|
||||
interface JQuery {
|
||||
terminal(options?: any): any;
|
||||
on(events: 'tbcontextmenu', handler: (e: TbContextMenuEvent) => void): this;
|
||||
}
|
||||
|
||||
@ -3052,10 +3052,10 @@
|
||||
dependencies:
|
||||
"@types/jasmine" "*"
|
||||
|
||||
"@types/jquery@*", "@types/jquery@^3.5.16", "@types/jquery@^3.5.29":
|
||||
version "3.5.29"
|
||||
resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.5.29.tgz#3c06a1f519cd5fc3a7a108971436c00685b5dcea"
|
||||
integrity sha512-oXQQC9X9MOPRrMhPHHOsXqeQDnWeCDT3PelUIg/Oy8FAbzSZtFHRjc7IpbfFVmpLtJ+UOoywpRsuO5Jxjybyeg==
|
||||
"@types/jquery@*", "@types/jquery@^3.5.29", "@types/jquery@^3.5.30":
|
||||
version "3.5.30"
|
||||
resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.5.30.tgz#888d584cbf844d3df56834b69925085038fd80f7"
|
||||
integrity sha512-nbWKkkyb919DOUxjmRVk8vwtDb0/k8FKncmUKFi+NY+QXqWltooxTrswvz4LspQwxvLdvzBN1TImr6cw3aQx2A==
|
||||
dependencies:
|
||||
"@types/sizzle" "*"
|
||||
|
||||
@ -7377,7 +7377,7 @@ jquery.terminal@^2.35.3:
|
||||
optionalDependencies:
|
||||
fsevents "^2.3.2"
|
||||
|
||||
jquery@>=1.9.1, jquery@^3.5.0, jquery@^3.6.3, jquery@^3.7.1:
|
||||
jquery@>=1.9.1, jquery@^3.5.0, jquery@^3.7.1:
|
||||
version "3.7.1"
|
||||
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.7.1.tgz#083ef98927c9a6a74d05a6af02806566d16274de"
|
||||
integrity sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user