Skip to content

Commit

Permalink
Merge pull request #152 from boostcampwm-2024/feature-fe-#146
Browse files Browse the repository at this point in the history
에디터 업데이트 시 노드에 반영하는 기능 추가
  • Loading branch information
djk01281 authored Nov 14, 2024
2 parents 095cafe + f5a6292 commit a0e9764
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 44 deletions.
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"axios": "^1.7.7",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"fast-diff": "^1.3.0",
"framer-motion": "^11.11.11",
"highlight.js": "^11.10.0",
"lowlight": "^3.1.0",
Expand Down
16 changes: 8 additions & 8 deletions frontend/src/components/EditorView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@ import * as Y from "yjs";

import Editor from "./editor";
import usePageStore from "@/store/usePageStore";
import {
usePage,
useUpdatePage,
useOptimisticUpdatePage,
} from "@/hooks/usePages";
import { usePage, useUpdatePage } from "@/hooks/usePages";
import EditorLayout from "./layout/EditorLayout";
import EditorTitle from "./editor/EditorTitle";
import SaveStatus from "./editor/ui/SaveStatus";
Expand All @@ -36,7 +32,7 @@ export default function EditorView() {
const pageContent = page?.content ?? {};

const updatePageMutation = useUpdatePage();
const optimisticUpdatePageMutation = useOptimisticUpdatePage({
/* const optimisticUpdatePageMutation = useOptimisticUpdatePage({
id: currentPage ?? 0,
});
Expand All @@ -54,7 +50,7 @@ export default function EditorView() {
onError: () => setSaveStatus("unsaved"),
},
);
};
}; */

const handleEditorUpdate = useDebouncedCallback(
async ({ editor }: { editor: EditorInstance }) => {
Expand Down Expand Up @@ -91,7 +87,11 @@ export default function EditorView() {
return (
<EditorLayout>
<SaveStatus saveStatus={saveStatus} />
<EditorTitle title={pageTitle} onTitleChange={handleTitleChange} />
<EditorTitle
key={currentPage}
currentPage={currentPage}
pageContent={pageContent}
/>
<Editor
key={ydoc.guid}
initialContent={pageContent}
Expand Down
77 changes: 47 additions & 30 deletions frontend/src/components/canvas/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import * as Y from "yjs";
import { WebsocketProvider } from "y-websocket";
import { cn } from "@/lib/utils";
import { useQueryClient } from "@tanstack/react-query";
import useYDocStore from "@/store/useYDocStore";

const proOptions = { hideAttribution: true };

Expand All @@ -35,23 +36,39 @@ export default function Canvas({ className }: CanvasProps) {
const { pages } = usePages();
const queryClient = useQueryClient();

const ydoc = useRef<Y.Doc>();
const { ydoc } = useYDocStore();

const provider = useRef<WebsocketProvider>();
const existingPageIds = useRef(new Set<string>());

useEffect(() => {
const doc = new Y.Doc();
if (!pages) return;

const yMap = ydoc.getMap("title");

pages.forEach((page) => {
if (yMap.get(`title_${page.id}`)) return;

const yText = new Y.Text();
yText.insert(0, page.title);

yMap.set(`title_${page.id}`, yText);
});
}, [pages]);

Check warning on line 57 in frontend/src/components/canvas/index.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

React Hook useEffect has a missing dependency: 'ydoc'. Either include it or remove the dependency array

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

const wsProvider = new WebsocketProvider(
import.meta.env.VITE_WS_URL,
"flow-room",
doc,
ydoc,
);

ydoc.current = doc;
provider.current = wsProvider;

const nodesMap = doc.getMap("nodes");
const edgesMap = doc.getMap("edges");
const nodesMap = ydoc.getMap("nodes");
const edgesMap = ydoc.getMap("edges");

nodesMap.observe((event) => {
event.changes.keys.forEach((change, key) => {
Expand Down Expand Up @@ -86,14 +103,14 @@ export default function Canvas({ className }: CanvasProps) {

return () => {
wsProvider.destroy();
doc.destroy();
ydoc.destroy();
};
}, [queryClient]);
}, [ydoc, queryClient]);

Check warning on line 108 in frontend/src/components/canvas/index.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

React Hook useEffect has missing dependencies: 'setEdges' and 'setNodes'. Either include them or remove the dependency array

useEffect(() => {
if (!pages || !ydoc.current) return;
if (!pages || !ydoc) return;

const nodesMap = ydoc.current.getMap("nodes");
const nodesMap = ydoc.getMap("nodes");
const currentPageIds = new Set(pages.map((page) => page.id.toString()));

existingPageIds.current.forEach((pageId) => {
Expand All @@ -105,27 +122,27 @@ export default function Canvas({ className }: CanvasProps) {

pages.forEach((page) => {
const pageId = page.id.toString();
if (!existingPageIds.current.has(pageId)) {
const newNode = {
id: pageId,
position: {
x: Math.random() * 500,
y: Math.random() * 500,
},
data: { title: page.title, id: page.id },
type: "note",
};

nodesMap.set(pageId, newNode);
existingPageIds.current.add(pageId);
}
//if (!existingPageIds.current.has(pageId)) {
const newNode = {
id: pageId,
position: {
x: Math.random() * 500,
y: Math.random() * 500,
},
data: { title: page.title, id: page.id },
type: "note",
};

nodesMap.set(pageId, newNode);
existingPageIds.current.add(pageId);
//}
});
}, [pages]);

Check warning on line 140 in frontend/src/components/canvas/index.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

React Hook useEffect has a missing dependency: 'ydoc'. Either include it or remove the dependency array

const handleNodesChange = useCallback(
(changes: NodeChange[]) => {
if (!ydoc.current) return;
const nodesMap = ydoc.current.getMap("nodes");
if (!ydoc) return;
const nodesMap = ydoc.getMap("nodes");

onNodesChange(changes);

Expand All @@ -147,8 +164,8 @@ export default function Canvas({ className }: CanvasProps) {

const handleEdgesChange = useCallback(
(changes: EdgeChange[]) => {
if (!ydoc.current) return;
const edgesMap = ydoc.current.getMap("edges");
if (!ydoc) return;
const edgesMap = ydoc.getMap("edges");

changes.forEach((change) => {
if (change.type === "remove") {
Expand All @@ -163,7 +180,7 @@ export default function Canvas({ className }: CanvasProps) {

const onConnect = useCallback(
(connection: Connection) => {
if (!connection.source || !connection.target || !ydoc.current) return;
if (!connection.source || !connection.target || !ydoc) return;

const newEdge: Edge = {
id: `e${connection.source}-${connection.target}`,
Expand All @@ -173,7 +190,7 @@ export default function Canvas({ className }: CanvasProps) {
targetHandle: connection.targetHandle || undefined,
};

ydoc.current.getMap("edges").set(newEdge.id, newEdge);
ydoc.getMap("edges").set(newEdge.id, newEdge);
setEdges((eds) => addEdge(connection, eds));
},
[setEdges],

Check warning on line 196 in frontend/src/components/canvas/index.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

React Hook useCallback has a missing dependency: 'ydoc'. Either include it or remove the dependency array
Expand Down
32 changes: 26 additions & 6 deletions frontend/src/components/editor/EditorTitle.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,39 @@
import useYDocStore from "@/store/useYDocStore";
import { useYText } from "@/hooks/useYText";
import { useOptimisticUpdatePage } from "@/hooks/usePages";
import { JSONContent } from "novel";

interface EditorTitleProps {
title?: string;
onTitleChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
currentPage: number;
pageContent: JSONContent;
}

export default function EditorTitle({
title,
onTitleChange,
currentPage,
pageContent,
}: EditorTitleProps) {
const { ydoc } = useYDocStore();
const { input, setYText } = useYText(ydoc, currentPage);

const optimisticUpdatePageMutation = useOptimisticUpdatePage({
id: currentPage ?? 0,
});

const handleTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setYText(e.target.value);

optimisticUpdatePageMutation.mutate({
pageData: { title: e.target.value, content: pageContent },
});
};

return (
<div className="p-12 pb-0">
<input
type="text"
value={input as string}
className="w-full text-xl font-bold outline-none"
value={title}
onChange={onTitleChange}
onChange={handleTitleChange}
/>
</div>
);
Expand Down
32 changes: 32 additions & 0 deletions frontend/src/hooks/useYText.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useState } from "react";
import * as Y from "yjs";
import diff from "fast-diff";

function diffToDelta(diffResult) {
return diffResult.map(([op, value]) =>
op === diff.INSERT
? { insert: value }
: op === diff.EQUAL
? { retain: value.length }
: op === diff.DELETE
? { delete: value.length }
: null,
);
}

export const useYText = (ydoc: Y.Doc, currentPage: number) => {
const yText = ydoc.getMap("title").get(`title_${currentPage}`) as Y.Text;

const [input, setInput] = useState(yText.toString());

const setYText = (textNew: string) => {
const delta = diffToDelta(diff(input, textNew));
yText.applyDelta(delta);
};

yText.observe(() => {
setInput(yText.toString());
});

return { input, setYText };
};
14 changes: 14 additions & 0 deletions frontend/src/store/useYDocStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { create } from "zustand";
import * as Y from "yjs";

interface YDocStore {
ydoc: Y.Doc;
setYDoc: (ydoc: Y.Doc) => void;
}

const useYDocStore = create<YDocStore>((set) => ({
ydoc: new Y.Doc(),
setYDoc: (ydoc: Y.Doc) => set({ ydoc }),
}));

export default useYDocStore;
5 changes: 5 additions & 0 deletions frontend/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2218,6 +2218,11 @@ fast-deep-equal@^3, fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==

fast-diff@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0"
integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==

fast-glob@^3.3.0, fast-glob@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
Expand Down

0 comments on commit a0e9764

Please sign in to comment.