From 947d05697afdd0acdeb1c67c8ae54a2e7dd12bf9 Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Tue, 11 Jul 2023 17:37:08 +0800 Subject: [PATCH] feat: add group background image in invite and move entry image from entry to all --- .../design/components/Image/index.tsx | 5 ++- .../shared/i18n/langs/en-US/translation.json | 3 ++ .../shared/i18n/langs/zh-CN/translation.json | 3 ++ client/shared/model/group.ts | 4 +++ client/web/src/App.tsx | 14 ++++++++- .../components/modals/GroupDetail/Config.tsx | 27 ++++++++++++++++ client/web/src/routes/Entry/index.tsx | 18 ----------- client/web/src/routes/Invite/InviteInfo.tsx | 4 +++ client/web/src/routes/Invite/index.tsx | 31 +++++++++++++++++-- server/packages/sdk/src/services/types.ts | 1 + server/services/core/group/group.service.ts | 3 ++ 11 files changed, 90 insertions(+), 23 deletions(-) diff --git a/client/packages/design/components/Image/index.tsx b/client/packages/design/components/Image/index.tsx index 97889a44..d005996a 100644 --- a/client/packages/design/components/Image/index.tsx +++ b/client/packages/design/components/Image/index.tsx @@ -3,10 +3,13 @@ import { Image as AntdImage, ImageProps as AntdImageProps } from 'antd'; export let imageUrlParser = (url: string) => url; +const fallback = + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAABRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMogwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgbSf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5IxEoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqqCZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPBHMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgeHANwDrkl1AuO+pmgAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAwqADAAQAAAABAAAAwwAAAAD9b/HnAAAHlklEQVR4Ae3dP3PTWBSGcbGzM6GCKqlIBRV0dHRJFarQ0eUT8LH4BnRU0NHR0UEFVdIlFRV7TzRksomPY8uykTk/zewQfKw/9znv4yvJynLv4uLiV2dBoDiBf4qP3/ARuCRABEFAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghgg0Aj8i0JO4OzsrPv69Wv+hi2qPHr0qNvf39+iI97soRIh4f3z58/u7du3SXX7Xt7Z2enevHmzfQe+oSN2apSAPj09TSrb+XKI/f379+08+A0cNRE2ANkupk+ACNPvkSPcAAEibACyXUyfABGm3yNHuAECRNgAZLuYPgEirKlHu7u7XdyytGwHAd8jjNyng4OD7vnz51dbPT8/7z58+NB9+/bt6jU/TI+AGWHEnrx48eJ/EsSmHzx40L18+fLyzxF3ZVMjEyDCiEDjMYZZS5wiPXnyZFbJaxMhQIQRGzHvWR7XCyOCXsOmiDAi1HmPMMQjDpbpEiDCiL358eNHurW/5SnWdIBbXiDCiA38/Pnzrce2YyZ4//59F3ePLNMl4PbpiL2J0L979+7yDtHDhw8vtzzvdGnEXdvUigSIsCLAWavHp/+qM0BcXMd/q25n1vF57TYBp0a3mUzilePj4+7k5KSLb6gt6ydAhPUzXnoPR0dHl79WGTNCfBnn1uvSCJdegQhLI1vvCk+fPu2ePXt2tZOYEV6/fn31dz+shwAR1sP1cqvLntbEN9MxA9xcYjsxS1jWR4AIa2Ibzx0tc44fYX/16lV6NDFLXH+YL32jwiACRBiEbf5KcXoTIsQSpzXx4N28Ja4BQoK7rgXiydbHjx/P25TaQAJEGAguWy0+2Q8PD6/Ki4R8EVl+bzBOnZY95fq9rj9zAkTI2SxdidBHqG9+skdw43borCXO/ZcJdraPWdv22uIEiLA4q7nvvCug8WTqzQveOH26fodo7g6uFe/a17W3+nFBAkRYENRdb1vkkz1CH9cPsVy/jrhr27PqMYvENYNlHAIesRiBYwRy0V+8iXP8+/fvX11Mr7L7ECueb/r48eMqm7FuI2BGWDEG8cm+7G3NEOfmdcTQw4h9/55lhm7DekRYKQPZF2ArbXTAyu4kDYB2YxUzwg0gi/41ztHnfQG26HbGel/crVrm7tNY+/1btkOEAZ2M05r4FB7r9GbAIdxaZYrHdOsgJ/wCEQY0J74TmOKnbxxT9n3FgGGWWsVdowHtjt9Nnvf7yQM2aZU/TIAIAxrw6dOnAWtZZcoEnBpNuTuObWMEiLAx1HY0ZQJEmHJ3HNvGCBBhY6jtaMoEiJB0Z29vL6ls58vxPcO8/zfrdo5qvKO+d3Fx8Wu8zf1dW4p/cPzLly/dtv9Ts/EbcvGAHhHyfBIhZ6NSiIBTo0LNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiEC/wGgKKC4YMA4TAAAAABJRU5ErkJggg=='; + export const Image: React.FC = React.memo((props) => { return ( { const { language } = useLanguage(); - const { serverName } = useGlobalConfigStore((state) => ({ + const { serverName, serverEntryImage } = useGlobalConfigStore((state) => ({ serverName: state.serverName, + serverEntryImage: state.serverEntryImage, })); return ( {serverName} + + {serverEntryImage && ( + + )} ); }); diff --git a/client/web/src/components/modals/GroupDetail/Config.tsx b/client/web/src/components/modals/GroupDetail/Config.tsx index bb74004f..9d61d5dc 100644 --- a/client/web/src/components/modals/GroupDetail/Config.tsx +++ b/client/web/src/components/modals/GroupDetail/Config.tsx @@ -3,15 +3,18 @@ import { model, showSuccessToasts, t, + UploadFileResult, useAsyncRequest, useGroupInfo, } from 'tailchat-shared'; +import { Image } from 'tailchat-design'; import { Loading } from '@/components/Loading'; import { FullModalField } from '@/components/FullModal/Field'; import { FullModalCommonTitle } from '@/components/FullModal/CommonTitle'; import { Switch } from 'antd'; import { pluginGroupConfigItems } from '@/plugin/common'; import { ensurePluginNamePrefix } from '@/utils/plugin-helper'; +import { ImageUploader } from '@/components/ImageUploader'; export const GroupConfig: React.FC<{ groupId: string; @@ -51,6 +54,30 @@ export const GroupConfig: React.FC<{ } /> + + + + +
+ {t('建议比例: 16:9 | 建议大小: 1280x720')} +
+ + } + /> + {pluginGroupConfigItems.map((item) => { const name = ensurePluginNamePrefix(item.name); return ( diff --git a/client/web/src/routes/Entry/index.tsx b/client/web/src/routes/Entry/index.tsx index 15207b71..5e4980f9 100644 --- a/client/web/src/routes/Entry/index.tsx +++ b/client/web/src/routes/Entry/index.tsx @@ -8,16 +8,10 @@ import { RegisterView } from './RegisterView'; import { useRecordMeasure } from '@/utils/measure-helper'; import { GuestView } from './GuestView'; import { ForgetPasswordView } from './ForgetPasswordView'; -import { Helmet } from 'react-helmet'; -import { parseUrlStr, useGlobalConfigStore } from 'tailchat-shared'; const EntryRoute = React.memo(() => { useRecordMeasure('appEntryRenderStart'); - const serverEntryImage = useGlobalConfigStore( - (state) => state.serverEntryImage - ); - return (
{
- {serverEntryImage && ( - - - - )} -
); diff --git a/client/web/src/routes/Invite/InviteInfo.tsx b/client/web/src/routes/Invite/InviteInfo.tsx index 9e4d6da1..56ea4271 100644 --- a/client/web/src/routes/Invite/InviteInfo.tsx +++ b/client/web/src/routes/Invite/InviteInfo.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { getCachedGroupInviteInfo, getGroupBasicInfo, + GroupBasicInfo, showErrorToasts, t, useAsync, @@ -15,6 +16,7 @@ import { JoinBtn } from './JoinBtn'; interface Props { inviteCode: string; + onLoadInfo: (groupInfo: GroupBasicInfo) => void; } export const InviteInfo: React.FC = React.memo((props) => { const { inviteCode } = props; @@ -30,6 +32,8 @@ export const InviteInfo: React.FC = React.memo((props) => { throw new Error(t('找不到群组信息')); } + props.onLoadInfo(groupBasicInfo); + return { group: groupBasicInfo, creator: invite.creator, diff --git a/client/web/src/routes/Invite/index.tsx b/client/web/src/routes/Invite/index.tsx index 935ef7a1..c920820a 100644 --- a/client/web/src/routes/Invite/index.tsx +++ b/client/web/src/routes/Invite/index.tsx @@ -1,21 +1,46 @@ -import React from 'react'; +import React, { useMemo, useState } from 'react'; import { useParams } from 'react-router'; import { InviteInfo } from './InviteInfo'; import { PortalHost } from '@/components/Portal'; import { useRecordMeasure } from '@/utils/measure-helper'; +import { parseUrlStr } from 'tailchat-shared'; /** * 邀请界面路由 */ const InviteRoute: React.FC = React.memo(() => { const { inviteCode = '' } = useParams<{ inviteCode: string }>(); + const [groupBackground, setGroupBackground] = useState(''); + useRecordMeasure('appInviteRenderStart'); + const style: React.CSSProperties = useMemo( + () => + groupBackground + ? { + backgroundImage: `url(${parseUrlStr(groupBackground)})`, + } + : { + backgroundImage: 'var(--tc-background-image)', + }, + [groupBackground] + ); + return ( -
+
- + { + if (info.backgroundImage) { + setGroupBackground(info.backgroundImage); + } + }} + />
diff --git a/server/packages/sdk/src/services/types.ts b/server/packages/sdk/src/services/types.ts index 05ac912e..7af7b471 100644 --- a/server/packages/sdk/src/services/types.ts +++ b/server/packages/sdk/src/services/types.ts @@ -55,6 +55,7 @@ export type GroupBaseInfo = Pick< 'name' | 'avatar' | 'owner' | 'description' > & { memberCount: number; + backgroundImage?: string; }; /** diff --git a/server/services/core/group/group.service.ts b/server/services/core/group/group.service.ts index 15491b9a..b2f91d1e 100644 --- a/server/services/core/group/group.service.ts +++ b/server/services/core/group/group.service.ts @@ -350,6 +350,7 @@ class GroupService extends TcService { owner: 1, description: 1, members: 1, + config: 1, }) .exec(); @@ -358,6 +359,7 @@ class GroupService extends TcService { } const groupMemberCount = group.members.length; + const backgroundImage = group.config['groupBackgroundImage']; return { name: group.name, @@ -365,6 +367,7 @@ class GroupService extends TcService { owner: String(group.owner), description: group.description ?? '', memberCount: groupMemberCount, + backgroundImage: backgroundImage, }; }