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/#156 server에 Page별 editor crdt 적용 #163

Merged
merged 28 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4818000
feat: 페이지 클릭해도 캐럿 이동 구현
Ludovico7 Nov 19, 2024
37ade42
Merge branch 'dev' of https://github.com/boostcampwm-2024/web33-boost…
Ludovico7 Nov 19, 2024
359d346
작업사항 저장 추후 삭제
hyonun321 Nov 19, 2024
2f039d7
Merge branch 'Feature/#156_server에_Page별_EditorCRDT_적용' of https://gi…
Ludovico7 Nov 20, 2024
ebc8048
temp: 작업사항 커밋
Ludovico7 Nov 20, 2024
c38c5c5
feat: 기존 editorref구조로 수정
Ludovico7 Nov 20, 2024
e6f495f
refactor: editor, 키입력 핸들러 서버에서 받아온 값 사용하도록 수정
Ludovico7 Nov 20, 2024
0565a7a
fix: 캐럿 이상하게 초기화되는 문제 수정
Ludovico7 Nov 20, 2024
d246ded
refactor: CRDT 라이브러리 직렬화, 역직렬화 메서드 추가
Ludovico7 Nov 20, 2024
9ab3021
feat: workspace 직렬화, 역직렬화 메서드 추가
Ludovico7 Nov 20, 2024
81f4767
feat: 소켓 데이터 수신하여 로컬 인스턴스 생성
Ludovico7 Nov 20, 2024
16a9038
feat: 서버 인스턴스 메서드 추가
Ludovico7 Nov 20, 2024
81679a3
feat: 소켓 연결 커스텀 훅 zustand로 변경
Ludovico7 Nov 20, 2024
c687408
refactor: authUser 역직렬화
Ludovico7 Nov 20, 2024
1f7f8f8
fix: 진행사항 커밋
Ludovico7 Nov 20, 2024
c01f779
변경사항 저장
Ludovico7 Nov 20, 2024
3c1c3cf
fix: 서버와 클라이언트 CRDT 인스턴스 동기화
Ludovico7 Nov 21, 2024
5082250
chore: build 에러 수정
hyonun321 Nov 21, 2024
d9519c0
fix: 블럭 업데이트 수정
Ludovico7 Nov 21, 2024
4aac477
fix: 블록 타입 변경시 통신 안되는 버그 수정
Ludovico7 Nov 21, 2024
2fcfeba
chore : build되게 개선
hyonun321 Nov 21, 2024
bb091a6
fix: 페이지 추가 진행상황 저장
Ludovico7 Nov 21, 2024
54b6e76
변경사항 저장
Ludovico7 Nov 21, 2024
641fbc8
feat: create/page 연동 추가
hyonun321 Nov 21, 2024
cae8395
chore: 빌드에러 수정
hyonun321 Nov 21, 2024
71db95e
Merge branch 'Feature/#156_server에_page별_EditorCRDT_적용' of https://gi…
hyonun321 Nov 21, 2024
bcddda1
Merge branch 'dev' of https://github.com/boostcampwm-2024/web33-Nocta…
pipisebastian Nov 21, 2024
5024188
chore: lint 설정
Ludovico7 Nov 21, 2024
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@
.DS_Store
.env

tsconfig.tsbuildinfo

# Jest globalConfig file
../globalConfig.json
121 changes: 96 additions & 25 deletions @noctaCrdt/Crdt.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,46 @@
import { LinkedList } from "./LinkedList";
import { LinkedList, BlockLinkedList, TextLinkedList } from "./LinkedList";
import { CharId, BlockId, NodeId } from "./NodeId";
import { Node, Char, Block } from "./Node";
import {
RemoteBlockDeleteOperation,
RemoteCharDeleteOperation,
RemoteBlockInsertOperation,
RemoteCharInsertOperation,
SerializedProps,
CRDTSerializedProps,
RemoteReorderOperation,
RemoteBlockUpdateOperation,
} from "./Interfaces";

