Files
ot-demo/src/App.tsx
T
2026-01-07 00:04:56 +08:00

107 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useLayoutEffect, useRef, useState } from "react";
import "./App.css";
import { Client } from "./ot/client";
import { generateOperations } from "./ot/helpers";
import { getDemoServerInstance } from "./ot/server";
function Editor({ name }: { name: string }) {
const clientRef = useRef<Client | null>(null);
const [doc, setDoc] = useState<string>("");
const prevDocRef = useRef<string>("");
const [enableSync, setEnableSync] = useState<boolean>(true);
// 初始化client和文档状态
useLayoutEffect(() => {
if (!clientRef.current) {
clientRef.current = new Client();
clientRef.current.name = name;
const initialDoc = clientRef.current.document || "";
prevDocRef.current = initialDoc;
setDoc(initialDoc);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
if (!clientRef.current) return;
// 定期同步client的文档状态到UI
const timer = setInterval(() => {
const currentDoc = clientRef.current!.document || "";
if (currentDoc !== prevDocRef.current) {
setDoc(currentDoc);
prevDocRef.current = currentDoc;
}
}, 100);
return () => clearInterval(timer);
}, []);
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
if (!clientRef.current) return;
const newValue = e.target.value;
// 使用client当前文档状态作为旧值
const oldValue = clientRef.current.document || "";
if (newValue !== oldValue) {
// 生成操作集合
const operations = generateOperations(oldValue, newValue);
// 按顺序应用操作
operations.forEach((op) => {
clientRef.current!.applyClient(op);
});
}
};
const handleToggleSync = () => {
const newValue = !enableSync;
setEnableSync(newValue);
if (clientRef.current) {
clientRef.current.setEnableSync(newValue);
}
};
return (
<div style={{ border: "1px solid #ccc", padding: 10 }}>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 10,
}}
>
<p style={{ margin: 0 }}>Document</p>
<label style={{ display: "flex", alignItems: "center", gap: 8, cursor: "pointer" }}>
<input type="checkbox" checked={enableSync} onChange={handleToggleSync} />
<span></span>
</label>
</div>
<textarea value={doc} onChange={handleChange} rows={5} />
</div>
);
}
function App() {
const [serverDoc, setServerDoc] = useState<string>("");
useEffect(() => {
const timer = setInterval(() => {
setServerDoc(getDemoServerInstance().document || "");
}, 100);
return () => clearInterval(timer);
}, []);
return (
<div>
<div style={{ border: "1px solid #ccc", padding: 10, height: 100 }}>
<p>Server Document:</p>
<p>{serverDoc}</p>
</div>
<div style={{ display: "flex", gap: 10, marginTop: 20 }}>
<Editor name="A" />
<Editor name="B" />
</div>
</div>
);
}
export default App;