@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.
@@ -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?: PaletteMode;
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
- }), [mode, _themeOptions, toggleMode]);
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
- return /*#__PURE__*/_jsx(IconButton, {
18
- onClick: toggleMode,
19
- children: mode === 'light' ? /*#__PURE__*/_jsx(Brightness2OutlinedIcon, {}) : /*#__PURE__*/_jsx(LightModeOutlinedIcon, {})
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
  }
@@ -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(): PaletteMode;
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;
@@ -63,8 +63,8 @@ export function loadFonts(fonts) {
63
63
  }
64
64
 
65
65
  // 获取默认主题偏好
66
- export function getDefaultThemePrefer() {
67
- const prefer = window.blocklet?.theme?.prefer;
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);
@@ -29,7 +29,7 @@ function MinimalContent(props) {
29
29
  display: "flex",
30
30
  justifyContent: "flex-start",
31
31
  alignItems: "center",
32
- gap: 2,
32
+ gap: 1,
33
33
  flex: 1,
34
34
  minWidth: 0,
35
35
  children: [/*#__PURE__*/_jsx(TooltipAvatar, {
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import React from 'react';
3
- import { Box } from '@mui/material';
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
- return /*#__PURE__*/_jsx(Avatar, {
16
- size: avatarSize,
17
- did: user.did,
18
- variant: "circle",
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
- ...(avatarProps || {}),
34
+ variant: "circular",
25
35
  children: avatarContent
26
36
  });
27
37
  }
@@ -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
  };
@@ -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;
@@ -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;
@@ -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, PaletteMode } from '@mui/material';
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?: PaletteMode | 'system';
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.17",
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": "cd775f10e9f90ed9813c8fd401fdae7c327c9c44",
74
+ "gitHead": "64c12fb7646d2323f1be0bef7a3901261687b165",
75
75
  "dependencies": {
76
76
  "@arcblock/did-motif": "^1.1.13",
77
- "@arcblock/icons": "^2.13.17",
78
- "@arcblock/nft-display": "^2.13.17",
79
- "@arcblock/react-hooks": "^2.13.17",
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.17",
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?: PaletteMode;
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
- return (
18
- <IconButton onClick={toggleMode}>
19
- {mode === 'light' ? <Brightness2OutlinedIcon /> : <LightModeOutlinedIcon />}
20
- </IconButton>
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
  }
@@ -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?.theme?.prefer;
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={2} flex={1} minWidth={0}>
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 { Box } from '@mui/material';
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
- <Avatar
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
- {...(avatarProps || {})}>
38
+ variant="circular">
32
39
  {avatarContent}
33
- </Avatar>
40
+ </MaterialAvatar>
34
41
  );
35
42
  }
36
43
 
@@ -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
  );
@@ -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;
@@ -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, PaletteMode } from '@mui/material';
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?: PaletteMode | 'system';
29
+ prefer?: 'light' | 'dark' | 'system';
30
30
  light: Theme;
31
31
  dark: Theme;
32
32
  };