diff --git a/shared/api/socket.ts b/shared/api/socket.ts index 42871d7a..bba3dfd3 100644 --- a/shared/api/socket.ts +++ b/shared/api/socket.ts @@ -2,6 +2,8 @@ import { io, Socket } from 'socket.io-client'; import _isNil from 'lodash/isNil'; import { getServiceUrl } from '../manager/service'; import { isDevelopment } from '../utils/environment'; +import { showToasts } from '../manager/ui'; +import { t } from '../i18n'; let socket: Socket; @@ -88,7 +90,13 @@ export function createSocket(token: string): Promise { socket.once('connect', () => { // 连接成功 const appSocket = new AppSocket(socket); - appSocket.request('chat.converse.findAndJoinRoom'); // 立即请求加入房间 + appSocket.request('chat.converse.findAndJoinRoom').catch((err) => { + console.error(err); + showToasts( + t('无法加入房间, 您将无法获取到最新的信息, 请刷新页面后重试'), + 'error' + ); + }); // 立即请求加入房间 resolve(appSocket); }); socket.once('error', () => { diff --git a/shared/hooks/useUpdateRef.ts b/shared/hooks/useUpdateRef.ts new file mode 100644 index 00000000..54990026 --- /dev/null +++ b/shared/hooks/useUpdateRef.ts @@ -0,0 +1,8 @@ +import { useRef, MutableRefObject } from 'react'; + +export function useUpdateRef(state: T): MutableRefObject { + const ref = useRef(state); + ref.current = state; + + return ref; +} diff --git a/shared/index.tsx b/shared/index.tsx index 1d62e538..be212c5d 100644 --- a/shared/index.tsx +++ b/shared/index.tsx @@ -33,6 +33,7 @@ export { useAsyncFn } from './hooks/useAsyncFn'; export { useAsyncRequest } from './hooks/useAsyncRequest'; export { useMountedState } from './hooks/useMountedState'; export { useRafState } from './hooks/useRafState'; +export { useUpdateRef } from './hooks/useUpdateRef'; // manager export { getStorage, setStorage, useStorage } from './manager/storage'; diff --git a/shared/model/group.ts b/shared/model/group.ts index 5fb5a438..d2ce0715 100644 --- a/shared/model/group.ts +++ b/shared/model/group.ts @@ -135,3 +135,22 @@ export async function applyGroupInvite(inviteCode: string): Promise { code: inviteCode, }); } + +/** + * 创建群组面板 + */ +export async function createGroupPanel( + groupId: string, + options: { + name: string; + type: number; + parentId?: string; + provider?: string; + meta?: Record; + } +) { + await request.post('/api/group/createGroupPanel', { + ...options, + groupId, + }); +} diff --git a/web/src/components/modals/CreateGroupPanel.tsx b/web/src/components/modals/CreateGroupPanel.tsx index 68449972..c7ea7abc 100644 --- a/web/src/components/modals/CreateGroupPanel.tsx +++ b/web/src/components/modals/CreateGroupPanel.tsx @@ -1,13 +1,68 @@ import React from 'react'; -import { t } from 'tailchat-shared'; +import { + FastFormFieldMeta, + GroupPanelType, + t, + useAsyncRequest, +} from 'tailchat-shared'; +import { createGroupPanel } from '../../../../shared/model/group'; import { ModalWrapper } from '../Modal'; +import { WebFastForm } from '../WebFastForm'; +type Values = { + name: string; + type: string; +}; + +const baseFields: FastFormFieldMeta[] = [ + { type: 'text', name: 'name', label: t('面板名') }, + { + type: 'select', + name: 'type', + label: t('类型'), + options: [ + { + label: t('聊天频道'), + value: GroupPanelType.TEXT, + }, + { + label: t('面板分组'), + value: GroupPanelType.GROUP, + }, + ], + }, +]; + +/** + * 创建群组面板 + */ export const ModalCreateGroupPanel: React.FC<{ groupId: string; + onCreateSuccess: () => void; }> = React.memo((props) => { + const [, handleSubmit] = useAsyncRequest( + async (values: Values) => { + const { name, type } = values; + let panelType: number; + + if (typeof type === 'string') { + panelType = GroupPanelType.PLUGIN; + } else { + panelType = type; + } + + await createGroupPanel(props.groupId, { + name, + type: panelType, + }); + props.onCreateSuccess(); + }, + [props.groupId, props.onCreateSuccess] + ); + return ( - - 创建群组面板: {props.groupId} + + ); }); diff --git a/web/src/components/modals/GroupDetail/Panel/index.tsx b/web/src/components/modals/GroupDetail/Panel/index.tsx index 79ae4033..447cf77c 100644 --- a/web/src/components/modals/GroupDetail/Panel/index.tsx +++ b/web/src/components/modals/GroupDetail/Panel/index.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useGroupInfo, GroupPanel as GroupPanelInfo, @@ -11,7 +11,7 @@ import { Button } from 'antd'; import _isEqual from 'lodash/isEqual'; import { GroupPanelTree } from './GroupPanelTree'; import { FullModalCommonTitle } from '@/components/FullModal/CommonTitle'; -import { openModal } from '@/components/Modal'; +import { closeModal, openModal } from '@/components/Modal'; import { ModalCreateGroupPanel } from '../../CreateGroupPanel'; export const GroupPanel: React.FC<{ @@ -21,34 +21,50 @@ export const GroupPanel: React.FC<{ const groupInfo = useGroupInfo(groupId); const groupPanels = groupInfo?.panels ?? []; const [editingGroupPanels, setEditingGroupPanels] = useState(groupPanels); + const isEditingRef = useRef(false); + + useEffect(() => { + // 如果不处于编辑状态, 则一直更新最新的面板 + if (isEditingRef.current === true) { + return; + } + + setEditingGroupPanels(groupPanels); + }, [groupPanels]); const handleChange = useCallback((newGroupPanels: GroupPanelInfo[]) => { + isEditingRef.current = true; setEditingGroupPanels(newGroupPanels); }, []); const [{ loading }, handleSave] = useAsyncRequest(async () => { await modifyGroupField(groupId, 'panels', editingGroupPanels); + isEditingRef.current = false; showToasts(t('保存成功'), 'success'); }, [editingGroupPanels]); - const [{ loading: createLoading }, handleCreatePanel] = - useAsyncRequest(async () => { - // TODO - }, []); + const handleReset = useCallback(() => { + setEditingGroupPanels(groupPanels); + isEditingRef.current = false; + }, [groupPanels]); const handleOpenCreatePanelModal = useCallback(() => { - openModal(); - }, [handleCreatePanel]); + const key = openModal( + { + closeModal(key); + isEditingRef.current = false; + }} + /> + ); + }, []); return (
+ } @@ -62,9 +78,12 @@ export const GroupPanel: React.FC<{ /> {!_isEqual(groupPanels, editingGroupPanels) && ( - +
+ + +
)}
);