Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
03d5a76
📝 스토어 액션 TODO 업데이트
y-minion Jan 12, 2026
f8eb82d
♻️ 노드 리사이즈 핸들 수정
y-minion Jan 13, 2026
a121d57
✨ 삭제할 노드 id 탐색 함수 구현
y-minion Jan 13, 2026
031a549
♻️ 타겟 노드 id의 자식 노드id추출하는 액션 수정
y-minion Jan 13, 2026
8c0e7a2
♻️ 단순 파일 구조 변경
y-minion Jan 18, 2026
f67beaa
✨ nodes 가져오는 서버액션 구현
y-minion Jan 18, 2026
b02ad5e
✨ 에디터 스토어의 액션 사용 커스텀 훅 추가
y-minion Jan 18, 2026
f551dae
✨ Editor스토어 상태 업데이트 래퍼 컴포넌트 구현
y-minion Jan 18, 2026
1c3eb05
♻️ EditorStoreInitializer 리팩토링
y-minion Jan 19, 2026
33f78a7
✨ Editor라우트 페이지 구현
y-minion Jan 19, 2026
5a0db98
✨ 노드 삭제 액션 구현
y-minion Jan 19, 2026
ee3d8c4
✨ updateNode 액션 추가 구현, 각 커스텀 훅 JsDoc 추가
y-minion Jan 19, 2026
49e7d3a
🐛 테일윈드 파일을 Import하지 않던 버그 해결
y-minion Jan 19, 2026
773d90d
♻️ 목 데이터의 이미지 경로 수정
y-minion Jan 19, 2026
ac0910f
♻️ 설정 파일 수정
y-minion Jan 19, 2026
713a1c6
✨ 기본 Cavas위젯 dev환경 설정
y-minion Jan 20, 2026
d856d04
💄 css파일 참조 수정, 격자 유틸 추가
y-minion Jan 20, 2026
2cf0150
♻️ 리팩토링
y-minion Jan 20, 2026
f3ad53a
✨ 휠 이벤트 핸들러 유틸리티 추가
y-minion Jan 20, 2026
04af7dc
✨ 캔버스 패닝 유틸리티 구현 완료
y-minion Jan 20, 2026
835b480
✨ 유틸리티 함수와 캔버스 연결 완료
y-minion Jan 20, 2026
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
36 changes: 30 additions & 6 deletions apps/editor/db.json

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions apps/editor/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: "standalone", // 배포에 필요한 파일만 모아줍니다
transpilePackages: ["@repo/ui"],
images: {
remotePatterns: [
{
protocol: "https",
hostname: "**", // 개발 편의를 위해 모든 도메인 허용 (운영 배포시에는 특정 도메인으로 제한)
},
],
},
};

export default nextConfig;
1 change: 1 addition & 0 deletions apps/editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"db": "json-server db.json --port 4000",
"build": "next build --turbopack",
"start": "next start",
"lint": "next lint",
Expand Down
26 changes: 26 additions & 0 deletions apps/editor/src/actions/editor/getNodesFromDB.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use server";

import { WcxNode } from "types";

const BASE_URL = process.env.NEXT_PUBLIC_JSON_SERVER_URL;

export default async function getNodesFromDB(
pageId: number,
): Promise<WcxNode[]> {
try {
const res = await fetch(`${BASE_URL}/nodes?page_id=${pageId}`, {
cache: "no-store", //FIXME-캐시 설정을 Tag기반으로 수정해야함!!
});

if (!res.ok) {
throw new Error(`Failed to fetch nodes from JSON-Server:${res.status}`);
}

const data = await res.json();

return data as WcxNode[];
} catch (error) {
console.error("❌ Error fetching nodes:", error);
return [];
}
}
24 changes: 24 additions & 0 deletions apps/editor/src/app/editor/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import getNodesFromDB from "@/actions/editor/getNodesFromDB";
import Canvas from "@/components/editor/Canvas";
import EditorStoreInitializer from "@/components/editor/EditorStoreInitializer";
import { RuntimeProvider } from "@repo/ui/context/runtimeContext";

export default async function EditorPage() {
const pageId = 201; //목데이터입니다.
//서버 액션 함수(nodes데이터 패칭함수)
const nodes = await getNodesFromDB(pageId);

return (
<div className="flex h-screen w-screen flex-col gap-10">
<h1 className="text-3xl text-amber-700">에디터 페이지 입니다.</h1>
<EditorStoreInitializer initialNodes={nodes}>
{/* 다른 에디터 관련 컴포넌트들은 이곳에서 렌더링 됩니다!(사이드바,매니패스트 수정 컴포넌트 등등...) */}
<RuntimeProvider>
<div className="relative h-full w-full border-2">
<Canvas />
</div>
</RuntimeProvider>
</EditorStoreInitializer>
</div>
);
}
1 change: 1 addition & 0 deletions apps/editor/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import "../styles.css";
import QueryProvider from "src/providers/queryProvider";

