+
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 36cc59b..3c67ba7 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,12 +1,8 @@
'use client';
import { Inter } from 'next/font/google';
-import { useEffect } from 'react';
-import { usePathname, useRouter } from 'next/navigation';
import NextProcessLoader from '@/components/nextTopLoader';
-import { PATHS, PATHS_SKIPPED_AUTH, STORAGE_KEY_AUTH } from '@/utils/constants';
-import { useAuthStore } from '@/store/authStore';
import './globals.css';
import './xterm.css';
@@ -18,34 +14,6 @@ export default function RootLayout({
}: Readonly<{
children: React.ReactNode;
}>) {
- const { setAuth, getAuth } = useAuthStore();
- const router = useRouter();
- const pathname = usePathname();
-
- useEffect(() => {
- // 首先,从zustand获取登录信息
- let auth = getAuth();
-
- // TODO 使用localstorage存放登录信息不合适
- // 如果zustand中没有登录信息,则从localStorage中读取
- if (!auth.access_token) {
- const storagedAuth = localStorage.getItem(STORAGE_KEY_AUTH);
-
- if (storagedAuth) {
- // 将localStorage中的登录信息同步到zustand
- auth = JSON.parse(storagedAuth);
-
- setAuth(auth);
- }
- }
-
- if (!PATHS_SKIPPED_AUTH.includes(pathname) && !auth.access_token) {
- // 当前路由需登录但未登录时,跳转到登录页
- // FIXME: 跳转时,会短暂显示目标页,再跳转到登录页
- router.push(`${PATHS.LOGIN}?redirect=${pathname}`);
- }
- }, [pathname]);
-
return (
diff --git a/src/components/cooperation/avatarList/index.tsx b/src/components/cooperation/avatarList/index.tsx
index d04e908..de846b0 100644
--- a/src/components/cooperation/avatarList/index.tsx
+++ b/src/components/cooperation/avatarList/index.tsx
@@ -6,13 +6,13 @@ export const AnimatedTooltip = ({
items,
}: {
items: {
- id: number;
+ id: string;
name: string;
designation: string;
image: string;
}[];
}) => {
- const [hoveredIndex, setHoveredIndex] = useState
(null);
+ const [hoveredIndex, setHoveredIndex] = useState(null);
return (
diff --git a/src/components/cooperation/cooperationEditor/index.tsx b/src/components/cooperation/cooperationEditor/index.tsx
index 2f45a57..1e7e963 100644
--- a/src/components/cooperation/cooperationEditor/index.tsx
+++ b/src/components/cooperation/cooperationEditor/index.tsx
@@ -1,6 +1,6 @@
'use client';
-import React, { useEffect, useMemo, useState } from 'react';
+import React, { memo, useEffect, useMemo, useState, useRef } from 'react';
import dynamic from 'next/dynamic';
import * as monaco from 'monaco-editor';
import * as Y from 'yjs';
@@ -25,60 +25,52 @@ const CooperationEditor: React.FC
= ({ roomId, userInfo
const [editor, setEditor] = useState(null);
const [provider, setProvider] = useState(null);
const [, setBinding] = useState(null);
- const [awareness, setAwareness] = useState();
- const { setPersons } = useCooperationPerson();
+ const [awareness, setAwareness] = useState([]);
+ const { removePersons, addPersons } = useCooperationPerson();
- useEffect(() => {}, []);
+ const emailsRef = useRef(new Set());
+ const emailMapRef = useRef(new Map());
useEffect(() => {
- if (roomId == null) {
- return;
- }
+ if (!roomId) return;
const provider = new WebsocketProvider(
`${process.env.NEXT_PUBLIC_WS_URL}`,
'collaborateDoc',
ydoc,
{
- params: {
- record_id: roomId,
- },
+ params: { record_id: roomId },
},
);
setProvider(provider);
const handleBeforeUnload = () => {
- provider.awareness.setLocalStateField('cursorLocation', {
- x: undefined,
- y: undefined,
- });
+ provider.awareness.setLocalStateField('cursorLocation', { x: undefined, y: undefined });
+ provider.destroy();
+ ydoc.destroy();
};
window.addEventListener('beforeunload', handleBeforeUnload);
return () => {
- provider?.destroy();
+ provider.destroy();
ydoc.destroy();
+ setBinding(null);
window.removeEventListener('beforeunload', handleBeforeUnload);
};
}, [ydoc, roomId]);
useEffect(() => {
- if (provider == null || editor == null || roomId === null) {
- return;
- }
+ if (!provider || !editor || !roomId) return;
- provider.awareness.setLocalStateField('cursorLocation', {
- x: undefined,
- y: undefined,
- });
+ provider.awareness.setLocalStateField('cursorLocation', { x: undefined, y: undefined });
provider.awareness.setLocalStateField('userInfo', userInfo);
const binding = new MonacoBinding(
ydoc.getText(),
editor.getModel()!,
new Set([editor]),
- provider?.awareness,
+ provider.awareness,
);
setBinding(binding);
@@ -86,26 +78,20 @@ const CooperationEditor: React.FC = ({ roomId, userInfo
const { clientX, clientY } = e;
const { innerWidth, innerHeight } = window;
- const isNearEdge =
- clientX < 10 || clientX > innerWidth - 10 || clientY < 10 || clientY > innerHeight - 10;
-
- if (isNearEdge) {
- provider.awareness.setLocalStateField('cursorLocation', {
- x: undefined,
- y: undefined,
- });
- } else {
- provider.awareness.setLocalStateField('cursorLocation', {
- x: clientX,
- y: clientY,
- });
- }
- }, 10);
- const handleMouseout = () => {
provider.awareness.setLocalStateField('cursorLocation', {
- x: undefined,
- y: undefined,
+ x:
+ clientX < 10 || clientX > innerWidth - 10 || clientY < 10 || clientY > innerHeight - 10
+ ? undefined
+ : clientX,
+ y:
+ clientX < 10 || clientX > innerWidth - 10 || clientY < 10 || clientY > innerHeight - 10
+ ? undefined
+ : clientY,
});
+ }, 10);
+
+ const handleMouseout = () => {
+ provider.awareness.setLocalStateField('cursorLocation', { x: undefined, y: undefined });
};
window.addEventListener('mousemove', handleMouseMove);
@@ -114,58 +100,58 @@ const CooperationEditor: React.FC = ({ roomId, userInfo
const styleElement = document.createElement('style');
document.head.appendChild(styleElement);
- provider.awareness.on(
- 'change',
- ({
- updated,
- added,
- removed,
- }: {
- updated: Array;
- added: Array;
- removed: Array;
- }) => {
- type UserAwarenessData = Map>;
-
- let awarenessState = provider.awareness.getStates() as UserAwarenessData;
- setAwareness(Array.from(awarenessState));
- console.log('awarenessState', awarenessState);
-
- let newStyles = '';
- // 如果一个用户打开2个标签也要处理为一个
- const emails = new Set();
-
- const updateUsers = (userIds: Array, action: 'add' | 'remove') => {
- userIds.forEach((id) => {
- const user = awarenessState.get(id);
-
- if (user && user.userInfo?.email) {
- const email = user.userInfo.email;
-
- if (action === 'add') {
- emails.add(email);
- setPersons(Array.from(emails));
- } else {
- emails.delete(email);
- setPersons(Array.from(emails));
- }
- }
- });
- };
+ const updateUsers = (userIds: Array, action: 'add' | 'remove' | 'update') => {
+ userIds.forEach((id) => {
+ const user = provider.awareness.getStates().get(id);
+ console.log(emailMapRef.current);
+
+ if (!user) {
+ const email = findKeyContainingElement(emailMapRef.current, id);
+
+ if (email) {
+ emailsRef.current.delete(email);
+ removePersons([email]);
+ }
+ } else if (user.userInfo?.email) {
+ const email = user.userInfo.email;
+
+ if (emailMapRef.current.has(email)) {
+ emailMapRef.current.set(
+ email,
+ Array.from(new Set([...emailMapRef.current.get(email), id])),
+ );
+ } else {
+ emailMapRef.current.set(email, [id]);
+ }
+
+ if (action === 'add') {
+ emailsRef.current.add(email);
+ addPersons(Array.from(emailsRef.current) as string[]);
+ } else if (action === 'remove') {
+ emailsRef.current.delete(email);
+ removePersons([email]);
+ }
+ }
+ });
+ };
- updateUsers(added, 'add');
- updateUsers(updated, 'add');
- updateUsers(removed, 'remove');
+ const throttledUpdate = throttle((changes: Record) => {
+ const { updated, added, removed } = changes;
+ const awarenessState = provider.awareness.getStates();
+ setAwareness(Array.from(awarenessState));
- for (let addedUserClientID of updated) {
- if (addedUserClientID === ydoc.clientID) return;
+ updateUsers(updated, 'update');
+ updateUsers(added, 'add');
+ updateUsers(removed, 'remove');
- let addUserId = '';
- Array.from(awarenessState).forEach(([o, t]) => {
- if (o === addedUserClientID) {
- addUserId = t.userInfo?.email;
- }
- });
+ let newStyles = '';
+
+ updated.forEach((addedUserClientID: any) => {
+ if (addedUserClientID === ydoc.clientID) return;
+
+ const addUserId = awarenessState.get(addedUserClientID)?.userInfo?.email;
+
+ if (addUserId) {
newStyles += `
.yRemoteSelection-${addedUserClientID} {
background-color: ${createColorFromId(addUserId)};
@@ -173,9 +159,7 @@ const CooperationEditor: React.FC = ({ roomId, userInfo
}
.yRemoteSelectionHead-${addedUserClientID} {
position: relative;
- border-left: 2px solid ${createColorFromId(addUserId)};
- border-top: 2px solid ${createColorFromId(addUserId)};
- border-bottom: 2px solid ${createColorFromId(addUserId)};
+ border: 2px solid ${createColorFromId(addUserId)};
height: 100%;
box-sizing: border-box;
}
@@ -192,10 +176,12 @@ const CooperationEditor: React.FC = ({ roomId, userInfo
}
`;
}
+ });
- styleElement.innerHTML += newStyles;
- },
- );
+ styleElement.innerHTML += newStyles;
+ }, 250);
+
+ provider.awareness.on('change', throttledUpdate);
return () => {
binding.destroy();
@@ -203,10 +189,11 @@ const CooperationEditor: React.FC = ({ roomId, userInfo
styleElement.remove();
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseout', handleMouseout);
+ provider.awareness.off('change', throttledUpdate); // 移除事件监听器
};
- }, [ydoc, provider, editor, roomId]);
+ }, [provider, editor, roomId, userInfo]);
- const handleEditorDidMount = async (editor: monaco.editor.IStandaloneCodeEditor) => {
+ const handleEditorDidMount = (editor: monaco.editor.IStandaloneCodeEditor) => {
setEditor(editor);
};
@@ -236,22 +223,30 @@ const CooperationEditor: React.FC = ({ roomId, userInfo
onMount={handleEditorDidMount}
/>
{awareness
- ?.filter(([id]) => id !== ydoc.clientID)
+ .filter(([id]) => id !== ydoc.clientID)
.filter(([, state]) => state.cursorLocation?.x !== undefined)
- .map(([id, state]) => {
- return (
-
- );
- })}
+ .map(([id, state]) => (
+
+ ))}
);
};
-export default CooperationEditor;
+function findKeyContainingElement(map: Map