Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/#168 블록 드래그앤드랍 서버 반영 #169

Merged
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
19 changes: 11 additions & 8 deletions @noctaCrdt/Crdt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
RemoteBlockInsertOperation,
RemoteCharInsertOperation,
CRDTSerializedProps,
RemoteReorderOperation,
RemoteBlockReorderOperation,
RemoteBlockUpdateOperation,
} from "./Interfaces";

Expand All @@ -22,17 +22,17 @@
this.LinkedList = new LinkedListClass();
}

localInsert(index: number, value: string, blockId?: BlockId, pageId?: string): any {

Check warning on line 25 in @noctaCrdt/Crdt.ts

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

'index' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 25 in @noctaCrdt/Crdt.ts

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

'value' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 25 in @noctaCrdt/Crdt.ts

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

'blockId' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 25 in @noctaCrdt/Crdt.ts

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

'pageId' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 25 in @noctaCrdt/Crdt.ts

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

Unexpected any. Specify a different type

Check warning on line 25 in @noctaCrdt/Crdt.ts

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

'index' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 25 in @noctaCrdt/Crdt.ts

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

'value' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 25 in @noctaCrdt/Crdt.ts

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

'blockId' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 25 in @noctaCrdt/Crdt.ts

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

'pageId' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 25 in @noctaCrdt/Crdt.ts

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

Unexpected any. Specify a different type
// 기본 CRDT에서는 구현하지 않고, 하위 클래스에서 구현
throw new Error("Method not implemented.");
}

localDelete(index: number, blockId?: BlockId, pageId?: string): any {

Check warning on line 30 in @noctaCrdt/Crdt.ts

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

'index' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 30 in @noctaCrdt/Crdt.ts

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

'blockId' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 30 in @noctaCrdt/Crdt.ts

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

'pageId' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 30 in @noctaCrdt/Crdt.ts

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

Unexpected any. Specify a different type

Check warning on line 30 in @noctaCrdt/Crdt.ts

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

'index' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 30 in @noctaCrdt/Crdt.ts

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

'blockId' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 30 in @noctaCrdt/Crdt.ts

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

'pageId' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 30 in @noctaCrdt/Crdt.ts

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

Unexpected any. Specify a different type
// 기본 CRDT에서는 구현하지 않고, 하위 클래스에서 구현
throw new Error("Method not implemented.");
}

remoteInsert(operation: any): void {

Check warning on line 35 in @noctaCrdt/Crdt.ts

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

'operation' is defined but never used. Allowed unused args must match /^_/u

Check warning on line 35 in @noctaCrdt/Crdt.ts

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

'operation' is defined but never used. Allowed unused args must match /^_/u
// 기본 CRDT에서는 구현하지 않고, 하위 클래스에서 구현
throw new Error("Method not implemented.");
}
Expand Down Expand Up @@ -149,20 +149,25 @@
targetId: BlockId;
beforeId: BlockId | null;
afterId: BlockId | null;
}): RemoteReorderOperation {
const operation: RemoteReorderOperation = {
pageId: string;
}): RemoteBlockReorderOperation {
const operation: RemoteBlockReorderOperation = {
...params,
clock: this.clock,
client: this.client,
};

this.LinkedList.reorderNodes(params);
this.LinkedList.reorderNodes({
targetId: params.targetId,
beforeId: params.beforeId,
afterId: params.afterId,
});
this.clock += 1;

return operation;
}