export class CRDT<T extends Node<NodeId>> {
clock: number;
client: number;
LinkedList: LinkedList<T>;

constructor(client: number) {
constructor(client: number, LinkedListClass: new () => LinkedList<T>) {
this.clock = 0;
this.client = client;
this.LinkedList = new LinkedList<T>();
this.LinkedList = new LinkedListClass();
}

localInsert(index: number, value: string, blockId?: BlockId) {}
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) {}
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: RemoteBlockInsertOperation | RemoteCharInsertOperation) {}
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.");
}

remoteDelete(operation: RemoteBlockDeleteOperation | RemoteCharDeleteOperation) {}
remoteDelete(operation: any): void {
// 기본 CRDT에서는 구현하지 않고, 하위 클래스에서 구현
throw new Error("Method not implemented.");
}

read(): string {
return this.LinkedList.stringify();
Expand All @@ -37,23 +50,27 @@
return this.LinkedList.spread();
}

serialize(): SerializedProps<T> {
serialize(): CRDTSerializedProps<T> {
return {
clock: this.clock,
client: this.client,
LinkedList: {
head: this.LinkedList.head,
nodeMap: this.LinkedList.nodeMap || {},
},
LinkedList: this.LinkedList.serialize(),
};
}

deserialize(data: any): void {
this.clock = data.clock;
this.client = data.client;
this.LinkedList.deserialize(data.LinkedList);
}
}

// EditorCRDT 클래스: 블록을 관리
export class EditorCRDT extends CRDT<Block> {
currentBlock: Block | null;

constructor(client: number) {
super(client);
super(client, BlockLinkedList);
this.currentBlock = null;
}

Expand All @@ -64,7 +81,7 @@
return { node: remoteInsertion.node } as RemoteBlockInsertOperation;
}

localDelete(index: number): RemoteBlockDeleteOperation {
localDelete(index: number, blockId: undefined, pageId: string): RemoteBlockDeleteOperation {
if (index < 0 || index >= this.LinkedList.spread().length) {
throw new Error(`Invalid index: ${index}`);
}
Expand All @@ -76,7 +93,8 @@

const operation: RemoteBlockDeleteOperation = {
targetId: nodeToDelete.id,
clock: this.clock + 1,
clock: this.clock,
pageId,
};

this.LinkedList.deleteNode(nodeToDelete.id);
Expand All @@ -85,9 +103,15 @@
return operation;
}

remoteUpdate(block: Block) {
this.LinkedList.nodeMap[JSON.stringify(block.id)] = block;
return { remoteUpdateOperation: block };
remoteUpdate(block: Block, pageId: string): RemoteBlockUpdateOperation {
const updatedBlock = this.LinkedList.nodeMap[JSON.stringify(block.id)];
updatedBlock.animation = block.animation;
updatedBlock.icon = block.icon;
updatedBlock.indent = block.indent;
updatedBlock.style = block.style;
updatedBlock.type = block.type;
// this.LinkedList.nodeMap[JSON.stringify(block.id)] = block;
return { node: updatedBlock, pageId };
}

remoteInsert(operation: RemoteBlockInsertOperation): void {
Expand All @@ -99,19 +123,26 @@

this.LinkedList.insertById(newNode);

this.clock = Math.max(this.clock, operation.node.id.clock) + 1;
/*
if (this.clock <= newNode.id.clock) {
this.clock = newNode.id.clock + 1;
}
*/
}

remoteDelete(operation: RemoteBlockDeleteOperation): void {
const { targetId, clock } = operation;
if (targetId) {
this.LinkedList.deleteNode(targetId);
const targetNodeId = new BlockId(operation.targetId.clock, operation.targetId.client);
this.LinkedList.deleteNode(targetNodeId);
}
this.clock = Math.max(this.clock, clock) + 1;
/*
if (this.clock <= clock) {
this.clock = clock + 1;
}
*/
}

localReorder(params: {
Expand All @@ -121,7 +152,7 @@
}): RemoteReorderOperation {
const operation: RemoteReorderOperation = {
...params,
clock: this.clock + 1,
clock: this.clock,
client: this.client,
};

Expand All @@ -144,29 +175,48 @@
this.clock = clock + 1;
}
}

serialize(): CRDTSerializedProps<Block> {
return {
...super.serialize(),
currentBlock: this.currentBlock ? this.currentBlock.serialize() : null,
};
}

deserialize(data: any): void {
super.deserialize(data);
this.currentBlock = data.currentBlock ? Block.deserialize(data.currentBlock) : null;
}
}

