Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/check-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:

jobs:
build:
if: github.base_ref != 'main' || github.head_ref != 'dev'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down
1 change: 1 addition & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@linkyboard/hooks": "workspace:*",
"@linkyboard/utils": "workspace:*",
"@next/third-parties": "^15.5.4",
"@radix-ui/react-context-menu": "^2.2.16",
"@tanstack/react-query": "^5.84.1",
"@xyflow/react": "^12.8.2",
"ky": "^1.8.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,14 @@ export default function DashboardHeader() {
infoToast("아직 준비 중 이에요.");
};

const onLogout = async () => {
try {
// 쿠키 삭제
await removeCookie("accessToken");
await removeCookie("refreshToken");
await removeCookie("loggedIn");
const onLogout = () => {
// 쿠키 삭제
removeCookie("accessToken");
removeCookie("refreshToken");
removeCookie("loggedIn");

// 로그인 페이지로 리다이렉트
router.push("/login");
} catch (error) {
console.error("로그아웃 중 오류 발생:", error);
}
// 로그인 페이지로 리다이렉트
router.push("/login");
};

const onUserButtonClick = () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import React, { useCallback, useEffect, useState } from "react";

import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuTrigger,
} from "@/components/common/context-menu";

interface ContextMenuProviderProps {
children: React.ReactNode;
isTriggerDisabled: boolean;
}

const isMac = typeof window !== "undefined" && /Mac|iPhone|iPad|iPod/.test(navigator.userAgent);

const onEscapeContextMenu = () => {
const escEvent = new KeyboardEvent("keydown", { key: "Escape" });
document.dispatchEvent(escEvent);
};

