From 976ab669e71aecb8712505f17ab04b2c2a44eb3b Mon Sep 17 00:00:00 2001 From: Ivan <8611739+IRBorisov@users.noreply.github.com> Date: Tue, 1 Jul 2025 12:21:10 +0300 Subject: [PATCH] B: Tentative fix for longpress on iOS --- .../context-menu/use-context-menu.ts | 13 ++-- .../editor-oss-graph/graph/node-core.tsx | 5 ++ .../oss-page/editor-oss-graph/oss-flow.tsx | 62 ++++++++++++++++++- rsconcept/frontend/src/utils/constants.ts | 1 + rsconcept/frontend/src/utils/utils.ts | 13 ++++ 5 files changed, 82 insertions(+), 12 deletions(-) diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/context-menu/use-context-menu.ts b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/context-menu/use-context-menu.ts index b07a5b95..cb62ff79 100644 --- a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/context-menu/use-context-menu.ts +++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/context-menu/use-context-menu.ts @@ -19,18 +19,13 @@ export function useContextMenu() { const setHoverOperation = useOperationTooltipStore(state => state.setHoverItem); const { addSelectedNodes } = useStoreApi().getState(); - function handleContextMenu(event: React.MouseEvent, node: OssNode) { - event.preventDefault(); - event.stopPropagation(); - + function openContextMenu(node: OssNode, clientX: number, clientY: number) { addSelectedNodes([node.id]); - setMenuProps({ item: node.type === 'block' ? node.data.block ?? null : node.data.operation ?? null, - cursorX: event.clientX, - cursorY: event.clientY + cursorX: clientX, + cursorY: clientY }); - setIsOpen(true); setHoverOperation(null); } @@ -42,7 +37,7 @@ export function useContextMenu() { return { isOpen, menuProps, - handleContextMenu, + openContextMenu, hideContextMenu }; } diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/graph/node-core.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/graph/node-core.tsx index 6be90695..de087696 100644 --- a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/graph/node-core.tsx +++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/graph/node-core.tsx @@ -31,6 +31,10 @@ export function NodeCore({ node }: NodeCoreProps) { const hasFile = !!node.data.operation.result; const longLabel = node.data.label.length > LONG_LABEL_CHARS; + function handleTouchStart(event: React.TouchEvent) { + console.log('handleTouchStart', event); + } + return (
state.showDeleteOperation); 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 longPressTimeout = useRef(null); + const longPressTarget = useRef(null); + function handleSavePositions() { void updateLayout({ itemID: schema.id, data: getLayout() }); } @@ -183,6 +187,55 @@ export function OssFlow() { setMouseCoords(targetPosition); } + function handleNodeContextMenu(event: React.MouseEvent, node: OssNode) { + event.preventDefault(); + event.stopPropagation(); + openContextMenu(node, event.clientX, event.clientY); + } + + function handleTouchStart(event: React.TouchEvent) { + 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 (
{showCoordinates ? : null} @@ -209,11 +262,14 @@ export function OssFlow() { showGrid={showGrid} onClick={hideContextMenu} onNodeDoubleClick={handleNodeDoubleClick} - onNodeContextMenu={handleContextMenu} + onNodeContextMenu={handleNodeContextMenu} onContextMenu={hideContextMenu} onNodeDragStart={handleDragStart} onNodeDrag={handleDrag} onNodeDragStop={handleDragStop} + onTouchStart={handleTouchStart} + onTouchEnd={handleTouchEnd} + onTouchMove={handleTouchMove} />
); diff --git a/rsconcept/frontend/src/utils/constants.ts b/rsconcept/frontend/src/utils/constants.ts index 16b543d0..09871961 100644 --- a/rsconcept/frontend/src/utils/constants.ts +++ b/rsconcept/frontend/src/utils/constants.ts @@ -11,6 +11,7 @@ export const PARAMETER = { notificationDelay: 300, // milliseconds delay for notifications zoomDuration: 500, // milliseconds animation duration 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 diff --git a/rsconcept/frontend/src/utils/utils.ts b/rsconcept/frontend/src/utils/utils.ts index 41884195..fe11fe15 100644 --- a/rsconcept/frontend/src/utils/utils.ts +++ b/rsconcept/frontend/src/utils/utils.ts @@ -192,3 +192,16 @@ export function removeTags(target?: string): string { export function prepareTooltip(text: string, hotkey?: string) { return hotkey ? `[${hotkey}]
${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) + ); +}