// BlockCRDT 클래스: 문자(Char)를 관리
export class BlockCRDT extends CRDT<Char> {
currentCaret: number;

constructor(client: number) {
super(client);
super(client, TextLinkedList);
this.currentCaret = 0;
}

localInsert(index: number, value: string, blockId: BlockId): RemoteCharInsertOperation {
localInsert(
index: number,
value: string,
blockId: BlockId,
pageId: string,
): RemoteCharInsertOperation {
const id = new CharId(this.clock + 1, this.client);
const { node } = this.LinkedList.insertAtIndex(index, value, id);
this.clock += 1;
const operation: RemoteCharInsertOperation = {
node,
blockId,
pageId,
};

return operation;
}

localDelete(index: number, blockId: BlockId): RemoteCharDeleteOperation {
localDelete(index: number, blockId: BlockId, pageId: string): RemoteCharDeleteOperation {
if (index < 0 || index >= this.LinkedList.spread().length) {
throw new Error(`Invalid index: ${index}`);
}
Expand All @@ -178,8 +228,9 @@

const operation: RemoteCharDeleteOperation = {
targetId: nodeToDelete.id,
clock: this.clock + 1,
clock: this.clock,
blockId,
pageId,
};

this.LinkedList.deleteNode(nodeToDelete.id);
Expand Down Expand Up @@ -212,4 +263,24 @@
this.clock = clock + 1;
}
}

serialize(): CRDTSerializedProps<Char> {
return {
...super.serialize(),
currentCaret: this.currentCaret,
};
}

static deserialize(data: any): BlockCRDT {
const crdt = new BlockCRDT(data.client);
crdt.clock = data.clock;
crdt.LinkedList.deserialize(data.LinkedList);
crdt.currentCaret = data.currentCaret;
return crdt;
}

deserialize(data: any): void {
super.deserialize(data);
this.currentCaret = data.currentCaret;
}
}
41 changes: 39 additions & 2 deletions @noctaCrdt/Interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { NodeId, BlockId, CharId } from "./NodeId";
import { Block, Char } from "./Node";
import { Page } from "./Page";
import { EditorCRDT } from "./Crdt";

export type ElementType = "p" | "h1" | "h2" | "h3" | "ul" | "ol" | "li" | "checkbox" | "blockquote";

Expand All @@ -12,42 +14,72 @@ export interface DeleteOperation {
clock: number;
}

export interface RemotePageCreateOperation {
clientId: number;
workspaceId: string;
page?: Page;
}

export interface RemoteBlockUpdateOperation {
node: Block;
pageId: string;
}

export interface RemoteBlockInsertOperation {
node: Block;
pageId: string;
}

export interface RemoteCharInsertOperation {
node: Char;
blockId: BlockId;
pageId: string;
}

export interface RemoteBlockDeleteOperation {
targetId: BlockId;
clock: number;
pageId: string;
}

export interface RemoteCharDeleteOperation {
targetId: CharId;
clock: number;
blockId?: BlockId;
pageId: string;
}

export interface CursorPosition {
clientId: number;
position: number;
}

export interface SerializedProps<T> {
// CRDT 직렬화라서 이름바꿔야함.
export interface CRDTSerializedProps<T> {
clock: number;
client: number;
LinkedList: {
head: NodeId | null;
nodeMap: { [key: string]: T };
};
currentBlock?: Block | null;
currentCaret?: number | null;
}

export interface serializedEditorDataProps {
clock: number;
client: number;
LinkedList: {
head: NodeId | null;
nodeMap: { [key: string]: Block };
};
currentBlock: Block | null;
}

export interface serializedPageProps {
id: string;
title: string;
icon: string;
crdt: EditorCRDT;
}

export interface ReorderNodesProps {
Expand All @@ -56,6 +88,11 @@ export interface ReorderNodesProps {
afterId: BlockId | null;
}

export interface WorkSpaceSerializedProps {
id: string;
pageList: Page[];
authUser: Map<string, string>;
}
export interface RemoteReorderOperation {
targetId: BlockId;
beforeId: BlockId | null;
Expand Down
Loading
Loading