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

feat:collaborate edit #128

Merged
merged 5 commits into from
Oct 21, 2024
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
32 changes: 16 additions & 16 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 75 additions & 2 deletions src/components/editor/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
'use client';

import { useCallback, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import Editor, { Monaco, loader } from '@monaco-editor/react';
import * as monaco from 'monaco-editor';
import { editor } from 'monaco-editor';
import { useDroppable } from '@dnd-kit/core';
import { createHighlighter } from 'shiki';
import { shikiToMonaco } from '@shikijs/monaco';
import * as Y from 'yjs';
import { WebsocketProvider } from 'y-websocket';
import { MonacoBinding } from 'y-monaco';
import parserBabel from 'prettier/plugins/babel';
import parserEstree from 'prettier/plugins/estree';
// import parserTypescript from 'prettier/plugins/typescript';
Expand All @@ -30,6 +33,15 @@ interface CodeEditorProps {
}
export type EditorWithThemeService = monaco.editor.IStandaloneCodeEditor & { _themeService: any };

interface CollaborateUser {
name: string;
color: string;
cursor: {
x: number;
y: number;
};
}

export default function CodeEditor({ editorId }: CodeEditorProps) {
const { webContainerInstance } = useWebContainerStore();
const { updateItem, fileData } = useUploadFileDataStore();
Expand All @@ -38,7 +50,7 @@ export default function CodeEditor({ editorId }: CodeEditorProps) {
const { setModels, models } = useModelsStore();
const { activeMap, setActiveModel } = useActiveModelStore();
const { activeEditorId, setActiveEditor } = useActiveEditorStore();
const thisEditor = getEditor(editorId);
const thisEditor = getEditor(editorId) as EditorWithThemeService;
const currentModel = activeMap[editorId];

const [_editor, _setEditor] = useState<monaco.editor.IStandaloneCodeEditor | undefined>();
Expand All @@ -50,6 +62,9 @@ export default function CodeEditor({ editorId }: CodeEditorProps) {
const currentPath = (activeMap[editorId]?.model as any)?.path;
const currentId = activeMap[editorId]?.model?.id;

const [, setUserInfo] = useState<Array<CollaborateUser>>([]);
const [provider, setProvider] = useState<WebsocketProvider>();

const { isOver, setNodeRef } = useDroppable({
id: editorId,
data: {
Expand Down Expand Up @@ -184,6 +199,64 @@ export default function CodeEditor({ editorId }: CodeEditorProps) {
webContainerInstance && writeFile(currentPath, value, webContainerInstance);
};

const ydoc = useMemo(() => new Y.Doc(), []);

useEffect(() => {
if (!ydoc) return;

//先用monaco官方的
const provider = new WebsocketProvider('wss://demos.yjs.dev/ws', 'monaco-react-2', ydoc);
setProvider(provider);

// 获取 provider 的 awareness 实例
const awareness = provider.awareness;

// 设置当前用户的状态
awareness.setLocalStateField('user', {
name: 'Alice', // 用户名,可以动态设置, 设置当前协作用户信息
color: '#ff0000', // 用户颜色
cursor: { x: 10, y: 20 }, // 光标位置信息
});

// 获取当前在线的用户信息
const getAllUsers = () => {
const userStates = Array.from(awareness.getStates().values()) as [CollaborateUser];
setUserInfo(userStates); // 更新用户状态到 state
};

// 初始化时获取所有在线用户
getAllUsers();

// 监听用户状态更新事件,当有用户加入、离开或更新状态时调用
awareness.on('update', () => {
getAllUsers();
});

// 清理函数,断开 WebSocket 连接
return () => {
provider.disconnect();
};
}, [ydoc]);

useEffect(() => {
if (provider === null || thisEditor === null || thisEditor.getModel() === null) {
return;
}

// Y.applyUpdate(ydoc, data) 这里可以把后台数据库存储的拿过来渲染

const binding = new MonacoBinding(
ydoc.getText('monaco'),
thisEditor.getModel()!,
new Set([thisEditor]),
provider?.awareness,
);

return () => {
binding.destroy();
};
}, [provider, thisEditor, ydoc]);

return (
<div
ref={setNodeRef}
Expand Down
Loading