diff --git a/client/shared/cache/cache.ts b/client/shared/cache/cache.ts index f4d16978..01c62075 100644 --- a/client/shared/cache/cache.ts +++ b/client/shared/cache/cache.ts @@ -11,10 +11,19 @@ import { fetchServiceRegistryPlugins, PluginManifest, } from '../model/plugin'; -import { fetchUserInfo, UserBaseInfo } from '../model/user'; +import { fetchUserInfo, getUserSettings, UserBaseInfo } from '../model/user'; import { parseUrlStr } from '../utils/url-helper'; import { queryClient } from './index'; +export enum CacheKey { + user = 'user', + converse = 'converse', + baseGroupInfo = 'baseGroupInfo', + groupInvite = 'groupInvite', + pluginRegistry = 'pluginRegistry', + userSettings = 'userSettings', +} + /** * 获取缓存的用户信息 */ @@ -23,7 +32,7 @@ export async function getCachedUserInfo( refetch = false ): Promise { const data = await queryClient.fetchQuery( - ['user', userId], + [CacheKey.user, userId], () => fetchUserInfo(userId), { staleTime: refetch ? 0 : 2 * 60 * 60 * 1000, // 缓存2小时 @@ -39,8 +48,9 @@ export async function getCachedUserInfo( export async function getCachedConverseInfo( converseId: string ): Promise { - const data = await queryClient.fetchQuery(['converse', converseId], () => - fetchConverseInfo(converseId) + const data = await queryClient.fetchQuery( + [CacheKey.converse, converseId], + () => fetchConverseInfo(converseId) ); return data; @@ -52,8 +62,9 @@ export async function getCachedConverseInfo( export async function getCachedBaseGroupInfo( groupId: string ): Promise { - const data = await queryClient.fetchQuery(['baseGroupInfo', groupId], () => - getGroupBasicInfo(groupId) + const data = await queryClient.fetchQuery( + [CacheKey.baseGroupInfo, groupId], + () => getGroupBasicInfo(groupId) ); return data; @@ -65,8 +76,9 @@ export async function getCachedBaseGroupInfo( export async function getCachedGroupInviteInfo( inviteCode: string ): Promise { - const data = await queryClient.fetchQuery(['groupInvite', inviteCode], () => - findGroupInviteByCode(inviteCode) + const data = await queryClient.fetchQuery( + [CacheKey.groupInvite, inviteCode], + () => findGroupInviteByCode(inviteCode) ); return data; @@ -77,7 +89,7 @@ export async function getCachedGroupInviteInfo( */ export async function getCachedRegistryPlugins(): Promise { const data = await queryClient.fetchQuery( - ['pluginRegistry'], + [CacheKey.pluginRegistry], () => Promise.all([ fetchRegistryPlugins().catch(() => []), @@ -111,3 +123,18 @@ export async function getCachedRegistryPlugins(): Promise { return data; } + +/** + * 获取用户配置 + */ +export async function getCachedUserSettings() { + const data = await queryClient.fetchQuery( + [CacheKey.userSettings], + () => getUserSettings, + { + staleTime: 1 * 60 * 1000, // 缓存1分钟 + } + ); + + return data; +} diff --git a/client/shared/event/index.ts b/client/shared/event/index.ts index 6702f5a5..257f79dd 100644 --- a/client/shared/event/index.ts +++ b/client/shared/event/index.ts @@ -2,7 +2,7 @@ import { useEffect } from 'react'; import { useUpdateRef } from '../hooks/useUpdateRef'; import type { ChatMessage, SendMessagePayload } from '../model/message'; import { EventEmitter } from 'eventemitter-strict'; -import type { UserBaseInfo } from '../model/user'; +import type { UserBaseInfo, UserSettings } from '../model/user'; /** * 共享事件类型 @@ -51,6 +51,11 @@ export interface SharedEventMap { * 群组面板状态更新 */ groupPanelBadgeUpdate: () => void; + + /** + * 用户设置发生了变更 + */ + userSettingsUpdate: (userSettings: UserSettings) => void; } export type SharedEventType = keyof SharedEventMap; diff --git a/client/shared/hooks/model/useUserSettings.ts b/client/shared/hooks/model/useUserSettings.ts index 904e8ba8..ba134cf5 100644 --- a/client/shared/hooks/model/useUserSettings.ts +++ b/client/shared/hooks/model/useUserSettings.ts @@ -1,4 +1,6 @@ +import { CacheKey } from '../../cache/cache'; import { useQuery, useQueryClient } from '../../cache/useCache'; +import { sharedEvent } from '../../event'; import { getUserSettings, setUserSettings, @@ -12,7 +14,7 @@ import { useAsyncRequest } from '../useAsyncRequest'; export function useUserSettings() { const client = useQueryClient(); const { data: settings, isLoading } = useQuery( - ['useUserSettings'], + [CacheKey], () => getUserSettings(), { staleTime: 1 * 60 * 1000, // 缓存1分钟 @@ -23,7 +25,8 @@ export function useUserSettings() { async (settings: UserSettings) => { const newSettings = await setUserSettings(settings); - client.setQueryData(['useUserSettings'], () => newSettings); + client.setQueryData([CacheKey], () => newSettings); + sharedEvent.emit('userSettingsUpdate', newSettings); }, [client] ); diff --git a/client/shared/index.tsx b/client/shared/index.tsx index 104ef987..e3f7b586 100644 --- a/client/shared/index.tsx +++ b/client/shared/index.tsx @@ -12,6 +12,7 @@ export { getCachedBaseGroupInfo, getCachedGroupInviteInfo, getCachedRegistryPlugins, + getCachedUserSettings, } from './cache/cache'; export { useCachedUserInfo, useCachedOnlineStatus } from './cache/useCache'; diff --git a/client/shared/model/user.ts b/client/shared/model/user.ts index b8480716..a6eb4a9a 100644 --- a/client/shared/model/user.ts +++ b/client/shared/model/user.ts @@ -27,6 +27,11 @@ export interface UserSettings { * 消息列表虚拟化 */ messageListVirtualization?: boolean; + + /** + * 其他的设置项 + */ + [key: string]: any; } export function pickUserBaseInfo(userInfo: UserLoginInfo) { diff --git a/client/web/src/components/FullModal/Factory.tsx b/client/web/src/components/FullModal/Factory.tsx new file mode 100644 index 00000000..5fdb893c --- /dev/null +++ b/client/web/src/components/FullModal/Factory.tsx @@ -0,0 +1,105 @@ +import { Select, Switch } from 'antd'; +import React from 'react'; +import { + DefaultFullModalInputEditorRender, + DefaultFullModalTextAreaEditorRender, + FullModalField, +} from './Field'; + +export type FullModalFactoryConfig = { + name: string; + label: string; + desc?: string; +} & ( + | { + type: 'text'; + } + | { + type: 'textarea'; + } + | { + type: 'boolean'; + } + | { + type: 'select'; + options: { label: string; value: string }[]; + } +); + +interface FullModalFactoryProps { + value: T; + onChange: (val: T) => void; + config: FullModalFactoryConfig; +} + +/** + * 输入配置输出组件 + */ +export const FullModalFactory: React.FC = React.memo( + (props) => { + const { value, onChange, config } = props; + if (config.type === 'text') { + return ( + onChange(val)} + /> + ); + } + if (config.type === 'textarea') { + return ( + onChange(val)} + /> + ); + } + + if (config.type === 'boolean') { + return ( + onChange(checked)} + /> + } + /> + ); + } + + if (config.type === 'select') { + return ( + onChange(val)} + > + {config.options.map((opt) => ( + + {opt.label} + + ))} + + } + /> + ); + } + + return null; + } +); +FullModalFactory.displayName = 'FullModalFactory'; diff --git a/client/web/src/components/modals/SettingsView/System.tsx b/client/web/src/components/modals/SettingsView/System.tsx index 78156056..1977b995 100644 --- a/client/web/src/components/modals/SettingsView/System.tsx +++ b/client/web/src/components/modals/SettingsView/System.tsx @@ -1,23 +1,20 @@ +import { FullModalFactory } from '@/components/FullModal/Factory'; import { FullModalField } from '@/components/FullModal/Field'; import { LanguageSelect } from '@/components/LanguageSelect'; -import { pluginColorScheme } from '@/plugin/common'; +import { pluginColorScheme, pluginSettings } from '@/plugin/common'; import { Select, Switch } from 'antd'; import React from 'react'; import { - AlphaContainer, t, useAlphaMode, useColorScheme, - useSingleUserSetting, + useUserSettings, } from 'tailchat-shared'; +import _get from 'lodash/get'; export const SettingsSystem: React.FC = React.memo(() => { const { colorScheme, setColorScheme } = useColorScheme(); - const { - value: messageListVirtualization, - setValue: setMessageListVirtualization, - loading, - } = useSingleUserSetting('messageListVirtualization', false); + const { settings, setSettings, loading } = useUserSettings(); const { isAlphaMode, setAlphaMode } = useAlphaMode(); return ( @@ -45,6 +42,23 @@ export const SettingsSystem: React.FC = React.memo(() => { } /> + {pluginSettings + .filter((item) => item.position === 'system') + .map((item) => { + return ( + { + setSettings({ + [item.name]: val, + }); + }} + config={item} + /> + ); + })} + { )} content={ setAlphaMode(checked)} /> @@ -65,8 +78,13 @@ export const SettingsSystem: React.FC = React.memo(() => { content={ setMessageListVirtualization(checked)} + loading={loading} + checked={settings.messageListVirtualization ?? false} + onChange={(checked) => + setSettings({ + messageListVirtualization: checked, + }) + } /> } /> diff --git a/client/web/src/plugin/common/index.ts b/client/web/src/plugin/common/index.ts index 06ce2a89..0a9c8c67 100644 --- a/client/web/src/plugin/common/index.ts +++ b/client/web/src/plugin/common/index.ts @@ -38,6 +38,7 @@ export { getCachedUserInfo, getCachedConverseInfo, getCachedBaseGroupInfo, + getCachedUserSettings, localTrans, getLanguage, sharedEvent, diff --git a/client/web/src/plugin/common/reg.ts b/client/web/src/plugin/common/reg.ts index 70e1907e..9d568f46 100644 --- a/client/web/src/plugin/common/reg.ts +++ b/client/web/src/plugin/common/reg.ts @@ -8,6 +8,7 @@ import { PermissionItemType, } from 'tailchat-shared'; import type { MetaFormFieldMeta } from 'tailchat-design'; +import type { FullModalFactoryConfig } from '@/components/FullModal/Factory'; /** * 注册自定义面板 @@ -254,3 +255,13 @@ interface PluginUserExtraInfo { */ export const [pluginUserExtraInfo, regUserExtraInfo] = buildRegList(); + +type PluginSettings = FullModalFactoryConfig & { + position: 'system'; // 后面可能还会有个人设置/群组设置 +}; + +/** + * 注册插件设置项 + */ +export const [pluginSettings, regPluginSettings] = + buildRegList();