export default function RootLayout({
Expand Down
2 changes: 2 additions & 0 deletions apps/editor/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export default function Home() {
<div>
<h1>환경합니다</h1>
<Link href="test_click_to_edit">테스트 환경</Link>
<br />
<Link href="/editor">에디터 페이지</Link>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,34 @@
"use client";

import {
useCanvas,
useCurNodes,
useSelectedNodeId,
useSelectNode,
useUpdateNode,
useSetCanvas,
useUpdateNodeLayout,
} from "@/stores/useEditorStore";
import EditorNodeWrapper from "@repo/ui/core/EditorNodeWrapper.jsx";
import NodeRenderer from "@repo/ui/core/NodeRenderer.jsx";
import { WcxNode } from "@repo/ui/types/nodes.js";
import React from "react";
import {
handleMouseDown,
handleMouseMove,
handleMouseUp,
} from "@/utils/editor/canvasMouseHandler";
import handleWheel from "@/utils/editor/handleWheel";
import EditorNodeWrapper from "@repo/ui/core/EditorNodeWrapper";
import NodeRenderer from "@repo/ui/core/NodeRenderer";
import { WcxNode } from "@repo/ui/types/nodes";
import React, { useRef } from "react";

export default function Canvas() {
const nodes = useCurNodes();
const selectedNodeId = useSelectedNodeId();
const selectNode = useSelectNode();
const updateNode = useUpdateNode();
const updateNode = useUpdateNodeLayout();
const canvasState = useCanvas();
const setCanvas = useSetCanvas();

const isPanning = useRef(false);
const lastMousePos = useRef({ x: 0, y: 0 });

//FIXME-각 노드들에 key속성 추가해주기. -> 리액트 경고 발생
//FIXME-nodes가 비어있는 상황에서 에러발생. -> Base Condition에 Root가 들어간다.(Root는 단지 더미 노드일뿐 로직에 들어가면 안된다.)
Expand Down Expand Up @@ -74,5 +87,34 @@ export default function Canvas() {
);
}

return <div className="canvas-root relative">{renderTree({ id: null })}</div>;
return (
<div
className="relative h-full w-full flex-1 cursor-grab overflow-hidden bg-white active:cursor-grabbing"
onWheel={(e) => handleWheel({ canvas: canvasState, e, setCanvas })}
onMouseDown={(e) =>
handleMouseDown({ e, isPanning, lastMousePos, selectNode })
}
onMouseMove={(e) =>
handleMouseMove({ e, isPanning, lastMousePos, setCanvas, canvasState })
}
onMouseUp={() => handleMouseUp({ isPanning })}
onMouseLeave={() => handleMouseUp({ isPanning })}
>
<div
style={{
transform: `translate(${canvasState.dx}px, ${canvasState.dy}px) scale(${canvasState.scale})`,
transformOrigin: "0 0",
width: "100%",
height: "100%",
}}
className="relative h-full w-full"
>
{/* 배경 격자 (Helper Grid) */}
<div className="bg-grid-pattern pointer-events-none absolute inset-[-1000%] z-0 h-[3000%] w-[3000%]" />
<div className="relative z-10 h-full w-full">
{renderTree({ id: null })}
</div>
</div>
</div>
);
}
26 changes: 26 additions & 0 deletions apps/editor/src/components/editor/EditorStoreInitializer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use client";

import { useSetNode } from "@/stores/useEditorStore";
import { useEffect, useRef } from "react";
import { WcxNode } from "types";

interface EditorStoreInitializer {
children: React.ReactNode;
initialNodes: WcxNode[];
}

//TODO-일단 DB와 연동하지 말자. 서버액션의 사용이유는 초기 렌더링 속도 향상임. 이후에는 클라이언트에서 스토어 업데이트를 할 예정.
export default function EditorStoreInitializer({
children,
initialNodes,
}: EditorStoreInitializer) {
const initialized = useRef(false);
const setNode = useSetNode();
useEffect(() => {
if (initialized.current) return;
setNode(initialNodes);
initialized.current = true;
}, [initialNodes, setNode]);

return <>{children}</>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
const mockUseCurNodes = vi.fn();
const mockUseSelectedNodeId = vi.fn();
const mockUseSelectNode = vi.fn();
const mockUseUpdateNode = vi.fn();
const mockUseUpdateNodeLayout = vi.fn();
const mockUseCanvas = vi.fn();
const mockUseSetCanvas = vi.fn();

Expand All @@ -15,7 +15,7 @@ vi.mock("@/stores/useEditorStore", () => ({
useCurNodes: () => mockUseCurNodes(), //지연 실행 -> 이렇게 되면 다른 테스트에서 목함수의 반환값을 바꿔도 언제나 새롭게 해당 함수가 호출 되므로 다른 테스트의 영향을 받지 않는다.
useSelectedNodeId: () => mockUseSelectedNodeId(),
useSelectNode: () => mockUseSelectNode(),
useUpdateNode: () => mockUseUpdateNode(),
useUpdateNodeLayout: () => mockUseUpdateNodeLayout(),
useCanvas: () => mockUseCanvas(),
useSetCanvas: () => mockUseSetCanvas(),
}));
Expand Down
Loading