@arcblock/ux 2.13.17 → 2.13.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/Config/config-provider.d.ts +5 -1
- package/lib/Config/config-provider.js +19 -9
- package/lib/Config/theme-mode-toggle.js +11 -5
- package/lib/Theme/theme.d.ts +5 -1
- package/lib/Theme/theme.js +2 -2
- package/lib/UserCard/Content/minimal.js +1 -1
- package/lib/UserCard/components.js +16 -6
- package/lib/UserCard/index.js +7 -2
- package/lib/UserCard/types.d.ts +1 -1
- package/lib/UserCard/utils.d.ts +1 -0
- package/lib/UserCard/utils.js +87 -0
- package/lib/type.d.ts +2 -2
- package/package.json +6 -6
- package/src/Config/config-provider.tsx +20 -10
- package/src/Config/theme-mode-toggle.tsx +11 -6
- package/src/Theme/theme.ts +2 -3
- package/src/UserCard/Content/minimal.tsx +1 -1
- package/src/UserCard/components.tsx +14 -7
- package/src/UserCard/index.tsx +2 -1
- package/src/UserCard/types.ts +1 -1
- package/src/UserCard/utils.ts +94 -0
- package/src/type.d.ts +2 -2
@@ -3,14 +3,16 @@ import { type PaletteMode } from '@mui/material';
|
|
3
3
|
import { LocaleProviderProps } from '../Locale/context';
|
4
4
|
import { ThemeProviderProps } from '../Theme/theme-provider';
|
5
5
|
import { type UserThemeOptions, type ThemeConfig } from '../Theme';
|
6
|
+
type Prefer = 'light' | 'dark' | 'system';
|
6
7
|
export interface ConfigContextType {
|
7
8
|
mode: PaletteMode;
|
9
|
+
prefer?: Prefer;
|
8
10
|
themeOptions: UserThemeOptions;
|
9
11
|
toggleMode: () => void;
|
10
12
|
}
|
11
13
|
export interface ConfigProviderProps extends Omit<LocaleProviderProps, 'translations'>, Omit<ThemeProviderProps, 'theme'> {
|
12
14
|
children: ReactNode;
|
13
|
-
prefer?:
|
15
|
+
prefer?: Prefer;
|
14
16
|
theme?: UserThemeOptions | ((config: ThemeConfig) => UserThemeOptions);
|
15
17
|
disableBlockletTheme?: boolean;
|
16
18
|
translations?: Record<string, any>;
|
@@ -24,6 +26,7 @@ export declare namespace ConfigProvider {
|
|
24
26
|
}
|
25
27
|
export declare function useConfig(): {
|
26
28
|
mode: PaletteMode;
|
29
|
+
prefer?: Prefer;
|
27
30
|
themeOptions: UserThemeOptions;
|
28
31
|
toggleMode: () => void;
|
29
32
|
locale: import("../type").Locale;
|
@@ -35,3 +38,4 @@ export declare function useConfig(): {
|
|
35
38
|
}[];
|
36
39
|
theme: import("@mui/material").Theme;
|
37
40
|
};
|
41
|
+
export {};
|
@@ -6,6 +6,20 @@ import { ThemeProvider as EmotionThemeProvider } from '@emotion/react';
|
|
6
6
|
import { LocaleProvider, useLocaleContext } from '../Locale/context';
|
7
7
|
import ThemeProvider from '../Theme/theme-provider';
|
8
8
|
import { createTheme, getDefaultThemePrefer, lazyThemeConfig, useTheme } from '../Theme';
|
9
|
+
const resolveMode = prefer => {
|
10
|
+
if (prefer) {
|
11
|
+
if (prefer === 'system') {
|
12
|
+
// 取系统默认
|
13
|
+
return getDefaultThemePrefer({
|
14
|
+
theme: {
|
15
|
+
prefer: 'system'
|
16
|
+
}
|
17
|
+
});
|
18
|
+
}
|
19
|
+
return prefer;
|
20
|
+
}
|
21
|
+
return getDefaultThemePrefer();
|
22
|
+
};
|
9
23
|
const ConfigContext = /*#__PURE__*/createContext({});
|
10
24
|
/**
|
11
25
|
* 集中化配置
|
@@ -24,12 +38,7 @@ export function ConfigProvider({
|
|
24
38
|
languages,
|
25
39
|
onLoadingTranslation
|
26
40
|
}) {
|
27
|
-
const [mode, setMode] = useState(() =>
|
28
|
-
if (prefer) {
|
29
|
-
return prefer;
|
30
|
-
}
|
31
|
-
return getDefaultThemePrefer();
|
32
|
-
});
|
41
|
+
const [mode, setMode] = useState(() => resolveMode(prefer));
|
33
42
|
const _themeOptions = useMemo(() => {
|
34
43
|
let result = {};
|
35
44
|
const getThemeConfig = lazyThemeConfig(mode);
|
@@ -60,13 +69,14 @@ export function ConfigProvider({
|
|
60
69
|
const config = useMemo(() => ({
|
61
70
|
mode,
|
62
71
|
themeOptions: _themeOptions,
|
63
|
-
toggleMode
|
64
|
-
|
72
|
+
toggleMode,
|
73
|
+
prefer
|
74
|
+
}), [mode, prefer, _themeOptions, toggleMode]);
|
65
75
|
|
66
76
|
// change prefer manually
|
67
77
|
useEffect(() => {
|
68
78
|
if (prefer) {
|
69
|
-
setMode(prefer);
|
79
|
+
setMode(resolveMode(prefer));
|
70
80
|
}
|
71
81
|
}, [prefer, setMode]);
|
72
82
|
return /*#__PURE__*/_jsx(ConfigContext.Provider, {
|
@@ -6,7 +6,8 @@ import { useConfig } from './config-provider';
|
|
6
6
|
export default function ThemeModeToggle() {
|
7
7
|
const {
|
8
8
|
mode,
|
9
|
-
toggleMode
|
9
|
+
toggleMode,
|
10
|
+
prefer
|
10
11
|
} = useConfig();
|
11
12
|
if (!toggleMode) {
|
12
13
|
if (process.env.NODE_ENV !== 'production') {
|
@@ -14,8 +15,13 @@ export default function ThemeModeToggle() {
|
|
14
15
|
}
|
15
16
|
return null;
|
16
17
|
}
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
|
19
|
+
// 跟随系统才显示切换
|
20
|
+
if (prefer === 'system' || window.blocklet?.theme?.prefer === 'system') {
|
21
|
+
return /*#__PURE__*/_jsx(IconButton, {
|
22
|
+
onClick: toggleMode,
|
23
|
+
children: mode === 'light' ? /*#__PURE__*/_jsx(Brightness2OutlinedIcon, {}) : /*#__PURE__*/_jsx(LightModeOutlinedIcon, {})
|
24
|
+
});
|
25
|
+
}
|
26
|
+
return null;
|
21
27
|
}
|
package/lib/Theme/theme.d.ts
CHANGED
@@ -11,7 +11,11 @@ export declare function collectFontFamilies(obj?: {
|
|
11
11
|
fontFamily?: string;
|
12
12
|
}, fontSet?: Set<string>): Set<string>;
|
13
13
|
export declare function loadFonts(fonts: string[]): Promise<boolean>;
|
14
|
-
export declare function getDefaultThemePrefer(
|
14
|
+
export declare function getDefaultThemePrefer(meta?: {
|
15
|
+
theme: {
|
16
|
+
prefer: 'light' | 'dark' | 'system';
|
17
|
+
};
|
18
|
+
}): PaletteMode;
|
15
19
|
export declare function createDefaultThemeOptions(mode?: PaletteMode): ThemeOptions;
|
16
20
|
export interface UserThemeOptions extends ThemeOptions {
|
17
21
|
disableBlockletTheme?: boolean;
|
package/lib/Theme/theme.js
CHANGED
@@ -63,8 +63,8 @@ export function loadFonts(fonts) {
|
|
63
63
|
}
|
64
64
|
|
65
65
|
// 获取默认主题偏好
|
66
|
-
export function getDefaultThemePrefer() {
|
67
|
-
const prefer = window.blocklet
|
66
|
+
export function getDefaultThemePrefer(meta) {
|
67
|
+
const prefer = Object.assign({}, window.blocklet, meta).theme?.prefer;
|
68
68
|
if (prefer === 'system') {
|
69
69
|
// 本地缓存
|
70
70
|
const localPrefer = localStorage.getItem(BLOCKLET_THEME_PREFER_KEY);
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
2
2
|
import React from 'react';
|
3
|
-
import
|
3
|
+
import Box from '@mui/material/Box';
|
4
|
+
import MaterialAvatar from '@mui/material/Avatar';
|
4
5
|
import Avatar from '../Avatar';
|
5
6
|
import { createNameOnlyAvatar } from './utils';
|
6
7
|
|
@@ -12,16 +13,25 @@ export const renderAvatar = (user, avatarSize = 48, avatarProps = undefined, onA
|
|
12
13
|
// 如果用户没有头像,则显示名称首字母头像
|
13
14
|
if (!user.avatar) {
|
14
15
|
const avatarContent = createNameOnlyAvatar(user);
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
// 从 avatarProps 中提取 MUI 的 Avatar 支持的基本 props
|
17
|
+
// className, style 等基本 React 属性
|
18
|
+
const {
|
19
|
+
className,
|
20
|
+
style,
|
21
|
+
alt
|
22
|
+
} = avatarProps || {};
|
23
|
+
return /*#__PURE__*/_jsx(MaterialAvatar, {
|
19
24
|
onClick: onClick,
|
25
|
+
className: className,
|
26
|
+
style: style,
|
27
|
+
alt: alt,
|
20
28
|
sx: {
|
29
|
+
width: avatarSize,
|
30
|
+
height: avatarSize,
|
21
31
|
fontSize: avatarSize * 0.4,
|
22
32
|
cursor: shouldShowHoverCard || onAvatarClick ? 'pointer' : 'default'
|
23
33
|
},
|
24
|
-
|
34
|
+
variant: "circular",
|
25
35
|
children: avatarContent
|
26
36
|
});
|
27
37
|
}
|
package/lib/UserCard/index.js
CHANGED
@@ -46,7 +46,11 @@ function UserCard(props) {
|
|
46
46
|
} else if (props.did && isUserDid(props.did) && !props.user) {
|
47
47
|
getUserByDid(props.did).then(_user => {
|
48
48
|
if (isSubscribed) {
|
49
|
-
setUser(_user
|
49
|
+
setUser(_user || {
|
50
|
+
fullName: 'Anonymous',
|
51
|
+
did: props.did,
|
52
|
+
avatar: ''
|
53
|
+
});
|
50
54
|
}
|
51
55
|
});
|
52
56
|
}
|
@@ -76,7 +80,8 @@ function UserCard(props) {
|
|
76
80
|
avatarProps: props.popupAvatarProps,
|
77
81
|
shortenLabelProps: props.popupShortenLabelProps,
|
78
82
|
renderFields: props.popupRenderFields,
|
79
|
-
renderName: props.popupRenderName
|
83
|
+
renderName: props.popupRenderName,
|
84
|
+
infoType: props.popupInfoType || props.infoType
|
80
85
|
})
|
81
86
|
});
|
82
87
|
};
|
package/lib/UserCard/types.d.ts
CHANGED
@@ -7,7 +7,6 @@ type UserPublicInfo = {
|
|
7
7
|
avatar: string;
|
8
8
|
did: string;
|
9
9
|
fullName: string;
|
10
|
-
sourceAppPid: string | null;
|
11
10
|
};
|
12
11
|
export type UserMetadataLink = {
|
13
12
|
url: string;
|
@@ -95,6 +94,7 @@ export interface UserCardProps {
|
|
95
94
|
did?: string;
|
96
95
|
cardType?: CardType;
|
97
96
|
infoType?: InfoType;
|
97
|
+
popupInfoType?: InfoType;
|
98
98
|
avatarSize?: number;
|
99
99
|
showHoverCard?: boolean;
|
100
100
|
showDid?: boolean;
|
package/lib/UserCard/utils.d.ts
CHANGED
@@ -2,3 +2,4 @@ import { User } from './types';
|
|
2
2
|
export declare function createNameOnlyAvatar(user: User): string | null;
|
3
3
|
export declare function isUserDid(did: string): boolean;
|
4
4
|
export declare function getUserByDid(did: string): Promise<User | null>;
|
5
|
+
export declare function clearUserCache(did?: string): void;
|
package/lib/UserCard/utils.js
CHANGED
@@ -9,6 +9,53 @@ try {
|
|
9
9
|
client = null;
|
10
10
|
}
|
11
11
|
|
12
|
+
// 用户信息缓存键前缀
|
13
|
+
const USER_CACHE_PREFIX = 'ux_user_';
|
14
|
+
// 用户缓存的有效期(毫秒),默认60分钟
|
15
|
+
const USER_CACHE_EXPIRATION = 60 * 60 * 1000;
|
16
|
+
|
17
|
+
// 从sessionStorage获取用户信息
|
18
|
+
const getUserFromStorage = did => {
|
19
|
+
if (typeof sessionStorage === 'undefined') return null;
|
20
|
+
try {
|
21
|
+
const cacheKey = `${USER_CACHE_PREFIX}${did}`;
|
22
|
+
const cachedData = sessionStorage.getItem(cacheKey);
|
23
|
+
if (!cachedData) return null;
|
24
|
+
const parsedData = JSON.parse(cachedData);
|
25
|
+
const timestamp = parsedData.timestamp || 0;
|
26
|
+
const now = Date.now();
|
27
|
+
|
28
|
+
// 检查缓存是否过期
|
29
|
+
if (now - timestamp > USER_CACHE_EXPIRATION) {
|
30
|
+
// 缓存已过期,删除并返回null
|
31
|
+
sessionStorage.removeItem(cacheKey);
|
32
|
+
return null;
|
33
|
+
}
|
34
|
+
|
35
|
+
// 返回用户数据
|
36
|
+
return parsedData.user;
|
37
|
+
} catch (error) {
|
38
|
+
console.error(`Failed to load user cache for did ${did}:`, error);
|
39
|
+
return null;
|
40
|
+
}
|
41
|
+
};
|
42
|
+
|
43
|
+
// 将用户信息保存到sessionStorage
|
44
|
+
const saveUserToStorage = (did, user) => {
|
45
|
+
if (typeof sessionStorage === 'undefined') return;
|
46
|
+
try {
|
47
|
+
const cacheKey = `${USER_CACHE_PREFIX}${did}`;
|
48
|
+
// 创建包含用户数据和时间戳的缓存对象
|
49
|
+
const cacheData = {
|
50
|
+
user,
|
51
|
+
timestamp: Date.now()
|
52
|
+
};
|
53
|
+
sessionStorage.setItem(cacheKey, JSON.stringify(cacheData));
|
54
|
+
} catch (error) {
|
55
|
+
console.error(`Failed to save user cache for did ${did}:`, error);
|
56
|
+
}
|
57
|
+
};
|
58
|
+
|
12
59
|
// 创建仅显示名称首字母的头像
|
13
60
|
// eslint-disable-next-line import/prefer-default-export
|
14
61
|
export function createNameOnlyAvatar(user) {
|
@@ -39,14 +86,54 @@ export function isUserDid(did) {
|
|
39
86
|
}
|
40
87
|
}
|
41
88
|
export async function getUserByDid(did) {
|
89
|
+
if (!did) return null;
|
90
|
+
|
91
|
+
// 先检查sessionStorage中是否有缓存
|
92
|
+
const storedUser = getUserFromStorage(did);
|
93
|
+
if (storedUser) {
|
94
|
+
return storedUser;
|
95
|
+
}
|
42
96
|
if (!client) return null;
|
43
97
|
try {
|
44
98
|
const user = await client.user.getUserPublicInfo({
|
45
99
|
did
|
46
100
|
});
|
101
|
+
// 将获取到的用户信息存入缓存
|
102
|
+
if (user) {
|
103
|
+
const userData = user;
|
104
|
+
saveUserToStorage(did, userData);
|
105
|
+
}
|
47
106
|
return user;
|
48
107
|
} catch (error) {
|
49
108
|
console.error('Failed to get user by did:', error);
|
50
109
|
return null;
|
51
110
|
}
|
111
|
+
}
|
112
|
+
|
113
|
+
// 清除缓存中特定用户的信息
|
114
|
+
export function clearUserCache(did) {
|
115
|
+
if (typeof sessionStorage === 'undefined') return;
|
116
|
+
if (did) {
|
117
|
+
// 清除指定用户缓存
|
118
|
+
try {
|
119
|
+
sessionStorage.removeItem(`${USER_CACHE_PREFIX}${did}`);
|
120
|
+
} catch (error) {
|
121
|
+
console.error(`Failed to remove cache for did ${did}:`, error);
|
122
|
+
}
|
123
|
+
} else {
|
124
|
+
// 清除所有用户缓存
|
125
|
+
try {
|
126
|
+
// 只清除以USER_CACHE_PREFIX开头的项
|
127
|
+
const keysToRemove = [];
|
128
|
+
for (let i = 0; i < sessionStorage.length; i++) {
|
129
|
+
const key = sessionStorage.key(i);
|
130
|
+
if (key && key.startsWith(USER_CACHE_PREFIX)) {
|
131
|
+
keysToRemove.push(key);
|
132
|
+
}
|
133
|
+
}
|
134
|
+
keysToRemove.forEach(key => sessionStorage.removeItem(key));
|
135
|
+
} catch (error) {
|
136
|
+
console.error('Failed to clear all user caches:', error);
|
137
|
+
}
|
138
|
+
}
|
52
139
|
}
|
package/lib/type.d.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import type { Theme
|
1
|
+
import type { Theme } from '@mui/material';
|
2
2
|
import type { LiteralUnion } from 'type-fest';
|
3
3
|
|
4
4
|
export type $TSFixMe = any;
|
@@ -26,7 +26,7 @@ export type Blocklet = {
|
|
26
26
|
mode: string;
|
27
27
|
tenantMode: 'single' | 'multiple';
|
28
28
|
theme: {
|
29
|
-
prefer?:
|
29
|
+
prefer?: 'light' | 'dark' | 'system';
|
30
30
|
light: Theme;
|
31
31
|
dark: Theme;
|
32
32
|
};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@arcblock/ux",
|
3
|
-
"version": "2.13.
|
3
|
+
"version": "2.13.19",
|
4
4
|
"description": "Common used react components for arcblock products",
|
5
5
|
"keywords": [
|
6
6
|
"react",
|
@@ -71,14 +71,14 @@
|
|
71
71
|
"react": ">=18.2.0",
|
72
72
|
"react-router-dom": ">=6.22.3"
|
73
73
|
},
|
74
|
-
"gitHead": "
|
74
|
+
"gitHead": "64c12fb7646d2323f1be0bef7a3901261687b165",
|
75
75
|
"dependencies": {
|
76
76
|
"@arcblock/did-motif": "^1.1.13",
|
77
|
-
"@arcblock/icons": "^2.13.
|
78
|
-
"@arcblock/nft-display": "^2.13.
|
79
|
-
"@arcblock/react-hooks": "^2.13.
|
77
|
+
"@arcblock/icons": "^2.13.19",
|
78
|
+
"@arcblock/nft-display": "^2.13.19",
|
79
|
+
"@arcblock/react-hooks": "^2.13.19",
|
80
80
|
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
81
|
-
"@blocklet/theme": "^2.13.
|
81
|
+
"@blocklet/theme": "^2.13.19",
|
82
82
|
"@fontsource/roboto": "~5.1.1",
|
83
83
|
"@fontsource/ubuntu-mono": "^5.0.18",
|
84
84
|
"@iconify-icons/logos": "^1.2.36",
|
@@ -14,19 +14,34 @@ import {
|
|
14
14
|
type ThemeConfig,
|
15
15
|
} from '../Theme';
|
16
16
|
|
17
|
+
type Prefer = 'light' | 'dark' | 'system';
|
18
|
+
|
17
19
|
export interface ConfigContextType {
|
18
20
|
mode: PaletteMode;
|
21
|
+
prefer?: Prefer;
|
19
22
|
themeOptions: UserThemeOptions;
|
20
23
|
toggleMode: () => void;
|
21
24
|
}
|
22
25
|
|
26
|
+
const resolveMode = (prefer?: Prefer): PaletteMode => {
|
27
|
+
if (prefer) {
|
28
|
+
if (prefer === 'system') {
|
29
|
+
// 取系统默认
|
30
|
+
return getDefaultThemePrefer({ theme: { prefer: 'system' } });
|
31
|
+
}
|
32
|
+
return prefer;
|
33
|
+
}
|
34
|
+
|
35
|
+
return getDefaultThemePrefer();
|
36
|
+
};
|
37
|
+
|
23
38
|
const ConfigContext = createContext<ConfigContextType>({} as ConfigContextType);
|
24
39
|
|
25
40
|
export interface ConfigProviderProps
|
26
41
|
extends Omit<LocaleProviderProps, 'translations'>,
|
27
42
|
Omit<ThemeProviderProps, 'theme'> {
|
28
43
|
children: ReactNode;
|
29
|
-
prefer?:
|
44
|
+
prefer?: Prefer;
|
30
45
|
theme?: UserThemeOptions | ((config: ThemeConfig) => UserThemeOptions);
|
31
46
|
disableBlockletTheme?: boolean;
|
32
47
|
translations?: Record<string, any>;
|
@@ -49,13 +64,7 @@ export function ConfigProvider({
|
|
49
64
|
languages,
|
50
65
|
onLoadingTranslation,
|
51
66
|
}: ConfigProviderProps) {
|
52
|
-
const [mode, setMode] = useState<PaletteMode>(() =>
|
53
|
-
if (prefer) {
|
54
|
-
return prefer;
|
55
|
-
}
|
56
|
-
|
57
|
-
return getDefaultThemePrefer();
|
58
|
-
});
|
67
|
+
const [mode, setMode] = useState<PaletteMode>(() => resolveMode(prefer));
|
59
68
|
|
60
69
|
const _themeOptions = useMemo(() => {
|
61
70
|
let result: ThemeOptions = {};
|
@@ -90,14 +99,15 @@ export function ConfigProvider({
|
|
90
99
|
mode,
|
91
100
|
themeOptions: _themeOptions,
|
92
101
|
toggleMode,
|
102
|
+
prefer,
|
93
103
|
}),
|
94
|
-
[mode, _themeOptions, toggleMode]
|
104
|
+
[mode, prefer, _themeOptions, toggleMode]
|
95
105
|
);
|
96
106
|
|
97
107
|
// change prefer manually
|
98
108
|
useEffect(() => {
|
99
109
|
if (prefer) {
|
100
|
-
setMode(prefer);
|
110
|
+
setMode(resolveMode(prefer));
|
101
111
|
}
|
102
112
|
}, [prefer, setMode]);
|
103
113
|
|
@@ -4,7 +4,7 @@ import Brightness2OutlinedIcon from '@mui/icons-material/Brightness2Outlined';
|
|
4
4
|
import { useConfig } from './config-provider';
|
5
5
|
|
6
6
|
export default function ThemeModeToggle() {
|
7
|
-
const { mode, toggleMode } = useConfig();
|
7
|
+
const { mode, toggleMode, prefer } = useConfig();
|
8
8
|
|
9
9
|
if (!toggleMode) {
|
10
10
|
if (process.env.NODE_ENV !== 'production') {
|
@@ -14,9 +14,14 @@ export default function ThemeModeToggle() {
|
|
14
14
|
return null;
|
15
15
|
}
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
// 跟随系统才显示切换
|
18
|
+
if (prefer === 'system' || window.blocklet?.theme?.prefer === 'system') {
|
19
|
+
return (
|
20
|
+
<IconButton onClick={toggleMode}>
|
21
|
+
{mode === 'light' ? <Brightness2OutlinedIcon /> : <LightModeOutlinedIcon />}
|
22
|
+
</IconButton>
|
23
|
+
);
|
24
|
+
}
|
25
|
+
|
26
|
+
return null;
|
22
27
|
}
|
package/src/Theme/theme.ts
CHANGED
@@ -6,7 +6,6 @@ import { deepmerge } from '@mui/utils';
|
|
6
6
|
import pick from 'lodash/pick';
|
7
7
|
import webfontloader from 'webfontloader';
|
8
8
|
import { BLOCKLET_THEME_LIGHT, BLOCKLET_THEME_DARK, DEFAULT_FONTS, BLOCKLET_THEME_PREFER_KEY } from '@blocklet/theme';
|
9
|
-
|
10
9
|
import { cleanedObj, deepmergeAll } from '../Util';
|
11
10
|
|
12
11
|
// 默认只加载最基本的 roboto latin 字体
|
@@ -73,8 +72,8 @@ export function loadFonts(fonts: string[]) {
|
|
73
72
|
}
|
74
73
|
|
75
74
|
// 获取默认主题偏好
|
76
|
-
export function getDefaultThemePrefer(): PaletteMode {
|
77
|
-
const prefer = window.blocklet
|
75
|
+
export function getDefaultThemePrefer(meta?: { theme: { prefer: 'light' | 'dark' | 'system' } }): PaletteMode {
|
76
|
+
const prefer = Object.assign({}, window.blocklet, meta).theme?.prefer;
|
78
77
|
|
79
78
|
if (prefer === 'system') {
|
80
79
|
// 本地缓存
|
@@ -33,7 +33,7 @@ function MinimalContent(props: MinimalContentProps) {
|
|
33
33
|
|
34
34
|
return (
|
35
35
|
<Box display="flex" justifyContent="space-between" alignItems="center" className="user-card__avatar-content">
|
36
|
-
<Box display="flex" justifyContent="flex-start" alignItems="center" gap={
|
36
|
+
<Box display="flex" justifyContent="flex-start" alignItems="center" gap={1} flex={1} minWidth={0}>
|
37
37
|
<TooltipAvatar
|
38
38
|
user={user}
|
39
39
|
avatarSize={avatarSize}
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import React from 'react';
|
2
|
-
import
|
2
|
+
import Box from '@mui/material/Box';
|
3
|
+
import MaterialAvatar from '@mui/material/Avatar';
|
3
4
|
import Avatar from '../Avatar';
|
4
5
|
import { User, UserCardProps } from './types';
|
5
6
|
import { createNameOnlyAvatar } from './utils';
|
@@ -18,19 +19,25 @@ export const renderAvatar = (
|
|
18
19
|
// 如果用户没有头像,则显示名称首字母头像
|
19
20
|
if (!user.avatar) {
|
20
21
|
const avatarContent = createNameOnlyAvatar(user);
|
22
|
+
// 从 avatarProps 中提取 MUI 的 Avatar 支持的基本 props
|
23
|
+
// className, style 等基本 React 属性
|
24
|
+
const { className, style, alt } = avatarProps || {};
|
25
|
+
|
21
26
|
return (
|
22
|
-
<
|
23
|
-
size={avatarSize}
|
24
|
-
did={user.did}
|
25
|
-
variant="circle"
|
27
|
+
<MaterialAvatar
|
26
28
|
onClick={onClick}
|
29
|
+
className={className}
|
30
|
+
style={style}
|
31
|
+
alt={alt}
|
27
32
|
sx={{
|
33
|
+
width: avatarSize,
|
34
|
+
height: avatarSize,
|
28
35
|
fontSize: avatarSize * 0.4,
|
29
36
|
cursor: shouldShowHoverCard || onAvatarClick ? 'pointer' : 'default',
|
30
37
|
}}
|
31
|
-
|
38
|
+
variant="circular">
|
32
39
|
{avatarContent}
|
33
|
-
</
|
40
|
+
</MaterialAvatar>
|
34
41
|
);
|
35
42
|
}
|
36
43
|
|
package/src/UserCard/index.tsx
CHANGED
@@ -47,7 +47,7 @@ function UserCard(props: UserCardProps) {
|
|
47
47
|
} else if (props.did && isUserDid(props.did) && !props.user) {
|
48
48
|
getUserByDid(props.did).then((_user) => {
|
49
49
|
if (isSubscribed) {
|
50
|
-
setUser(_user);
|
50
|
+
setUser(_user || { fullName: 'Anonymous', did: props.did as string, avatar: '' });
|
51
51
|
}
|
52
52
|
});
|
53
53
|
}
|
@@ -74,6 +74,7 @@ function UserCard(props: UserCardProps) {
|
|
74
74
|
shortenLabelProps={props.popupShortenLabelProps}
|
75
75
|
renderFields={props.popupRenderFields}
|
76
76
|
renderName={props.popupRenderName}
|
77
|
+
infoType={props.popupInfoType || props.infoType}
|
77
78
|
/>
|
78
79
|
</DialogContainer>
|
79
80
|
);
|
package/src/UserCard/types.ts
CHANGED
@@ -8,7 +8,6 @@ type UserPublicInfo = {
|
|
8
8
|
avatar: string;
|
9
9
|
did: string;
|
10
10
|
fullName: string;
|
11
|
-
sourceAppPid: string | null;
|
12
11
|
};
|
13
12
|
export type UserMetadataLink = {
|
14
13
|
url: string;
|
@@ -112,6 +111,7 @@ export interface UserCardProps {
|
|
112
111
|
did?: string;
|
113
112
|
cardType?: CardType;
|
114
113
|
infoType?: InfoType;
|
114
|
+
popupInfoType?: InfoType;
|
115
115
|
avatarSize?: number;
|
116
116
|
showHoverCard?: boolean;
|
117
117
|
showDid?: boolean;
|
package/src/UserCard/utils.ts
CHANGED
@@ -11,6 +11,57 @@ try {
|
|
11
11
|
client = null;
|
12
12
|
}
|
13
13
|
|
14
|
+
// 用户信息缓存键前缀
|
15
|
+
const USER_CACHE_PREFIX = 'ux_user_';
|
16
|
+
// 用户缓存的有效期(毫秒),默认60分钟
|
17
|
+
const USER_CACHE_EXPIRATION = 60 * 60 * 1000;
|
18
|
+
|
19
|
+
// 从sessionStorage获取用户信息
|
20
|
+
const getUserFromStorage = (did: string): User | null => {
|
21
|
+
if (typeof sessionStorage === 'undefined') return null;
|
22
|
+
|
23
|
+
try {
|
24
|
+
const cacheKey = `${USER_CACHE_PREFIX}${did}`;
|
25
|
+
const cachedData = sessionStorage.getItem(cacheKey);
|
26
|
+
|
27
|
+
if (!cachedData) return null;
|
28
|
+
|
29
|
+
const parsedData = JSON.parse(cachedData);
|
30
|
+
const timestamp = parsedData.timestamp || 0;
|
31
|
+
const now = Date.now();
|
32
|
+
|
33
|
+
// 检查缓存是否过期
|
34
|
+
if (now - timestamp > USER_CACHE_EXPIRATION) {
|
35
|
+
// 缓存已过期,删除并返回null
|
36
|
+
sessionStorage.removeItem(cacheKey);
|
37
|
+
return null;
|
38
|
+
}
|
39
|
+
|
40
|
+
// 返回用户数据
|
41
|
+
return parsedData.user;
|
42
|
+
} catch (error) {
|
43
|
+
console.error(`Failed to load user cache for did ${did}:`, error);
|
44
|
+
return null;
|
45
|
+
}
|
46
|
+
};
|
47
|
+
|
48
|
+
// 将用户信息保存到sessionStorage
|
49
|
+
const saveUserToStorage = (did: string, user: User): void => {
|
50
|
+
if (typeof sessionStorage === 'undefined') return;
|
51
|
+
|
52
|
+
try {
|
53
|
+
const cacheKey = `${USER_CACHE_PREFIX}${did}`;
|
54
|
+
// 创建包含用户数据和时间戳的缓存对象
|
55
|
+
const cacheData = {
|
56
|
+
user,
|
57
|
+
timestamp: Date.now(),
|
58
|
+
};
|
59
|
+
sessionStorage.setItem(cacheKey, JSON.stringify(cacheData));
|
60
|
+
} catch (error) {
|
61
|
+
console.error(`Failed to save user cache for did ${did}:`, error);
|
62
|
+
}
|
63
|
+
};
|
64
|
+
|
14
65
|
// 创建仅显示名称首字母的头像
|
15
66
|
// eslint-disable-next-line import/prefer-default-export
|
16
67
|
export function createNameOnlyAvatar(user: User) {
|
@@ -44,12 +95,55 @@ export function isUserDid(did: string) {
|
|
44
95
|
}
|
45
96
|
|
46
97
|
export async function getUserByDid(did: string): Promise<User | null> {
|
98
|
+
if (!did) return null;
|
99
|
+
|
100
|
+
// 先检查sessionStorage中是否有缓存
|
101
|
+
const storedUser = getUserFromStorage(did);
|
102
|
+
if (storedUser) {
|
103
|
+
return storedUser;
|
104
|
+
}
|
105
|
+
|
47
106
|
if (!client) return null;
|
107
|
+
|
48
108
|
try {
|
49
109
|
const user = await client.user.getUserPublicInfo({ did });
|
110
|
+
// 将获取到的用户信息存入缓存
|
111
|
+
if (user) {
|
112
|
+
const userData = user as User;
|
113
|
+
saveUserToStorage(did, userData);
|
114
|
+
}
|
50
115
|
return user as User;
|
51
116
|
} catch (error) {
|
52
117
|
console.error('Failed to get user by did:', error);
|
53
118
|
return null;
|
54
119
|
}
|
55
120
|
}
|
121
|
+
|
122
|
+
// 清除缓存中特定用户的信息
|
123
|
+
export function clearUserCache(did?: string): void {
|
124
|
+
if (typeof sessionStorage === 'undefined') return;
|
125
|
+
|
126
|
+
if (did) {
|
127
|
+
// 清除指定用户缓存
|
128
|
+
try {
|
129
|
+
sessionStorage.removeItem(`${USER_CACHE_PREFIX}${did}`);
|
130
|
+
} catch (error) {
|
131
|
+
console.error(`Failed to remove cache for did ${did}:`, error);
|
132
|
+
}
|
133
|
+
} else {
|
134
|
+
// 清除所有用户缓存
|
135
|
+
try {
|
136
|
+
// 只清除以USER_CACHE_PREFIX开头的项
|
137
|
+
const keysToRemove: string[] = [];
|
138
|
+
for (let i = 0; i < sessionStorage.length; i++) {
|
139
|
+
const key = sessionStorage.key(i);
|
140
|
+
if (key && key.startsWith(USER_CACHE_PREFIX)) {
|
141
|
+
keysToRemove.push(key);
|
142
|
+
}
|
143
|
+
}
|
144
|
+
keysToRemove.forEach((key) => sessionStorage.removeItem(key));
|
145
|
+
} catch (error) {
|
146
|
+
console.error('Failed to clear all user caches:', error);
|
147
|
+
}
|
148
|
+
}
|
149
|
+
}
|
package/src/type.d.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import type { Theme
|
1
|
+
import type { Theme } from '@mui/material';
|
2
2
|
import type { LiteralUnion } from 'type-fest';
|
3
3
|
|
4
4
|
export type $TSFixMe = any;
|
@@ -26,7 +26,7 @@ export type Blocklet = {
|
|
26
26
|
mode: string;
|
27
27
|
tenantMode: 'single' | 'multiple';
|
28
28
|
theme: {
|
29
|
-
prefer?:
|
29
|
+
prefer?: 'light' | 'dark' | 'system';
|
30
30
|
light: Theme;
|
31
31
|
dark: Theme;
|
32
32
|
};
|