export default function ContextMenuProvider({
children,
isTriggerDisabled,
}: ContextMenuProviderProps) {
const [isContextMenuOpen, setIsContextMenuOpen] = useState(false);

const onDuplicate = () => {
console.log("Duplicate 실행");
// 여기에 Duplicate 로직 추가
};

const onLink = () => {
console.log("Link 실행");
// 여기에 Link 로직 추가
};

const onUnlink = () => {
console.log("Unlink 실행");
// 여기에 Unlink 로직 추가
};

const onSummary = () => {
console.log("Summary 실행");
// 여기에 Summary 로직 추가
};

const onDelete = () => {
console.log("Delete 실행");
// 여기에 Delete 로직 추가
};

const onCloseContextMenu = useCallback(() => {
setIsContextMenuOpen(false);
onEscapeContextMenu();
}, []);

const onKeyDown = useCallback(
(e: KeyboardEvent) => {
const key = e.key.toLowerCase();
const isModifierPressed = isMac ? e.metaKey : e.ctrlKey;

switch (key) {
case "d":
if (!isModifierPressed) return;
e.preventDefault();
onDuplicate();
onCloseContextMenu();
break;
case "l":
e.preventDefault();
onLink();
onCloseContextMenu();
break;
case "u":
e.preventDefault();
onUnlink();
onCloseContextMenu();
break;
case "s":
e.preventDefault();
onSummary();
onCloseContextMenu();
break;
case "x":
e.preventDefault();
onDelete();
onCloseContextMenu();
break;
}
},
[onCloseContextMenu]
);

useEffect(() => {
if (!open) return;

window.addEventListener("keydown", onKeyDown);

return () => {
window.removeEventListener("keydown", onKeyDown);
};
}, [isContextMenuOpen, onKeyDown]);

return (
<ContextMenu onOpenChange={setIsContextMenuOpen}>
<ContextMenuTrigger asChild disabled={isTriggerDisabled}>
{children}
</ContextMenuTrigger>
<ContextMenuContent className="min-w-60 rounded-2xl bg-[#333] p-5 text-white">
<ContextMenuItem
className="flex items-center justify-between rounded-xl px-4 py-1 focus:bg-[#7E4ED7] focus:text-white"
onSelect={onDuplicate}
>
<p className="text-xl font-bold">Duplicate</p>
<p className="font-light">{isMac ? "⌘D" : "Ctrl+D"}</p>
</ContextMenuItem>
<ContextMenuSeparator className="my-5" />
<ContextMenuItem
className="focus:bg-primary flex items-center justify-between rounded-xl px-4 py-1 focus:text-white"
onSelect={onLink}
>
<p className="text-xl font-bold">Link</p>
<p className="font-light">L</p>
</ContextMenuItem>
<ContextMenuItem
className="focus:bg-primary flex items-center justify-between rounded-xl px-4 py-1 focus:text-white"
onSelect={onUnlink}
>
<p className="text-xl font-bold">Unlink</p>
<p className="font-light">U</p>
</ContextMenuItem>
<ContextMenuItem
className="focus:bg-primary flex items-center justify-between rounded-xl px-4 py-1 focus:text-white"
onSelect={onSummary}
>
<p className="text-xl font-bold">Summary</p>
<p className="font-light">S</p>
</ContextMenuItem>
<ContextMenuItem
className="focus:bg-primary flex items-center justify-between rounded-xl px-4 py-1 focus:text-white"
onSelect={onDelete}
>
<p className="text-xl font-bold">Delete</p>
<p className="font-light">X</p>
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
);
}
69 changes: 37 additions & 32 deletions apps/web/src/components/(with-side-bar)/topic/react-flow-canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {

import { AlertTriangle } from "lucide-react";

import ContextMenuProvider from "./context-menu-provider";
import Sticker from "./sticker";

interface ReactFlowCanvasProps {
Expand Down Expand Up @@ -70,6 +71,8 @@ function FlowCanvas({
const { screenToFlowPosition } = useReactFlow();
const { mutateAsync: createContent } = useCreateContent(id);

const isTriggerDisabled = selectedNodeIds.length === 0;

const nodeTypes: NodeTypes = useMemo(
() => ({
custom: (props: NodeProps) => (
Expand Down Expand Up @@ -111,37 +114,39 @@ function FlowCanvas({
};

return (
<div
className="relative flex-1 overflow-hidden rounded-r-lg border border-l-0"
onDragOver={onDragOver}
onDrop={onDrop}
>
{isLoading ? (
<div className="flex h-full w-full items-center justify-center">
<Spinner className="text-muted-foreground size-16" />
</div>
) : isTopicError ? (
<div className="flex h-full flex-col items-center justify-center gap-2">
<AlertTriangle className="text-destructive size-16" />
<p className="text-muted-foreground text-xl font-semibold">
{isNotFoundError ? "토픽을 찾을 수 없어요." : "토픽을 불러오는데 실패했어요."}
</p>
</div>
) : (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onEdgeClick={onEdgeClick}
nodeTypes={nodeTypes}
connectionLineStyle={connectionLineStyle}
defaultEdgeOptions={defaultEdgeOptions}
>
<Background />
</ReactFlow>
)}
</div>
<ContextMenuProvider isTriggerDisabled={isTriggerDisabled}>
<div
className="relative flex-1 overflow-hidden rounded-r-lg border border-l-0"
onDragOver={onDragOver}
onDrop={onDrop}
>
{isLoading ? (
<div className="flex h-full w-full items-center justify-center">
<Spinner className="text-muted-foreground size-16" />
</div>
) : isTopicError ? (
<div className="flex h-full flex-col items-center justify-center gap-2">
<AlertTriangle className="text-destructive size-16" />
<p className="text-muted-foreground text-xl font-semibold">
{isNotFoundError ? "토픽을 찾을 수 없어요." : "토픽을 불러오는데 실패했어요."}
</p>
</div>
) : (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onEdgeClick={onEdgeClick}
nodeTypes={nodeTypes}
connectionLineStyle={connectionLineStyle}
defaultEdgeOptions={defaultEdgeOptions}
>
<Background />
</ReactFlow>
)}
</div>
</ContextMenuProvider>
);
}
Loading