From 3dcc1df45187d0947ef8f68d0f9af13d0bbbbe0f Mon Sep 17 00:00:00 2001 From: Sebastian Mohr <sebastian@mohrenclan.de> Date: Mon, 6 Jan 2025 14:28:58 +0100 Subject: [PATCH] Added possibility to trigger context menu on mobile devices. --- CHANGELOG.md | 2 +- .../debug/playground/inputs/contextMenu.tsx | 24 +++++++++ .../app/debug/playground/inputs/page.tsx | 2 + .../editor/sidebar/previews/previewSnips.tsx | 16 +++--- .../lib/hooks/useMobileSafeContextMenu.ts | 54 +++++++++++++++++++ 5 files changed, 90 insertions(+), 8 deletions(-) create mode 100644 apps/fullstack/app/debug/playground/inputs/contextMenu.tsx create mode 100644 apps/fullstack/lib/hooks/useMobileSafeContextMenu.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index b32a74b2..234bc7d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,7 +36,7 @@ the labels did not update correctly and were not removed correctly - New snips notification not disappearing from the not seen list once they are placed see [#100](https://gitlab.gwdg.de/irp/snip/-/issues/100) - Sidebar not clickable in fullscreen on some mobile devices. Added safe area padding to the sidebar to avoid this issue. - Reconnection issue on stale pages to websocket server see [#89](https://gitlab.gwdg.de/irp/snip/-/issues/89) - +- Allow contextMenu click on mobile devices by holding the touch event for a longer time ## [1.9.0] diff --git a/apps/fullstack/app/debug/playground/inputs/contextMenu.tsx b/apps/fullstack/app/debug/playground/inputs/contextMenu.tsx new file mode 100644 index 00000000..ff24b579 --- /dev/null +++ b/apps/fullstack/app/debug/playground/inputs/contextMenu.tsx @@ -0,0 +1,24 @@ +import useMobileSafeContextMenu from "lib/hooks/useMobileSafeContextMenu"; + +import { Wrapper } from "./utils"; + +export const ContextMenu = () => { + const eleProps = useMobileSafeContextMenu(() => { + alert("context menu"); + }); + return ( + <Wrapper> + <h3>ContextMenu</h3> + <div + {...eleProps} + style={{ + width: "100px", + height: "100px", + backgroundColor: "red", + }} + > + Right click me or long press me on mobile + </div> + </Wrapper> + ); +}; diff --git a/apps/fullstack/app/debug/playground/inputs/page.tsx b/apps/fullstack/app/debug/playground/inputs/page.tsx index 5c1bf9b1..37fa81a3 100644 --- a/apps/fullstack/app/debug/playground/inputs/page.tsx +++ b/apps/fullstack/app/debug/playground/inputs/page.tsx @@ -3,6 +3,7 @@ import React, { useState } from "react"; import ColorInputs from "./color"; +import { ContextMenu } from "./contextMenu"; import NumberInputs from "./number"; import OptionInputs from "./options"; import TextInputs from "./text"; @@ -266,6 +267,7 @@ const Page = () => { showValid={state.showValid} showInvalid={state.showInvalid} /> + <ContextMenu /> </div> </div> ); diff --git a/apps/fullstack/components/editor/sidebar/previews/previewSnips.tsx b/apps/fullstack/components/editor/sidebar/previews/previewSnips.tsx index e3b20521..9f11f964 100644 --- a/apps/fullstack/components/editor/sidebar/previews/previewSnips.tsx +++ b/apps/fullstack/components/editor/sidebar/previews/previewSnips.tsx @@ -82,6 +82,7 @@ export function PreviewSnips({ ); } +import useMobileSafeContextMenu from "lib/hooks/useMobileSafeContextMenu"; import { TbLink, TbLinkOff, @@ -131,7 +132,7 @@ function PreviewSingleSnip({ }) { const canvasRef = useRef<HTMLCanvasElement>(null); - // Prevernts hydration mismatch + // Prevents hydration mismatch const [info, setInfo] = useState<Info>({ width: 0, height: 0, @@ -139,8 +140,14 @@ function PreviewSingleSnip({ id: -1, }); + // Context menu const [showContext, setShowContext] = useState(false); + const contextMenuProps = useMobileSafeContextMenu((e) => { + e.preventDefault(); + setShowContext((p) => !p); + }); + //Render snip useEffect(() => { const ctx = canvasRef.current!.getContext("2d")!; @@ -208,17 +215,12 @@ function PreviewSingleSnip({ className={styles.snip} data-active={active} data-hidden={hidden} - onContextMenu={(e) => { - e.preventDefault(); - setShowContext((p) => !p); - // Handle outside click - //document.addEventListener("click", outsideClickListener); - }} onPointerMove={() => { if (newBadge) { resetNewBadge?.(); } }} + {...contextMenuProps} > <FlipCard flipped={showContext} diff --git a/apps/fullstack/lib/hooks/useMobileSafeContextMenu.ts b/apps/fullstack/lib/hooks/useMobileSafeContextMenu.ts new file mode 100644 index 00000000..0f6da2fb --- /dev/null +++ b/apps/fullstack/lib/hooks/useMobileSafeContextMenu.ts @@ -0,0 +1,54 @@ +import { UIEvent, useRef } from "react"; + +/** + * A custom hook to handle context menu events in a mobile-friendly way. + * It triggers a custom context menu callback after a long press on touch devices + * or a right-click on desktop devices. + * + * @param onContextMenu - The callback function to execute when the context menu is triggered. + * @param duration - The duration (in milliseconds) to wait before triggering the context menu on touch devices. Default is 500ms. + * @returns An object containing event handlers to be spread onto the target element. + * + * @example + * const elemProps = useMobileSafeContextMenu((event) => { + * console.log("Context menu triggered", event); + * }); + * + * return ( + * <div + * {...elemProps} + * > + * Right-click or long-press me + * </div> + * ); + */ +const useMobileSafeContextMenu = ( + onContextMenu: (event: UIEvent) => void, + duration = 500, +) => { + const pressTimer = useRef<number | null>(null); + + const start = (event: UIEvent) => { + pressTimer.current = window.setTimeout(() => { + onContextMenu(event); // Trigger the context menu callback + }, duration); + }; + + const stop = () => { + if (pressTimer.current !== null) { + window.clearTimeout(pressTimer.current); + } + }; + + return { + onTouchStart: start, + onTouchEnd: stop, + onTouchCancel: stop, + onContextMenu: (event: UIEvent) => { + event.preventDefault(); // Prevent default context menu + onContextMenu(event); + }, + }; +}; + +export default useMobileSafeContextMenu; -- GitLab