From 53b02c90ab5a11249ff67dc4c806b142e7aeded2 Mon Sep 17 00:00:00 2001 From: CatyJazzy Date: Mon, 2 Dec 2024 18:52:21 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=ED=84=B0=EC=B9=98=EB=94=94?= =?UTF-8?q?=EB=B0=94=EC=9D=B4=EC=8A=A4=20=EC=97=AC=EB=B6=80=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=20=EB=A1=9C=EC=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/Node.tsx | 33 +++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/components/Node.tsx b/packages/frontend/src/components/Node.tsx index dc60972..e73b425 100644 --- a/packages/frontend/src/components/Node.tsx +++ b/packages/frontend/src/components/Node.tsx @@ -1,5 +1,5 @@ import { ReactNode, useEffect, useRef, useState } from "react"; -import { Circle, Group, KonvaNodeEvents, Text } from "react-konva"; +import { Circle, Group, KonvaNodeEvents, Shape, Text } from "react-konva"; import { useNavigate } from "react-router-dom"; import Konva from "konva"; @@ -99,6 +99,29 @@ Node.Text = function NodeText({ ); }; +Node.MoreButton = function NodeMoreButton({ onTap }: { onTap: () => void }) { + const [isTouch, setIsTouch] = useState(false); + + useEffect(() => { + setIsTouch("ontouchstart" in window || navigator.maxTouchPoints > 0); + }, []); + + if (!isTouch) return null; + + return ( + + + + ); +}; + export type HeadNodeProps = { id: string; name: string; @@ -143,6 +166,7 @@ export function NoteNode({ id, x, y, name, src, ...rest }: NoteNodeProps) { > + console.log("터치!")} /> ); } @@ -174,7 +198,12 @@ export function SubspaceNode({ {...rest} > - + ); } From e5c540e895b1d79877676b30809593d9f2f0ecd0 Mon Sep 17 00:00:00 2001 From: CatyJazzy Date: Tue, 3 Dec 2024 03:01:48 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=EB=AA=A8=EB=B0=94=EC=9D=BC?= =?UTF-8?q?=ED=99=98=EA=B2=BD=EC=97=90=EC=84=9C=20=EB=8D=94=EB=B3=B4?= =?UTF-8?q?=EA=B8=B0=20=EB=B2=84=ED=8A=BC=20=ED=81=B4=EB=A6=AD=20=EC=8B=9C?= =?UTF-8?q?=20context-menu=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/Node.tsx | 73 ++++++++++++++++--- .../src/components/space/SpaceView.tsx | 6 +- 2 files changed, 68 insertions(+), 11 deletions(-) diff --git a/packages/frontend/src/components/Node.tsx b/packages/frontend/src/components/Node.tsx index e73b425..fcc979b 100644 --- a/packages/frontend/src/components/Node.tsx +++ b/packages/frontend/src/components/Node.tsx @@ -1,8 +1,13 @@ import { ReactNode, useEffect, useRef, useState } from "react"; -import { Circle, Group, KonvaNodeEvents, Shape, Text } from "react-konva"; +import { Circle, Group, KonvaNodeEvents, Text } from "react-konva"; import { useNavigate } from "react-router-dom"; import Konva from "konva"; +import { + KonvaEventObject, + Node as KonvaNode, + NodeConfig, +} from "konva/lib/Node"; import { Vector2d } from "konva/lib/types"; const RADIUS = 64; @@ -72,6 +77,7 @@ Node.Text = function NodeText({ fontSize, fontStyle, width, + ...rest }: NodeTextProps) { const ref = useRef(null); const [offset, setOffset] = useState(undefined); @@ -95,11 +101,19 @@ Node.Text = function NodeText({ align="center" wrap="none" ellipsis + {...rest} /> ); }; -Node.MoreButton = function NodeMoreButton({ onTap }: { onTap: () => void }) { +type NodeMoreButtonProps = { + onTap?: + | ((evt: KonvaEventObject>) => void) + | undefined; + content: string; +}; + +Node.MoreButton = function NodeMoreButton({ content }: NodeMoreButtonProps) { const [isTouch, setIsTouch] = useState(false); useEffect(() => { @@ -108,15 +122,44 @@ Node.MoreButton = function NodeMoreButton({ onTap }: { onTap: () => void }) { if (!isTouch) return null; + const handleTap = (e: KonvaEventObject) => { + e.cancelBubble = true; + + const parentNode = e.target.findAncestor("Group").findAncestor("Group"); + if (!parentNode) return; + + const contextMenuEvent = new MouseEvent("contextmenu", { + button: 2, + buttons: 2, + }); + + parentNode.fire("contextmenu", { + evt: contextMenuEvent, + target: parentNode, + }); + }; + return ( - + + ); @@ -149,7 +192,15 @@ export type NoteNodeProps = { name: string; } & NodeHandlers; -export function NoteNode({ id, x, y, name, src, ...rest }: NoteNodeProps) { +export function NoteNode({ + id, + x, + y, + name, + src, + onContextMenu, + ...rest +}: NoteNodeProps) { // TODO: src 적용 필요 const navigate = useNavigate(); return ( @@ -162,11 +213,12 @@ export function NoteNode({ id, x, y, name, src, ...rest }: NoteNodeProps) { navigate(`/note/${src}`); } }} + onContextMenu={onContextMenu} {...rest} > - console.log("터치!")} /> + ); } @@ -185,6 +237,7 @@ export function SubspaceNode({ y, name, src, + onContextMenu, ...rest }: SubspaceNodeProps) { const navigate = useNavigate(); @@ -195,6 +248,7 @@ export function SubspaceNode({ x={x} y={y} onClick={() => navigate(`/space/${src}`)} + onContextMenu={onContextMenu} {...rest} > @@ -204,6 +258,7 @@ export function SubspaceNode({ fontStyle="700" content={name} /> + ); } diff --git a/packages/frontend/src/components/space/SpaceView.tsx b/packages/frontend/src/components/space/SpaceView.tsx index 7b550e9..79299d9 100644 --- a/packages/frontend/src/components/space/SpaceView.tsx +++ b/packages/frontend/src/components/space/SpaceView.tsx @@ -146,7 +146,7 @@ export default function SpaceView({ spaceId, autofitTo }: SpaceViewProps) { }; }, [autofitTo]); - const handleContextMenu = (e: KonvaEventObject) => { + const handleContextMenu = (e: KonvaEventObject) => { clearSelection(); const { target } = e; @@ -159,7 +159,9 @@ export default function SpaceView({ spaceId, autofitTo }: SpaceViewProps) { return; } - const group = target.findAncestor("Group"); + // Mobile 환경에서는 group을 대상으로 임의로 이벤트 발생시킴 + const group = + target instanceof Konva.Group ? target : target.findAncestor("Group"); const nodeId = group?.attrs?.id as string | undefined; From a5a50d7646884d6b01efdbedcfdc7223ffadb7c9 Mon Sep 17 00:00:00 2001 From: CatyJazzy Date: Tue, 3 Dec 2024 04:53:09 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=EB=8D=94=EB=B3=B4=EA=B8=B0=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=ED=81=B4=EB=A6=AD=20=EC=8B=9C=20clientX,?= =?UTF-8?q?=20clientY=EA=B0=92=20=EC=A0=95=EC=9D=98=ED=95=98=EC=97=AC=20?= =?UTF-8?q?=EC=A0=84=EB=8B=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/Node.tsx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/frontend/src/components/Node.tsx b/packages/frontend/src/components/Node.tsx index fcc979b..03f748c 100644 --- a/packages/frontend/src/components/Node.tsx +++ b/packages/frontend/src/components/Node.tsx @@ -11,6 +11,7 @@ import { import { Vector2d } from "konva/lib/types"; const RADIUS = 64; +const MORE_BUTTON_RADIUS = 12; type NodeProps = { id: string; @@ -126,11 +127,17 @@ Node.MoreButton = function NodeMoreButton({ content }: NodeMoreButtonProps) { e.cancelBubble = true; const parentNode = e.target.findAncestor("Group").findAncestor("Group"); + if (!parentNode) return; + const absolutePosition = parentNode.getAbsolutePosition(); + const contextMenuEvent = new MouseEvent("contextmenu", { button: 2, buttons: 2, + clientX: absolutePosition.x, + clientY: absolutePosition.y, + bubbles: true, }); parentNode.fire("contextmenu", { @@ -144,7 +151,7 @@ Node.MoreButton = function NodeMoreButton({ content }: NodeMoreButtonProps) { ); From b0f53ecb9df5d8246fffcc5a341e2cdafb038252 Mon Sep 17 00:00:00 2001 From: CatyJazzy Date: Tue, 3 Dec 2024 13:20:33 +0900 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20=EA=B0=84=EC=84=A0=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EB=B2=84=ED=8A=BC=20=EC=8A=A4=ED=83=80=EC=9D=BC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/Edge.tsx | 55 ++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/packages/frontend/src/components/Edge.tsx b/packages/frontend/src/components/Edge.tsx index 413538f..836eeee 100644 --- a/packages/frontend/src/components/Edge.tsx +++ b/packages/frontend/src/components/Edge.tsx @@ -1,11 +1,63 @@ import { useEffect, useState } from "react"; -import { Group, Line } from "react-konva"; +import { Circle, Group, Line, Text } from "react-konva"; import Konva from "konva"; +import { KonvaEventObject, Node, NodeConfig } from "konva/lib/Node"; import type { Edge } from "shared/types"; type EdgeProps = Edge & Konva.LineConfig; +const BUTTON_RADIUS = 12; + +type EdgeEditButtonProps = { + points: number[]; + onTap?: + | ((evt: KonvaEventObject>) => void) + | undefined; +}; + +function EdgeEditButton({ points, onTap }: EdgeEditButtonProps) { + const [isTouch, setIsTouch] = useState(false); + + useEffect(() => { + setIsTouch("ontouchstart" in window || navigator.maxTouchPoints > 0); + }, []); + + if (!isTouch || points.length < 4) return null; + + const handleTap = (e: KonvaEventObject) => { + if (onTap) { + const { target } = e; + console.log(target); + } + }; + + const middleX = (points[0] + points[2]) / 2; + const middleY = (points[1] + points[3]) / 2; + + return ( + + + + + ); +} + function calculateOffsets( from: { x: number; y: number }, to: { x: number; y: number }, @@ -66,6 +118,7 @@ export default function Edge({ name="edge" id={id} /> + ); } From 7e47a0a67320ebff62cccf403c6f370c6ff2f76c Mon Sep 17 00:00:00 2001 From: CatyJazzy Date: Tue, 3 Dec 2024 15:14:45 +0900 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20=EC=82=AD=EC=A0=9C=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=ED=84=B0=EC=B9=98=20=ED=95=B8=EB=93=A4=EB=9F=AC=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/Edge.tsx | 26 ++++++++++++------- .../src/components/space/SpaceView.tsx | 1 + 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/frontend/src/components/Edge.tsx b/packages/frontend/src/components/Edge.tsx index 836eeee..5608d5f 100644 --- a/packages/frontend/src/components/Edge.tsx +++ b/packages/frontend/src/components/Edge.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import { Circle, Group, Line, Text } from "react-konva"; import Konva from "konva"; -import { KonvaEventObject, Node, NodeConfig } from "konva/lib/Node"; +import { KonvaEventObject } from "konva/lib/Node"; import type { Edge } from "shared/types"; type EdgeProps = Edge & Konva.LineConfig; @@ -11,9 +11,7 @@ const BUTTON_RADIUS = 12; type EdgeEditButtonProps = { points: number[]; - onTap?: - | ((evt: KonvaEventObject>) => void) - | undefined; + onTap: (edgeId: string) => void; }; function EdgeEditButton({ points, onTap }: EdgeEditButtonProps) { @@ -26,10 +24,19 @@ function EdgeEditButton({ points, onTap }: EdgeEditButtonProps) { if (!isTouch || points.length < 4) return null; const handleTap = (e: KonvaEventObject) => { - if (onTap) { - const { target } = e; - console.log(target); - } + const targetGroup = e.target + .findAncestor("Group") + .findAncestor("Group") as Konva.Group; + + if (!targetGroup) return; + + const targetEdge = targetGroup.children.find( + (konvaNode) => konvaNode.attrs.name === "edge", + ); + + if (!targetEdge) return; + + onTap(targetEdge.attrs.id); }; const middleX = (points[0] + points[2]) / 2; @@ -78,6 +85,7 @@ export default function Edge({ to, id, onContextMenu, + onDelete, ...rest }: EdgeProps) { const [points, setPoints] = useState([]); @@ -118,7 +126,7 @@ export default function Edge({ name="edge" id={id} /> - + ); } diff --git a/packages/frontend/src/components/space/SpaceView.tsx b/packages/frontend/src/components/space/SpaceView.tsx index 79299d9..884725c 100644 --- a/packages/frontend/src/components/space/SpaceView.tsx +++ b/packages/frontend/src/components/space/SpaceView.tsx @@ -301,6 +301,7 @@ export default function SpaceView({ spaceId, autofitTo }: SpaceViewProps) { to={edge.to} nodes={nodes} onContextMenu={handleContextMenu} + onDelete={deleteEdge} /> ));