Skip to content

Commit

Permalink
TestApp.tsx 생성
Browse files Browse the repository at this point in the history
  • Loading branch information
hyonun321 committed Nov 14, 2024
1 parent 4c33ca2 commit f96eecc
Showing 1 changed file with 189 additions and 0 deletions.
189 changes: 189 additions & 0 deletions client/src/TestApp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import { BlockCRDT } from "@noctaCrdt/Crdt";
import { Char } from "@noctaCrdt/Node";
import { CharId } from "@noctaCrdt/NodeId";
import { useEffect, useState, useRef } from "react";
import { io, Socket } from "socket.io-client";

interface RemoteInsertOperation {
node: Char; // Node<NodeId>μ—μ„œ Char둜 λ³€κ²½
}
interface RemoteDeleteOperation {
targetId: CharId;
clock: number;
}

export const TestApp = () => {
const contentRef = useRef<HTMLDivElement>(null);
const crdtRef = useRef<BlockCRDT | null>(null); // CRDTλ₯Ό BlockCRDT둜 λ³€κ²½
const [content, setContent] = useState("");
const socketRef = useRef<Socket | null>(null);

useEffect(() => {
const socket: Socket = io("http://localhost:3000");
socketRef.current = socket;

socket.on("connect", () => {
socket.emit("requestId");
});

socket.on("assignId", (id: number) => {
crdtRef.current = new BlockCRDT(id);
});

socket.on("document", (data: any) => {

Check warning on line 33 in client/src/TestApp.tsx

View workflow job for this annotation

GitHub Actions / Lint and Unit Test

Unexpected any. Specify a different type
if (crdtRef.current) {
crdtRef.current.clock = data.clock;
crdtRef.current.client = data.client;

if (data.LinkedList.head) {
crdtRef.current.LinkedList.head = new CharId( // NodeIdλ₯Ό CharId둜 λ³€κ²½
data.LinkedList.head.clock,
data.LinkedList.head.client,
);
} else {
crdtRef.current.LinkedList.head = null;
}

crdtRef.current.LinkedList.nodeMap = {};
for (const key in data.LinkedList.nodeMap) {
const node = data.LinkedList.nodeMap[key];
const reconstructedNode = new Char(node.value, new CharId(node.id.clock, node.id.client)); // Nodeλ₯Ό Char둜 λ³€κ²½
reconstructedNode.next = node.next ? new CharId(node.next.clock, node.next.client) : null;
reconstructedNode.prev = node.prev ? new CharId(node.prev.clock, node.prev.client) : null;
crdtRef.current.LinkedList.nodeMap[key] = reconstructedNode;
}

setContent(crdtRef.current.read());
}
});

socket.on("insert", (operation: RemoteInsertOperation) => {
if (crdtRef.current) {
crdtRef.current.remoteInsert(operation);
setContent(crdtRef.current.read());
}
});

socket.on("delete", (operation: RemoteDeleteOperation) => {
if (crdtRef.current) {
crdtRef.current.remoteDelete(operation);
setContent(crdtRef.current.read());
}
});

return () => {
socket.disconnect();
};
}, []);
// content μƒνƒœκ°€ λ³€κ²½λ˜λ©΄ contentEditable divλ₯Ό μ—…λ°μ΄νŠΈ
useEffect(() => {
if (contentRef.current && contentRef.current.innerText !== content) {
contentRef.current.innerText = content;
}
}, [content]);

/**
* 두 λ¬Έμžμ—΄ κ°„μ˜ 차이 인덱슀λ₯Ό μ°ΎμŠ΅λ‹ˆλ‹€.
* @param oldStr 이전 λ¬Έμžμ—΄
* @param newStr ν˜„μž¬ λ¬Έμžμ—΄
* @returns 차이가 λ°œμƒν•œ 인덱슀
*/
const findDifferenceIndex = (oldStr: string, newStr: string): number => {
let i = 0;
while (i < oldStr.length && i < newStr.length && oldStr[i] === newStr[i]) {
i += 1;
}
return i;
};

// 캐럿 μ»€μ„œ μœ„μΉ˜λ‘œ μΆ”κ°€ 인덱슀λ₯Ό 정해도 λ κ²ƒκ°™μ€λŠλ‚Œ.

// const handleCursorChange = () => {
// if (!crdtRef.current || !contentRef.current || !socketRef.current) return;

// const selection = window.getSelection();
// if (selection && selection.rangeCount > 0) {
// const range = selection.getRangeAt(0);
// const preCaretRange = range.cloneRange();
// preCaretRange.selectNodeContents(contentRef.current);
// preCaretRange.setEnd(range.endContainer, range.endOffset);
// const caretPosition = preCaretRange.toString().length;

// const cursorData: CursorPosition = {
// clientId: numericId,
// position: caretPosition,
// };

// // μ„œλ²„λ‘œ μ»€μ„œ μœ„μΉ˜ μ „νŒŒ
// socketRef.current.emit("cursor", cursorData);
// }
// };
/**
* μ‚¬μš©μž μž…λ ₯ ν•Έλ“€λŸ¬
*/
const handleInput = () => {
if (!crdtRef.current || !contentRef.current || !socketRef.current) return;
const newContent = contentRef.current.innerText || "";
const oldContent = crdtRef.current.read();
const diffIndex = findDifferenceIndex(oldContent, newContent);

const trimmedNewContent = newContent.trim();
console.log("newContent:", JSON.stringify(newContent));
console.log("oldContent:", oldContent);
console.log("newContent.length:", newContent.length, "oldContent.length:", oldContent.length);

if (trimmedNewContent.length > oldContent.length) {
// μ‚½μž…μ΄ λ°œμƒν•œ 경우
const insertedChar = newContent[diffIndex];
console.log(`μ‚½μž…: '${insertedChar}' at index ${diffIndex}`);

// CRDT 둜컬 μ‚½μž… μˆ˜ν–‰
const insertOp = crdtRef.current.localInsert(diffIndex, insertedChar);

// μ„œλ²„λ‘œ μ‚½μž… μ—°μ‚° μ „νŒŒ
socketRef.current.emit("insert", insertOp);

// 둜컬 ν…μŠ€νŠΈ μ—…λ°μ΄νŠΈ
setContent(crdtRef.current.read());
} else if (trimmedNewContent.length < oldContent.length) {
// μ‚­μ œκ°€ λ°œμƒν•œ 경우
console.log(`μ‚­μ œ: at index ${diffIndex}`);

// CRDT 둜컬 μ‚­μ œ μˆ˜ν–‰
const deleteOp = crdtRef.current.localDelete(diffIndex);
console.log(deleteOp, typeof deleteOp);
// μ„œλ²„λ‘œ μ‚­μ œ μ—°μ‚° μ „νŒŒ
socketRef.current.emit("delete", deleteOp);

// 둜컬 ν…μŠ€νŠΈ μ—…λ°μ΄νŠΈ
setContent(crdtRef.current.read());
} else {
// 변경이 μ—†κ±°λ‚˜, ꡐ체 λ°œμƒν•œ 경우 (단일 ꡐ체λ₯Ό μ²˜λ¦¬ν•˜λ €λ©΄ μΆ”κ°€ 둜직 ν•„μš”)
console.log("λ³€κ²½ μ—†μŒ λ˜λŠ” ꡐ체 λ°œμƒ.");
// μΆ”κ°€ 둜직 κ΅¬ν˜„ κ°€λŠ₯
}
};

return (
<div style={{ padding: "20px", maxWidth: "800px", margin: "0 auto" }}>
<h1>μ‹€μ‹œκ°„ νŽΈμ§‘κΈ° (CRDT 적용)</h1>
<p>ν΄λΌμ΄μ–ΈνŠΈ 숫자ID: {0}</p>
<div
contentEditable="true"
ref={contentRef}
onInput={handleInput}
// onSelect={handleCursorChange}
style={{
border: "1px solid #ddd",
padding: "8px",
minHeight: "150px",
width: "100%",
outline: "none",
whiteSpace: "pre-wrap",
}}
/>
<h2>CRDT μƒνƒœ</h2>
<pre>{JSON.stringify(crdtRef.current?.LinkedList, null, 2)}</pre>
</div>
);
};

0 comments on commit f96eecc

Please sign in to comment.