mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-08-14 21:00:37 +03:00
B: Tentative fix for longpress on iOS
This commit is contained in:
parent
91186b0e2e
commit
4207a52b2d
|
@ -19,18 +19,13 @@ export function useContextMenu() {
|
||||||
const setHoverOperation = useOperationTooltipStore(state => state.setHoverItem);
|
const setHoverOperation = useOperationTooltipStore(state => state.setHoverItem);
|
||||||
const { addSelectedNodes } = useStoreApi().getState();
|
const { addSelectedNodes } = useStoreApi().getState();
|
||||||
|
|
||||||
function handleContextMenu(event: React.MouseEvent<Element>, node: OssNode) {
|
function openContextMenu(node: OssNode, clientX: number, clientY: number) {
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
addSelectedNodes([node.id]);
|
addSelectedNodes([node.id]);
|
||||||
|
|
||||||
setMenuProps({
|
setMenuProps({
|
||||||
item: node.type === 'block' ? node.data.block ?? null : node.data.operation ?? null,
|
item: node.type === 'block' ? node.data.block ?? null : node.data.operation ?? null,
|
||||||
cursorX: event.clientX,
|
cursorX: clientX,
|
||||||
cursorY: event.clientY
|
cursorY: clientY
|
||||||
});
|
});
|
||||||
|
|
||||||
setIsOpen(true);
|
setIsOpen(true);
|
||||||
setHoverOperation(null);
|
setHoverOperation(null);
|
||||||
}
|
}
|
||||||
|
@ -42,7 +37,7 @@ export function useContextMenu() {
|
||||||
return {
|
return {
|
||||||
isOpen,
|
isOpen,
|
||||||
menuProps,
|
menuProps,
|
||||||
handleContextMenu,
|
openContextMenu,
|
||||||
hideContextMenu
|
hideContextMenu
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
import { DiagramFlow, useReactFlow, useStoreApi } from '@/components/flow/diagram-flow';
|
import { DiagramFlow, useReactFlow, useStoreApi } from '@/components/flow/diagram-flow';
|
||||||
|
@ -8,6 +8,7 @@ import { useMainHeight } from '@/stores/app-layout';
|
||||||
import { useDialogsStore } from '@/stores/dialogs';
|
import { useDialogsStore } from '@/stores/dialogs';
|
||||||
import { PARAMETER } from '@/utils/constants';
|
import { PARAMETER } from '@/utils/constants';
|
||||||
import { promptText } from '@/utils/labels';
|
import { promptText } from '@/utils/labels';
|
||||||
|
import { isIOS } from '@/utils/utils';
|
||||||
|
|
||||||
import { useDeleteBlock } from '../../../backend/use-delete-block';
|
import { useDeleteBlock } from '../../../backend/use-delete-block';
|
||||||
import { useMutatingOss } from '../../../backend/use-mutating-oss';
|
import { useMutatingOss } from '../../../backend/use-mutating-oss';
|
||||||
|
@ -24,7 +25,7 @@ import { OssNodeTypes } from './graph/oss-node-types';
|
||||||
import { CoordinateDisplay } from './coordinate-display';
|
import { CoordinateDisplay } from './coordinate-display';
|
||||||
import { useOssFlow } from './oss-flow-context';
|
import { useOssFlow } from './oss-flow-context';
|
||||||
import { ToolbarOssGraph } from './toolbar-oss-graph';
|
import { ToolbarOssGraph } from './toolbar-oss-graph';
|
||||||
import { useDragging } from './use-dragging';
|
// import { useDragging } from './use-dragging';
|
||||||
import { useGetLayout } from './use-get-layout';
|
import { useGetLayout } from './use-get-layout';
|
||||||
|
|
||||||
export const flowOptions = {
|
export const flowOptions = {
|
||||||
|
@ -64,8 +65,11 @@ export function OssFlow() {
|
||||||
const showDeleteOperation = useDialogsStore(state => state.showDeleteOperation);
|
const showDeleteOperation = useDialogsStore(state => state.showDeleteOperation);
|
||||||
const showEditBlock = useDialogsStore(state => state.showEditBlock);
|
const showEditBlock = useDialogsStore(state => state.showEditBlock);
|
||||||
|
|
||||||
const { isOpen: isContextMenuOpen, menuProps, handleContextMenu, hideContextMenu } = useContextMenu();
|
const { isOpen: isContextMenuOpen, menuProps, openContextMenu, hideContextMenu } = useContextMenu();
|
||||||
const { handleDragStart, handleDrag, handleDragStop } = useDragging({ hideContextMenu });
|
// const { handleDragStart, handleDrag, handleDragStop } = useDragging({ hideContextMenu });
|
||||||
|
|
||||||
|
const longPressTimeout = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
const longPressTarget = useRef<EventTarget | null>(null);
|
||||||
|
|
||||||
function handleSavePositions() {
|
function handleSavePositions() {
|
||||||
void updateLayout({ itemID: schema.id, data: getLayout() });
|
void updateLayout({ itemID: schema.id, data: getLayout() });
|
||||||
|
@ -123,6 +127,7 @@ export function OssFlow() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleNodeDoubleClick(event: React.MouseEvent<Element>, node: OssNode) {
|
function handleNodeDoubleClick(event: React.MouseEvent<Element>, node: OssNode) {
|
||||||
|
console.log('handleNodeDoubleClick', event, node);
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
|
@ -183,6 +188,56 @@ export function OssFlow() {
|
||||||
setMouseCoords(targetPosition);
|
setMouseCoords(targetPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleNodeContextMenu(event: React.MouseEvent<Element>, node: OssNode) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
openContextMenu(node, event.clientX, event.clientY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTouchStart(event: React.TouchEvent) {
|
||||||
|
console.log('handleTouchStart', event);
|
||||||
|
if (!isIOS() || event.touches.length !== 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Long-press support for iOS/iPadOS
|
||||||
|
const touch = event.touches[0];
|
||||||
|
longPressTarget.current = touch.target;
|
||||||
|
longPressTimeout.current = setTimeout(() => {
|
||||||
|
let targetID = null;
|
||||||
|
let element = touch.target as HTMLElement | null;
|
||||||
|
while (element) {
|
||||||
|
if (element?.getAttribute?.('data-id')) {
|
||||||
|
targetID = element.getAttribute('data-id');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
element = element.parentElement;
|
||||||
|
}
|
||||||
|
if (targetID) {
|
||||||
|
const targetNode = nodes.find(node => node.id === targetID);
|
||||||
|
if (targetNode) {
|
||||||
|
openContextMenu(targetNode, touch.clientX, touch.clientY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, PARAMETER.ossContextMenuDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTouchEnd() {
|
||||||
|
if (!isIOS()) return;
|
||||||
|
if (longPressTimeout.current) {
|
||||||
|
clearTimeout(longPressTimeout.current);
|
||||||
|
longPressTimeout.current = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTouchMove() {
|
||||||
|
if (!isIOS()) return;
|
||||||
|
if (longPressTimeout.current) {
|
||||||
|
clearTimeout(longPressTimeout.current);
|
||||||
|
longPressTimeout.current = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div tabIndex={-1} className='relative' onMouseMove={showCoordinates ? handleMouseMove : undefined}>
|
<div tabIndex={-1} className='relative' onMouseMove={showCoordinates ? handleMouseMove : undefined}>
|
||||||
{showCoordinates ? <CoordinateDisplay mouseCoords={mouseCoords} className='absolute top-1 right-2' /> : null}
|
{showCoordinates ? <CoordinateDisplay mouseCoords={mouseCoords} className='absolute top-1 right-2' /> : null}
|
||||||
|
@ -209,16 +264,20 @@ export function OssFlow() {
|
||||||
showGrid={showGrid}
|
showGrid={showGrid}
|
||||||
onClick={hideContextMenu}
|
onClick={hideContextMenu}
|
||||||
onNodeDoubleClick={handleNodeDoubleClick}
|
onNodeDoubleClick={handleNodeDoubleClick}
|
||||||
onNodeContextMenu={handleContextMenu}
|
onNodeContextMenu={handleNodeContextMenu}
|
||||||
onContextMenu={hideContextMenu}
|
onContextMenu={hideContextMenu}
|
||||||
onNodeDragStart={handleDragStart}
|
onTouchStart={handleTouchStart}
|
||||||
onNodeDrag={handleDrag}
|
onTouchEnd={handleTouchEnd}
|
||||||
onNodeDragStop={handleDragStop}
|
onTouchMove={handleTouchMove}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// onNodeDragStart={handleDragStart}
|
||||||
|
// onNodeDrag={handleDrag}
|
||||||
|
// onNodeDragStop={handleDragStop}
|
||||||
|
|
||||||
// -------- Internals --------
|
// -------- Internals --------
|
||||||
function extractBlockParent(selectedItems: IOssItem[]): number | null {
|
function extractBlockParent(selectedItems: IOssItem[]): number | null {
|
||||||
if (selectedItems.length === 1 && selectedItems[0].nodeType === NodeType.BLOCK) {
|
if (selectedItems.length === 1 && selectedItems[0].nodeType === NodeType.BLOCK) {
|
||||||
|
|
|
@ -42,6 +42,7 @@ export function useDragging({ hideContextMenu }: DraggingProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDragStart(event: React.MouseEvent, target: Node) {
|
function handleDragStart(event: React.MouseEvent, target: Node) {
|
||||||
|
console.log('handleDragStart', event);
|
||||||
if (event.shiftKey) {
|
if (event.shiftKey) {
|
||||||
setContainMovement(true);
|
setContainMovement(true);
|
||||||
applyContainMovement([target.id, ...selected], true);
|
applyContainMovement([target.id, ...selected], true);
|
||||||
|
|
|
@ -11,6 +11,7 @@ export const PARAMETER = {
|
||||||
notificationDelay: 300, // milliseconds delay for notifications
|
notificationDelay: 300, // milliseconds delay for notifications
|
||||||
zoomDuration: 500, // milliseconds animation duration
|
zoomDuration: 500, // milliseconds animation duration
|
||||||
navigationPopupDelay: 300, // milliseconds delay for navigation popup
|
navigationPopupDelay: 300, // milliseconds delay for navigation popup
|
||||||
|
ossContextMenuDuration: 500, // milliseconds - duration of long-press to trigger context menu on iOS/iPadOS
|
||||||
|
|
||||||
moveDuration: 500, // milliseconds - duration of move animation
|
moveDuration: 500, // milliseconds - duration of move animation
|
||||||
|
|
||||||
|
|
|
@ -192,3 +192,16 @@ export function removeTags(target?: string): string {
|
||||||
export function prepareTooltip(text: string, hotkey?: string) {
|
export function prepareTooltip(text: string, hotkey?: string) {
|
||||||
return hotkey ? `<kbd>[${hotkey}]</kbd><br/>${text}` : text;
|
return hotkey ? `<kbd>[${hotkey}]</kbd><br/>${text}` : text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility to detect iOS/iPadOS.
|
||||||
|
*/
|
||||||
|
export function isIOS() {
|
||||||
|
if (typeof navigator === 'undefined') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
/iPad|iPhone|iPod/.test(navigator.userAgent) ||
|
||||||
|
(navigator.userAgent.includes('Macintosh') && 'ontouchend' in document)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user