diff --git a/packages/design/components/Avatar/index.tsx b/packages/design/components/Avatar/index.tsx index 4e9216bc..ac0364a9 100644 --- a/packages/design/components/Avatar/index.tsx +++ b/packages/design/components/Avatar/index.tsx @@ -9,6 +9,8 @@ import type { AvatarProps as AntdAvatarProps } from 'antd/lib/avatar'; import { getTextColorHex } from './utils'; import { isValidStr } from '../utils'; +export { getTextColorHex }; + export interface AvatarProps extends AntdAvatarProps { name?: string; isOnline?: boolean; diff --git a/packages/design/components/index.ts b/packages/design/components/index.ts index c948495d..795ce292 100644 --- a/packages/design/components/index.ts +++ b/packages/design/components/index.ts @@ -1,5 +1,5 @@ export { AutoFolder } from './AutoFolder'; -export { Avatar } from './Avatar'; +export { Avatar, getTextColorHex } from './Avatar'; export { AvatarWithPreview } from './AvatarWithPreview'; export { CombinedAvatar } from './Avatar/combined'; export { DelayTip } from './DelayTip'; diff --git a/web/src/components/UserProfileContainer.tsx b/web/src/components/UserProfileContainer.tsx new file mode 100644 index 00000000..20c2dd5b --- /dev/null +++ b/web/src/components/UserProfileContainer.tsx @@ -0,0 +1,42 @@ +import { fetchImagePrimaryColor } from '@/utils/image-helper'; +import React from 'react'; +import { AvatarWithPreview, getTextColorHex } from 'tailchat-design'; +import { useAsync, UserBaseInfo } from 'tailchat-shared'; + +/** + * 用户信息容器 + */ +export const UserProfileContainer: React.FC<{ userInfo: UserBaseInfo }> = + React.memo((props) => { + const { userInfo } = props; + const { value: bannerColor } = useAsync(async () => { + if (!userInfo.avatar) { + return getTextColorHex(userInfo.nickname); + } + + const rgba = await fetchImagePrimaryColor(userInfo.avatar); + return `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})`; + }, [userInfo.avatar]); + return ( +
+
+ +
+ +
+ +
{props.children}
+
+ ); + }); +UserProfileContainer.displayName = 'UserProfileContainer'; diff --git a/web/src/components/popover/GroupUserPopover.tsx b/web/src/components/popover/GroupUserPopover.tsx index f1c298bc..880816c9 100644 --- a/web/src/components/popover/GroupUserPopover.tsx +++ b/web/src/components/popover/GroupUserPopover.tsx @@ -1,28 +1,34 @@ +import { fetchImagePrimaryColor } from '@/utils/image-helper'; import { Tag } from 'antd'; -import React from 'react'; -import { AvatarWithPreview } from 'tailchat-design'; +import React, { useEffect } from 'react'; import { t, UserBaseInfo } from 'tailchat-shared'; +import { UserProfileContainer } from '../UserProfileContainer'; export const GroupUserPopover: React.FC<{ userInfo: UserBaseInfo; }> = React.memo((props) => { const { userInfo } = props; + useEffect(() => { + if (userInfo.avatar) { + fetchImagePrimaryColor(userInfo.avatar).then((rgba) => { + console.log('fetchImagePrimaryColor', rgba); + }); + } + }, [userInfo.avatar]); + return ( -
- -
- {userInfo.nickname} - #{userInfo.discriminator} -
+
+ +
+ {userInfo.nickname} + #{userInfo.discriminator} +
-
- {userInfo.temporary && {t('游客')}} -
+
+ {userInfo.temporary && {t('游客')}} +
+
); }); diff --git a/web/src/styles/antd/overwrite.less b/web/src/styles/antd/overwrite.less index 5f7628ea..6e7abc8a 100644 --- a/web/src/styles/antd/overwrite.less +++ b/web/src/styles/antd/overwrite.less @@ -57,3 +57,9 @@ vertical-align: text-top; } } + +.ant-popover { + .ant-popover-inner-content { + background-color: inherit; + } +} diff --git a/web/src/utils/image-helper.ts b/web/src/utils/image-helper.ts new file mode 100644 index 00000000..e29b9f3e --- /dev/null +++ b/web/src/utils/image-helper.ts @@ -0,0 +1,57 @@ +/** + * 加载图片 + */ +async function loadImage(url: string): Promise { + const el = document.createElement('img'); + return new Promise((resolve, reject) => { + el.onload = () => resolve(el); + el.onerror = reject; + el.src = url; + el.crossOrigin = 'Anonymous'; + }); +} + +/** + * 获取图片的主要颜色 + */ +export async function fetchImagePrimaryColor(imageUrl: string) { + const img = await loadImage(imageUrl); + const canvas = document.createElement('canvas'); + + canvas.width = img.width; + canvas.height = img.height; + const ctx = canvas.getContext('2d'); + if (!ctx) { + return { r: 0, g: 0, b: 0, a: 0 }; + } + + ctx.drawImage(img, 0, 0, canvas.width, canvas.height); + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + + let r = 0; + let g = 0; + let b = 0; + let a = 0; + + for (let row = 0; row < imageData.height; row++) { + for (let col = 0; col < imageData.width; col++) { + r += imageData.data[(imageData.width * row + col) * 4]; + g += imageData.data[(imageData.width * row + col) * 4 + 1]; + b += imageData.data[(imageData.width * row + col) * 4 + 2]; + a += imageData.data[(imageData.width * row + col) * 4 + 3]; + } + } + + const sum = imageData.width * imageData.height; + r = Math.round(r / sum); + g = Math.round(g / sum); + b = Math.round(b / sum); + a = Math.round(a / sum); + + return { + r, + g, + b, + a, + }; +} diff --git a/web/tailwind.config.js b/web/tailwind.config.js index 6cafe220..7b93f54c 100644 --- a/web/tailwind.config.js +++ b/web/tailwind.config.js @@ -55,6 +55,9 @@ module.exports = { }, extend: { colors: { + inherit: { + DEFAULT: 'inherit', + }, navbar: { light: colors.coolGray[300], dark: colors.coolGray[900],