remoteReorder(operation: RemoteReorderOperation): void {
remoteReorder(operation: RemoteBlockReorderOperation): void {
const { targetId, beforeId, afterId, clock } = operation;

this.LinkedList.reorderNodes({
Expand All @@ -171,9 +176,7 @@
afterId,
});

if (this.clock <= clock) {
this.clock = clock + 1;
}
this.clock = Math.max(this.clock, clock) + 1;
}

serialize(): CRDTSerializedProps<Block> {
Expand Down
3 changes: 2 additions & 1 deletion @noctaCrdt/Interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,11 @@ export interface WorkSpaceSerializedProps {
pageList: Page[];
authUser: Map<string, string>;
}
export interface RemoteReorderOperation {
export interface RemoteBlockReorderOperation {
targetId: BlockId;
beforeId: BlockId | null;
afterId: BlockId | null;
clock: number;
client: number;
pageId: string;
}
23 changes: 13 additions & 10 deletions client/src/features/editor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,6 @@ export interface EditorStateProps {
}
// TODO: pageId, editorCRDT를 props로 받아와야함
export const Editor = ({ onTitleChange, pageId, serializedEditorData }: EditorProps) => {
/*
const {
sendCharInsertOperation,
sendCharDeleteOperation,
subscribeToRemoteOperations,
sendBlockInsertOperation,
sendBlockDeleteOperation,
sendBlockUpdateOperation,
} = useSocket();
*/
const {
sendCharInsertOperation,
sendCharDeleteOperation,
Expand All @@ -62,6 +52,7 @@ export const Editor = ({ onTitleChange, pageId, serializedEditorData }: EditorPr
editorCRDT: editorCRDT.current,
editorState,
setEditorState,
pageId,
});

const { handleKeyDown } = useMarkdownGrammer({
Expand Down Expand Up @@ -222,6 +213,18 @@ export const Editor = ({ onTitleChange, pageId, serializedEditorData }: EditorPr
currentBlock: prev.currentBlock,
}));
},

onRemoteBlockReorder: (operation) => {
console.log(operation, "block : 재정렬 확인합니다이");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

합니다이.. 할때마다 웃고 갑니다....

if (!editorCRDT.current) return;
editorCRDT.current.remoteReorder(operation);
setEditorState((prev) => ({
clock: editorCRDT.current.clock,
linkedList: editorCRDT.current.LinkedList,
currentBlock: prev.currentBlock,
}));
},

onRemoteCursor: (position) => {
console.log(position, "커서위치 수신");
},
Expand Down
10 changes: 9 additions & 1 deletion client/src/features/editor/hooks/useBlockDragAndDrop.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
// hooks/useBlockDragAndDrop.ts
import { DragEndEvent, PointerSensor, useSensor, useSensors } from "@dnd-kit/core";
import { EditorCRDT } from "@noctaCrdt/Crdt";
import { useSocketStore } from "@src/stores/useSocketStore.ts";
import { EditorStateProps } from "../Editor";

interface UseBlockDragAndDropProps {
editorCRDT: EditorCRDT;
editorState: EditorStateProps;
setEditorState: React.Dispatch<React.SetStateAction<EditorStateProps>>;
pageId: string;
}

export const useBlockDragAndDrop = ({
editorCRDT,
editorState,
setEditorState,
pageId,
}: UseBlockDragAndDropProps) => {
const sensors = useSensors(
useSensor(PointerSensor, {
Expand All @@ -22,6 +25,8 @@ export const useBlockDragAndDrop = ({
}),
);

const { sendBlockReorderOperation } = useSocketStore();

const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event;

Expand Down Expand Up @@ -73,12 +78,15 @@ export const useBlockDragAndDrop = ({
}

// EditorCRDT의 현재 상태로 작업
editorCRDT.localReorder({
const operation = editorCRDT.localReorder({
targetId: targetNode.id,
beforeId: beforeNode?.id || null,
afterId: afterNode?.id || null,
pageId,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저희가 논의했던 방식으로 구현하셨네요.
reorder를 서버에 송신해서 서버는 emit하고.
고생하셨습니다!

});

sendBlockReorderOperation(operation);

// EditorState 업데이트
setEditorState({
clock: editorCRDT.clock,
Expand Down
10 changes: 10 additions & 0 deletions client/src/stores/useSocketStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
RemoteCharInsertOperation,
RemoteCharDeleteOperation,
RemoteBlockUpdateOperation,
RemoteBlockReorderOperation,
CursorPosition,
WorkSpaceSerializedProps,
} from "@noctaCrdt/Interfaces";
Expand All @@ -24,6 +25,7 @@ interface SocketStore {
sendCharInsertOperation: (operation: RemoteCharInsertOperation) => void;
sendBlockDeleteOperation: (operation: RemoteBlockDeleteOperation) => void;
sendCharDeleteOperation: (operation: RemoteCharDeleteOperation) => void;
sendBlockReorderOperation: (operation: RemoteBlockReorderOperation) => void;
sendCursorPosition: (position: CursorPosition) => void;
subscribeToRemoteOperations: (handlers: RemoteOperationHandlers) => (() => void) | undefined;
subscribeToPageOperations: (handlers: PageOperationsHandlers) => (() => void) | undefined;
Expand All @@ -34,6 +36,7 @@ interface RemoteOperationHandlers {
onRemoteBlockUpdate: (operation: RemoteBlockUpdateOperation) => void;
onRemoteBlockInsert: (operation: RemoteBlockInsertOperation) => void;
onRemoteBlockDelete: (operation: RemoteBlockDeleteOperation) => void;
onRemoteBlockReorder: (operation: RemoteBlockReorderOperation) => void;
onRemoteCharInsert: (operation: RemoteCharInsertOperation) => void;
onRemoteCharDelete: (operation: RemoteCharDeleteOperation) => void;
onRemoteCursor: (position: CursorPosition) => void;
Expand Down Expand Up @@ -146,13 +149,19 @@ export const useSocketStore = create<SocketStore>((set, get) => ({
socket?.emit("cursor", position);
},

sendBlockReorderOperation: (operation: RemoteBlockReorderOperation) => {
const { socket } = get();
socket?.emit("reorder/block", operation);
},

subscribeToRemoteOperations: (handlers: RemoteOperationHandlers) => {
const { socket } = get();
if (!socket) return;

socket.on("update/block", handlers.onRemoteBlockUpdate);
socket.on("insert/block", handlers.onRemoteBlockInsert);
socket.on("delete/block", handlers.onRemoteBlockDelete);
socket.on("reorder/block", handlers.onRemoteBlockReorder);
socket.on("insert/char", handlers.onRemoteCharInsert);
socket.on("delete/char", handlers.onRemoteCharDelete);
socket.on("cursor", handlers.onRemoteCursor);
Expand All @@ -161,6 +170,7 @@ export const useSocketStore = create<SocketStore>((set, get) => ({
socket.off("update/block", handlers.onRemoteBlockUpdate);
socket.off("insert/block", handlers.onRemoteBlockInsert);
socket.off("delete/block", handlers.onRemoteBlockDelete);
socket.off("reorder/block", handlers.onRemoteBlockReorder);
socket.off("insert/char", handlers.onRemoteCharInsert);
socket.off("delete/char", handlers.onRemoteCharDelete);
socket.off("cursor", handlers.onRemoteCursor);
Expand Down
38 changes: 38 additions & 0 deletions server/src/crdt/crdt.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
RemoteCharInsertOperation,
RemoteBlockUpdateOperation,
RemotePageCreateOperation,
RemoteBlockReorderOperation,
CursorPosition,
} from "@noctaCrdt/Interfaces";
import { Logger } from "@nestjs/common";
Expand Down Expand Up @@ -362,6 +363,43 @@ export class CrdtGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
}
}

@SubscribeMessage("reorder/block")
async handleBlockReorder(
@MessageBody() data: RemoteBlockReorderOperation,
@ConnectedSocket() client: Socket,
): Promise<void> {
const clientInfo = this.clientMap.get(client.id);
try {
this.logger.debug(
`블록 Reorder 연산 수신 - Client ID: ${clientInfo?.clientId}, Data:`,
JSON.stringify(data),
);
// 1. 워크스페이스 가져오기
const workspace = this.workSpaceService.getWorkspace();

const currentPage = workspace.pageList.find((p) => p.id === data.pageId);
if (!currentPage) {
throw new Error(`Page with id ${data.pageId} not found`);
}
currentPage.crdt.remoteReorder(data);

// 5. 다른 클라이언트들에게 업데이트된 블록 정보 브로드캐스트
const operation = {
targetId: data.targetId,
beforeId: data.beforeId,
afterId: data.afterId,
pageId: data.pageId,
} as RemoteBlockReorderOperation;
client.broadcast.emit("reorder/block", operation);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

} catch (error) {
this.logger.error(
`블록 Reorder 연산 처리 중 오류 발생 - Client ID: ${clientInfo?.clientId}`,
error.stack,
);
throw new WsException(`Update 연산 실패: ${error.message}`);
}
}

/**
* 커서 위치 업데이트 처리
*/
Expand Down
Loading