diff --git a/@noctaCrdt/Crdt.ts b/@noctaCrdt/Crdt.ts index 153bb8fc..d6fcbf3b 100644 --- a/@noctaCrdt/Crdt.ts +++ b/@noctaCrdt/Crdt.ts @@ -1,16 +1,23 @@ import { LinkedList } from "./LinkedList"; import { NodeId, Node } from "./Node"; -import { RemoteInsertOperation, RemoteDeleteOperation, SerializedProps } from "./Interfaces"; +import { + RemoteInsertOperation, + RemoteDeleteOperation, + SerializedProps, + Block, + Char, + CRDT as CRDTClassProps, +} from "./Interfaces"; -export class CRDT { +export class CRDT implements CRDTClassProps { clock: number; client: number; - textLinkedList: LinkedList; + LinkedList: LinkedList; constructor(client: number) { this.clock = 0; // 이 CRDT의 논리적 시간 설정 this.client = client; - this.textLinkedList = new LinkedList(); + this.LinkedList = new LinkedList(); } /** @@ -21,7 +28,7 @@ export class CRDT { */ localInsert(index: number, value: string): RemoteInsertOperation { const id = new NodeId((this.clock += 1), this.client); - const remoteInsertion = this.textLinkedList.insertAtIndex(index, value, id); + const remoteInsertion = this.LinkedList.insertAtIndex(index, value, id); return { node: remoteInsertion.node }; } @@ -32,12 +39,12 @@ export class CRDT { */ localDelete(index: number): RemoteDeleteOperation { // 유효한 인덱스인지 확인 - if (index < 0 || index >= this.textLinkedList.spread().length) { + if (index < 0 || index >= this.LinkedList.spread().length) { throw new Error(`유효하지 않은 인덱스입니다: ${index}`); } // 삭제할 노드 찾기 - const nodeToDelete = this.textLinkedList.findByIndex(index); + const nodeToDelete = this.LinkedList.findByIndex(index); if (!nodeToDelete) { throw new Error(`삭제할 노드를 찾을 수 없습니다. 인덱스: ${index}`); } @@ -49,7 +56,7 @@ export class CRDT { }; // 로컬 삭제 수행 - this.textLinkedList.deleteNode(nodeToDelete.id); + this.LinkedList.deleteNode(nodeToDelete.id); // 클록 업데이트 this.clock += 1; @@ -66,7 +73,7 @@ export class CRDT { const newNode = new Node(operation.node.value, newNodeId); newNode.next = operation.node.next; newNode.prev = operation.node.prev; - this.textLinkedList.insertById(newNode); + this.LinkedList.insertById(newNode); // 동기화 논리적 시간 if (this.clock <= newNode.id.clock) { this.clock = newNode.id.clock + 1; @@ -80,7 +87,7 @@ export class CRDT { remoteDelete(operation: RemoteDeleteOperation): void { const { targetId, clock } = operation; if (targetId) { - this.textLinkedList.deleteNode(targetId); + this.LinkedList.deleteNode(targetId); } // 동기화 논리적 시간 if (this.clock <= clock) { @@ -93,15 +100,15 @@ export class CRDT { * @returns 현재 텍스트 */ read(): string { - return this.textLinkedList.stringify(); + return this.LinkedList.stringify(); } /** * 현재 텍스트를 배열로 반환합니다. * @returns 현재 텍스트 배열 */ - spread(): string[] { - return this.textLinkedList.spread(); + spread(): Block[] | Char[] { + return this.LinkedList.spread(); } /** @@ -109,7 +116,7 @@ export class CRDT { * @returns LinkedList 인스턴스 */ public getTextLinkedList(): LinkedList { - return this.textLinkedList; + return this.LinkedList; } /** @@ -121,8 +128,8 @@ export class CRDT { clock: this.clock, client: this.client, textLinkedList: { - head: this.textLinkedList.head, - nodeMap: this.textLinkedList.nodeMap, + head: this.LinkedList.head, + nodeMap: this.LinkedList.nodeMap, }, }; } diff --git a/@noctaCrdt/Interfaces.ts b/@noctaCrdt/Interfaces.ts index f3359df6..e6bd05c8 100644 --- a/@noctaCrdt/Interfaces.ts +++ b/@noctaCrdt/Interfaces.ts @@ -1,5 +1,7 @@ import { NodeId, Node } from "./Node"; +export type ElementType = "p" | "h1" | "h2" | "h3" | "ul" | "ol" | "li" | "checkbox" | "blockquote"; + export interface InsertOperation { node: Node; } @@ -30,3 +32,57 @@ export interface SerializedProps { nodeMap: { [key: string]: Node }; }; } + +export interface WorkSpace { + id: string; + pageList: Page[]; + authUser: object; +} + +export interface Page { + id: string; + title: string; + icon: string; // 추후 수정 + crdt: CRDT; +} + +export interface CRDT { + clock: number; + client: number; + LinkedList: LinkedList; + localInsert(index: number, value: string): RemoteInsertOperation; + localDelete(index: number): RemoteDeleteOperation; + remoteInsert(operation: RemoteInsertOperation): void; + remoteDelete(operation: RemoteDeleteOperation): void; + read(): string; + spread(): Block[] | Char[]; +} + +export interface LinkedList { + head: NodeId | null; + nodeMap: { [key: string]: Block | Char }; +} + +export interface Block { + id: BlockId; + icon: string; // 추후 수정 + type: ElementType; + animation: string; + crdt: CRDT; + indent: number; + next: NodeId; + prev: NodeId; + style: string[]; +} + +export interface Char { + id: NodeId; + value: string; + next: NodeId | null; + prev: NodeId | null; +} + +export interface BlockId { + clock: number; + client: number; +} diff --git a/@noctaCrdt/LinkedList.ts b/@noctaCrdt/LinkedList.ts index 84b589c8..6b252974 100644 --- a/@noctaCrdt/LinkedList.ts +++ b/@noctaCrdt/LinkedList.ts @@ -223,14 +223,14 @@ export class LinkedList { * 현재 리스트를 배열로 변환합니다. * @returns 배열로 변환된 리스트 */ - spread(): string[] { + spread(): Node[] { let currentNodeId = this.head; - const result: string[] = []; + const result: Node[] = []; while (currentNodeId !== null) { const currentNode = this.getNode(currentNodeId); if (!currentNode) break; - result.push(currentNode.value); + result.push(currentNode); currentNodeId = currentNode.next; } diff --git a/@noctaCrdt/Page.ts b/@noctaCrdt/Page.ts new file mode 100644 index 00000000..d6268e9d --- /dev/null +++ b/@noctaCrdt/Page.ts @@ -0,0 +1,18 @@ +import { Page as PageInterface } from "@noctaCrdt/Interfaces"; +import { CRDT } from "./Crdt"; + +export class Page implements PageInterface { + id: string; + title: string; + icon: string; + crdt: CRDT; + + constructor(editorCRDT: CRDT = new CRDT(0)) { + // 추후 수정 + this.id = "id"; + this.title = "title"; + this.icon = "icon"; + this.crdt = editorCRDT; + // 직렬화, 역직렬화 메서드 추가 + } +} diff --git a/@noctaCrdt/WorkSpace.ts b/@noctaCrdt/WorkSpace.ts new file mode 100644 index 00000000..f6bf80b7 --- /dev/null +++ b/@noctaCrdt/WorkSpace.ts @@ -0,0 +1,15 @@ +import { WorkSpace as WorkSpaceInterface } from "./Interfaces"; +import { Page } from "./Page"; + +export class WorkSpace implements WorkSpaceInterface { + id: string; + pageList: Page[]; + authUser: object; + + constructor(id: string, pageList: Page[]) { + this.id = id; + this.pageList = pageList; + this.authUser = new Map(); + // 직렬화, 역직렬화 메서드 추가 + } +}