Skip to content

Commit

Permalink
Merge pull request #346 from boostcampwm-2024/bug-fe-#343
Browse files Browse the repository at this point in the history
yjs, 쿼리 관련 데이터 흐름 변경
  • Loading branch information
github-actions[bot] authored Dec 1, 2024
2 parents 8b8a3dd + 51a3b99 commit c366e05
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 153 deletions.
1 change: 1 addition & 0 deletions apps/backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { ScheduleModule } from '@nestjs/schedule';
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({

// type: 'sqlite',
// database: 'db.sqlite',
type: 'postgres',
Expand Down
46 changes: 25 additions & 21 deletions apps/backend/src/yjs/yjs.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ export class YjsService
const editorDoc = doc.getXmlFragment('default');
const customDoc = editorDoc.doc as CustomDoc;

if (customDoc.name === 'users') {
return;
}

// document name이 flow-room이라면 모든 노드들을 볼 수 있는 화면입니다.
// 노드를 클릭해 페이지를 열었을 때만 해당 페이지 값을 가져와서 초기 데이터로 세팅해줍니다.
if (customDoc.name?.startsWith('document-')) {
Expand Down Expand Up @@ -109,18 +113,18 @@ export class YjsService
editorDoc.observeDeep(() => {
const document = editorDoc.doc as CustomDoc;
const pageId = parseInt(document.name.split('-')[1]);
// this.pageService.updatePage(
// pageId,
// JSON.parse(
// JSON.stringify(yXmlFragmentToProsemirrorJSON(editorDoc)),
// ),
// );

this.redisService.setField(
pageId.toString(),
'content',
JSON.stringify(yXmlFragmentToProsemirrorJSON(editorDoc)),
this.pageService.updatePage(
pageId,
JSON.parse(
JSON.stringify(yXmlFragmentToProsemirrorJSON(editorDoc)),
),
);

// this.redisService.setField(
// pageId.toString(),
// 'content',
// JSON.stringify(yXmlFragmentToProsemirrorJSON(editorDoc)),
// );
// this.redisService.get(pageId.toString()).then((data) => {
// console.log(data);
// });
Expand Down Expand Up @@ -154,17 +158,17 @@ export class YjsService
title.observeDeep(async (event) => {
// path가 존재할 때만 페이지 갱신
event[0].path.toString().split('_')[1] &&
// this.pageService.updatePage(
// parseInt(event[0].path.toString().split('_')[1]),
// {
// title: event[0].target.toString(),
// },
// );
this.redisService.setField(
event[0].path.toString().split('_')[1],
'title',
event[0].target.toString(),
this.pageService.updatePage(
parseInt(event[0].path.toString().split('_')[1]),
{
title: event[0].target.toString(),
},
);
// this.redisService.setField(
// event[0].path.toString().split('_')[1],
// 'title',
// event[0].target.toString(),
// );
});
emoji.observeDeep((event) => {
// path가 존재할 때만 페이지 갱신
Expand Down
120 changes: 26 additions & 94 deletions apps/frontend/src/features/canvas/model/useCanvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,11 @@ import {
} from "@xyflow/react";
import "@xyflow/react/dist/style.css";
import { SocketIOProvider } from "y-socket.io";
import { useQueryClient } from "@tanstack/react-query";

import { usePages } from "@/features/pageSidebar/api/usePages";
import useYDocStore from "@/shared/model/ydocStore";
import { calculateBestHandles } from "@/features/canvas/model/calculateHandles";
import { createSocketIOProvider } from "@/shared/api/socketProvider";
import { useCollaborativeCursors } from "./useCollaborativeCursors";
import { getSortedNodes } from "./sortNodes";
import { usePageStore } from "@/features/pageSidebar/model/pageStore";
import { useWorkspace } from "@/shared/lib/useWorkspace";

Expand All @@ -31,8 +28,6 @@ export const useCanvas = () => {
const [nodes, setNodes, onNodesChange] = useNodesState<Node>([]);
const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([]);
const workspace = useWorkspace();
const { pages } = usePages(workspace);
const queryClient = useQueryClient();
const { ydoc } = useYDocStore();

const { cursors, handleMouseMove, handleNodeDrag, handleMouseLeave } =
Expand All @@ -41,7 +36,6 @@ export const useCanvas = () => {
});

const provider = useRef<SocketIOProvider>();
const existingPageIds = useRef(new Set<string>());
const holdingNodeRef = useRef<string | null>(null);

const currentPage = usePageStore((state) => state.currentPage);
Expand All @@ -68,7 +62,6 @@ export const useCanvas = () => {
useEffect(() => {
const yTitleMap = ydoc.getMap("title");
const yEmojiMap = ydoc.getMap("emoji");

const nodesMap = ydoc.getMap("nodes");

yTitleMap.observeDeep((event) => {
Expand All @@ -78,6 +71,7 @@ export const useCanvas = () => {
const value = event[0].target.toString();

const existingNode = nodesMap.get(pageId) as YNode;
if (!existingNode) return;

const newNode: YNode = {
id: existingNode.id,
Expand All @@ -98,6 +92,7 @@ export const useCanvas = () => {
const value = event[0].target.toString();

const existingNode = nodesMap.get(pageId) as YNode;
if (!existingNode) return;

const newNode: YNode = {
id: pageId,
Expand All @@ -110,67 +105,50 @@ export const useCanvas = () => {

nodesMap.set(pageId, newNode);
});
}, []);

}, [ydoc]);
useEffect(() => {
if (!ydoc) return;

const wsProvider = createSocketIOProvider(`flow-room-${workspace}`, ydoc);

provider.current = wsProvider;

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

const yNodes = Array.from(nodesMap.values()) as YNode[];

const initialNodes = yNodes.map((yNode) => {
const nodeEntries = Object.entries(yNode).filter(
([key]) => key !== "isHolding",
);
return Object.fromEntries(nodeEntries) as Node;
wsProvider.on("sync", (isSynced: boolean) => {
if (isSynced) {
const nodesMap = ydoc.getMap("nodes");
const yNodes = Array.from(nodesMap.values()) as YNode[];
setNodes(yNodes);
}
});

console.log(initialNodes);

setNodes(initialNodes);

let isInitialSync = true;
const nodesMap = ydoc.getMap("nodes");
const edgesMap = ydoc.getMap("edges");
const yEdges = Array.from(edgesMap.values()) as Edge[];
setEdges(yEdges);

nodesMap.observe((event) => {
if (isInitialSync) {
isInitialSync = false;
return;
}

event.changes.keys.forEach((change, key) => {
const nodeId = key;
if (change.action === "add" || change.action === "update") {
const updatedYNode = nodesMap.get(nodeId) as YNode;
const updatedNodeEntries = Object.entries(updatedYNode).filter(
([key]) => key !== "isHolding",
);
const updatedNode = Object.fromEntries(updatedNodeEntries) as Node;

if (change.action === "add") {
queryClient.invalidateQueries({ queryKey: ["pages"] });
}

const updatedNode = nodesMap.get(nodeId) as Node;
setNodes((nds) => {
const index = nds.findIndex((n) => n.id === nodeId);
if (index === -1) {
return [...nds, updatedNode];
}
const newNodes = [...nds];
newNodes[index] = {
...updatedNode,
selected: newNodes[index].selected,
};
newNodes[index] = updatedNode;
return newNodes;
});
} else if (change.action === "delete") {
// parseInt는 yjs.service.ts에서 타입 변환 로직 참고.
const deletedNodeId = parseInt(nodeId);
const currentPageValue = usePageStore.getState().currentPage;

if (currentPageValue === deletedNodeId) {
usePageStore.setState({ currentPage: null, isPanelOpen: false });
}

setNodes((nds) => nds.filter((n) => n.id !== nodeId));
queryClient.invalidateQueries({ queryKey: ["pages"] });
}
});
});
Expand All @@ -183,52 +161,7 @@ export const useCanvas = () => {
return () => {
wsProvider.destroy();
};
}, [ydoc, queryClient]);

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

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

existingPageIds.current.forEach((pageId) => {
if (!currentPageIds.has(pageId)) {
nodesMap.delete(pageId);
existingPageIds.current.delete(pageId);
}
});

pages.forEach((page) => {
const pageId = page.id.toString();
const existingNode = nodesMap.get(pageId) as YNode | undefined;

const newNode: YNode = {
id: pageId,
type: "note",
data: { title: page.title, id: page.id, emoji: page.emoji },
position: existingNode?.position || {
x: Math.random() * 500,
y: Math.random() * 500,
},
selected: false,
isHolding: false,
};

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

const sortNodes = async () => {
const sortedNodes = await getSortedNodes(nodes, edges);
const nodesMap = ydoc.getMap("nodes");

sortedNodes.forEach((updateNode) => {
nodesMap.set(updateNode.id, updateNode);
});

setNodes(sortedNodes);
};
}, [ydoc, setNodes, setEdges, workspace]);

const handleNodesChange = useCallback(
(changes: NodeChange[]) => {
Expand Down Expand Up @@ -275,7 +208,7 @@ export const useCanvas = () => {

onNodesChange(changes);
},
[nodes, edges, onNodesChange],
[nodes, edges, onNodesChange, ydoc],
);

const handleEdgesChange = useCallback(
Expand All @@ -291,7 +224,7 @@ export const useCanvas = () => {

onEdgesChange(changes);
},
[onEdgesChange],
[onEdgesChange, ydoc],
);

const onConnect = useCallback(
Expand Down Expand Up @@ -360,7 +293,6 @@ export const useCanvas = () => {
onNodeDragStart,
onNodeDragStop,
onConnect,
sortNodes,
cursors,
};
};
3 changes: 1 addition & 2 deletions apps/frontend/src/features/canvas/ui/Canvas/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ export function Canvas({ className }: CanvasProps) {
onNodeDragStart,
onNodeDragStop,
onConnect,
sortNodes,
cursors,
} = useCanvas();

Expand All @@ -63,7 +62,7 @@ export function Canvas({ className }: CanvasProps) {
>
<Controls />
<div className="fixed bottom-5 left-16 z-30 h-4 w-4 text-neutral-50 hover:cursor-pointer">
<button onClick={sortNodes}>Sort</button>
{/* <button onClick={sortNodes}>Sort</button> */}
</div>
<MiniMap />
<Background variant={BackgroundVariant.Dots} gap={12} size={1} />
Expand Down
Loading

0 comments on commit c366e05

Please sign in to comment.