@arcblock/ux 2.12.53 → 2.12.55
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/BlockletContext/index.d.ts +9 -2
- package/lib/BlockletContext/index.js +30 -8
- package/lib/Config/config-provider.js +15 -5
- package/lib/Theme/theme.d.ts +7 -11
- package/lib/Theme/theme.js +60 -13
- package/package.json +9 -7
- package/src/BlockletContext/index.tsx +30 -8
- package/src/Config/config-provider.tsx +17 -5
- package/src/Theme/theme.ts +69 -30
@@ -1,6 +1,12 @@
|
|
1
1
|
import type { Blocklet } from '../type';
|
2
|
-
declare const BlockletContext: import("react").Context<
|
3
|
-
|
2
|
+
declare const BlockletContext: import("react").Context<{
|
3
|
+
blocklet: Blocklet | null;
|
4
|
+
masterBlocklet: Blocklet | null;
|
5
|
+
}>;
|
6
|
+
declare const Consumer: import("react").Consumer<{
|
7
|
+
blocklet: Blocklet | null;
|
8
|
+
masterBlocklet: Blocklet | null;
|
9
|
+
}>;
|
4
10
|
declare function BlockletProvider({ children, baseUrl, loading, }: {
|
5
11
|
children?: React.ReactNode;
|
6
12
|
/**
|
@@ -11,5 +17,6 @@ declare function BlockletProvider({ children, baseUrl, loading, }: {
|
|
11
17
|
}): import("react/jsx-runtime").JSX.Element;
|
12
18
|
declare function useBlockletContext(): {
|
13
19
|
blocklet: Blocklet | null;
|
20
|
+
masterBlocklet: Blocklet | null;
|
14
21
|
};
|
15
22
|
export { BlockletContext, BlockletProvider, Consumer as BlockletConsumer, useBlockletContext };
|
@@ -1,8 +1,11 @@
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
2
2
|
import { useMemoizedFn, useAsyncEffect } from 'ahooks';
|
3
3
|
import { createContext, useContext, useState } from 'react';
|
4
|
-
import { getBlockletData } from '../Util/federated';
|
5
|
-
const BlockletContext = /*#__PURE__*/createContext(
|
4
|
+
import { getBlockletData, getFederatedEnabled, getMaster } from '../Util/federated';
|
5
|
+
const BlockletContext = /*#__PURE__*/createContext({
|
6
|
+
blocklet: null,
|
7
|
+
masterBlocklet: null
|
8
|
+
});
|
6
9
|
const {
|
7
10
|
Provider,
|
8
11
|
Consumer
|
@@ -13,6 +16,7 @@ function BlockletProvider({
|
|
13
16
|
loading = null
|
14
17
|
}) {
|
15
18
|
const [blockletData, setBlockletData] = useState(null);
|
19
|
+
const [masterBlockletData, setMasterBlockletData] = useState(null);
|
16
20
|
const getBlockleDataWithCache = useMemoizedFn(async () => {
|
17
21
|
if (!baseUrl || window.location.href.startsWith(baseUrl)) {
|
18
22
|
throw new Error('no blocklet data');
|
@@ -21,25 +25,43 @@ function BlockletProvider({
|
|
21
25
|
return jsonData;
|
22
26
|
});
|
23
27
|
useAsyncEffect(async () => {
|
28
|
+
let result = null;
|
29
|
+
let resultMaster = null;
|
24
30
|
try {
|
25
|
-
|
26
|
-
setBlockletData(data);
|
31
|
+
result = await getBlockleDataWithCache();
|
27
32
|
} catch {
|
28
33
|
// NOTICE: 如果获取指定 blockletData 失败,则使用 window.blocklet
|
29
34
|
const data = globalThis.blocklet || globalThis.env;
|
30
|
-
|
35
|
+
result = data;
|
31
36
|
}
|
37
|
+
if (result) {
|
38
|
+
if (getFederatedEnabled(result)) {
|
39
|
+
const masterSite = getMaster(result);
|
40
|
+
if (masterSite) {
|
41
|
+
resultMaster = await getBlockletData(masterSite.appUrl);
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|
45
|
+
setBlockletData(result);
|
46
|
+
setMasterBlockletData(resultMaster);
|
32
47
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
33
48
|
}, [baseUrl]);
|
34
49
|
return /*#__PURE__*/_jsx(Provider, {
|
35
|
-
value:
|
50
|
+
value: {
|
51
|
+
blocklet: blockletData,
|
52
|
+
masterBlocklet: masterBlockletData
|
53
|
+
},
|
36
54
|
children: blockletData ? children : loading || null
|
37
55
|
});
|
38
56
|
}
|
39
57
|
function useBlockletContext() {
|
40
|
-
const
|
58
|
+
const {
|
59
|
+
blocklet,
|
60
|
+
masterBlocklet
|
61
|
+
} = useContext(BlockletContext);
|
41
62
|
return {
|
42
|
-
blocklet
|
63
|
+
blocklet,
|
64
|
+
masterBlocklet
|
43
65
|
};
|
44
66
|
}
|
45
67
|
export { BlockletContext, BlockletProvider, Consumer as BlockletConsumer, useBlockletContext };
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
2
|
-
import { createContext, useContext, useMemo, useState, useCallback } from 'react';
|
2
|
+
import { createContext, useContext, useMemo, useState, useCallback, useEffect } from 'react';
|
3
3
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
4
4
|
import set from 'lodash/set';
|
5
5
|
import { LocaleProvider, useLocaleContext } from '../Locale/context';
|
@@ -28,11 +28,14 @@ export function ConfigProvider({
|
|
28
28
|
}) {
|
29
29
|
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
|
30
30
|
const [mode, setMode] = useState(() => {
|
31
|
-
|
32
|
-
|
33
|
-
return preferMode;
|
31
|
+
if (prefer) {
|
32
|
+
return prefer;
|
34
33
|
}
|
35
|
-
|
34
|
+
const localPrefer = localStorage.getItem(preferThemeModeKey);
|
35
|
+
if (localPrefer && (localPrefer === 'light' || localPrefer === 'dark')) {
|
36
|
+
return localPrefer;
|
37
|
+
}
|
38
|
+
return prefersDarkMode ? 'dark' : 'light';
|
36
39
|
});
|
37
40
|
const _themeOptions = useMemo(() => {
|
38
41
|
let result = {};
|
@@ -64,6 +67,13 @@ export function ConfigProvider({
|
|
64
67
|
themeOptions: _themeOptions,
|
65
68
|
toggleMode
|
66
69
|
}), [mode, _themeOptions, toggleMode]);
|
70
|
+
|
71
|
+
// change prefer manually
|
72
|
+
useEffect(() => {
|
73
|
+
if (prefer) {
|
74
|
+
setMode(prefer);
|
75
|
+
}
|
76
|
+
}, [prefer, setMode]);
|
67
77
|
return /*#__PURE__*/_jsx(ConfigContext.Provider, {
|
68
78
|
value: config,
|
69
79
|
children: /*#__PURE__*/_jsx(LocaleProvider, {
|
package/lib/Theme/theme.d.ts
CHANGED
@@ -1,13 +1,8 @@
|
|
1
1
|
import { Components, type ThemeOptions } from '@mui/material/styles';
|
2
2
|
import type { Typography } from '@mui/material/styles/createTypography';
|
3
|
-
import '@fontsource/
|
4
|
-
import '@fontsource/
|
5
|
-
import '@fontsource/
|
6
|
-
import '@fontsource/inter/latin-700.css';
|
7
|
-
import '@fontsource/inter/latin-ext-300.css';
|
8
|
-
import '@fontsource/inter/latin-ext-400.css';
|
9
|
-
import '@fontsource/inter/latin-ext-500.css';
|
10
|
-
import '@fontsource/inter/latin-ext-700.css';
|
3
|
+
import '@fontsource/roboto/400';
|
4
|
+
import '@fontsource/roboto/500';
|
5
|
+
import '@fontsource/roboto/700';
|
11
6
|
import { ThemeMode } from '../type';
|
12
7
|
declare module '@mui/material/styles' {
|
13
8
|
interface Theme {
|
@@ -39,11 +34,12 @@ declare module '@mui/material/styles/createTypography' {
|
|
39
34
|
interface TypographyOptions {
|
40
35
|
useNextVariants?: boolean;
|
41
36
|
color?: Record<string, string>;
|
42
|
-
button?: {
|
43
|
-
fontWeight?: number;
|
44
|
-
};
|
45
37
|
}
|
46
38
|
}
|
39
|
+
export declare function collectFontFamilies(obj?: {
|
40
|
+
fontFamily?: string;
|
41
|
+
}, fontSet?: Set<string>): Set<string>;
|
42
|
+
export declare function loadFonts(fonts: string[]): Promise<boolean>;
|
47
43
|
export declare function createDefaultThemeOptions(mode?: ThemeMode): ThemeOptions;
|
48
44
|
export declare const create: ({ mode, pageWidth, overrides, palette, components, ...rest }?: ThemeOptions) => import("@mui/material/styles").Theme;
|
49
45
|
export declare const createTheme: ({ mode, pageWidth, overrides, palette, components, ...rest }?: ThemeOptions) => import("@mui/material/styles").Theme;
|
package/lib/Theme/theme.js
CHANGED
@@ -2,15 +2,14 @@
|
|
2
2
|
// https://app.zeplin.io/styleguide/5d1436f1e97c2156f49c0725/colors
|
3
3
|
import { createTheme as _createTheme, responsiveFontSizes } from '@mui/material/styles';
|
4
4
|
import { deepmerge } from '@mui/utils';
|
5
|
-
|
6
|
-
|
7
|
-
import
|
8
|
-
import '@fontsource/
|
9
|
-
import
|
10
|
-
import '@fontsource/
|
11
|
-
import
|
12
|
-
import '@fontsource/
|
13
|
-
import '@fontsource/inter/latin-ext-700.css';
|
5
|
+
import webfontloader from 'webfontloader';
|
6
|
+
// 为了避免加载全量的字体导致打包后体积太大,目前只选择了 MUI 默认的 Roboto 字体
|
7
|
+
// eslint-disable-next-line import/no-unresolved
|
8
|
+
import '@fontsource/roboto/400';
|
9
|
+
// eslint-disable-next-line import/no-unresolved
|
10
|
+
import '@fontsource/roboto/500';
|
11
|
+
// eslint-disable-next-line import/no-unresolved
|
12
|
+
import '@fontsource/roboto/700';
|
14
13
|
import colors from '../Colors';
|
15
14
|
import { cleanedObj } from '../Util';
|
16
15
|
|
@@ -18,12 +17,56 @@ import { cleanedObj } from '../Util';
|
|
18
17
|
|
19
18
|
// 扩展 TypographyOptions
|
20
19
|
|
21
|
-
|
20
|
+
// 默认深色主题
|
21
|
+
const defaultDarkTheme = _createTheme({
|
22
22
|
palette: {
|
23
23
|
mode: 'dark'
|
24
24
|
}
|
25
25
|
});
|
26
|
-
|
26
|
+
|
27
|
+
// 收集字体配置
|
28
|
+
export function collectFontFamilies(obj, fontSet = new Set()) {
|
29
|
+
if (!obj || typeof obj !== 'object') return fontSet;
|
30
|
+
if (typeof obj.fontFamily === 'string') {
|
31
|
+
obj.fontFamily.replace(/"/g, '').split(',').map(font => font.trim()).filter(Boolean).forEach(font => fontSet.add(font));
|
32
|
+
}
|
33
|
+
Object.values(obj).forEach(value => {
|
34
|
+
if (typeof value === 'object') {
|
35
|
+
collectFontFamilies(value, fontSet);
|
36
|
+
}
|
37
|
+
});
|
38
|
+
return fontSet;
|
39
|
+
}
|
40
|
+
|
41
|
+
// 动态加载字体
|
42
|
+
const prevFonts = new Set(['Roboto', 'inherit']);
|
43
|
+
export function loadFonts(fonts) {
|
44
|
+
// 过滤出未加载的字体
|
45
|
+
const unloadedFonts = fonts.filter(font => !prevFonts.has(font));
|
46
|
+
|
47
|
+
// 如果所有字体都已加载,直接返回
|
48
|
+
if (unloadedFonts.length === 0) {
|
49
|
+
return Promise.resolve(true);
|
50
|
+
}
|
51
|
+
|
52
|
+
// record
|
53
|
+
unloadedFonts.forEach(font => prevFonts.add(font));
|
54
|
+
return new Promise(resolve => {
|
55
|
+
webfontloader.load({
|
56
|
+
google: {
|
57
|
+
families: unloadedFonts
|
58
|
+
},
|
59
|
+
active: () => resolve(true),
|
60
|
+
inactive: () => resolve(true),
|
61
|
+
fontinactive: (familyName, fvd) => {
|
62
|
+
prevFonts.delete(familyName);
|
63
|
+
console.warn(`font ${familyName} ${fvd} download failed`);
|
64
|
+
}
|
65
|
+
});
|
66
|
+
});
|
67
|
+
}
|
68
|
+
|
69
|
+
// 创建默认主题配置
|
27
70
|
export function createDefaultThemeOptions(mode = 'light') {
|
28
71
|
const result = {
|
29
72
|
palette: {
|
@@ -44,7 +87,6 @@ export function createDefaultThemeOptions(mode = 'light') {
|
|
44
87
|
main: mode === 'light' ? '#222222' : colors.common.white,
|
45
88
|
gray: mode === 'light' ? colors.grey[500] : colors.grey[300]
|
46
89
|
},
|
47
|
-
fontFamily: DEFAULT_FONT_FAMILY,
|
48
90
|
// button 默认使用粗体
|
49
91
|
button: {
|
50
92
|
fontWeight: 700
|
@@ -95,10 +137,11 @@ export function createDefaultThemeOptions(mode = 'light') {
|
|
95
137
|
}
|
96
138
|
}
|
97
139
|
};
|
140
|
+
|
98
141
|
// 深色主题
|
99
142
|
if (mode === 'dark') {
|
100
143
|
result.palette = {
|
101
|
-
...
|
144
|
+
...defaultDarkTheme.palette,
|
102
145
|
background: {
|
103
146
|
paper: colors.grey[900],
|
104
147
|
default: colors.grey[900]
|
@@ -163,6 +206,10 @@ export const create = ({
|
|
163
206
|
const mergedThemeOptions = deepmerge(deepmerge(defaultThemeOptions, cleanedObj(blockletThemeOptions)), cleanedObj(userThemeOptions));
|
164
207
|
const theme = _createTheme(mergedThemeOptions);
|
165
208
|
|
209
|
+
// 异步加载字体
|
210
|
+
const fonts = collectFontFamilies(theme.typography);
|
211
|
+
loadFonts(Array.from(fonts));
|
212
|
+
|
166
213
|
/**
|
167
214
|
* 响应式字体,配置后,theme.typography 会变为下面的结构
|
168
215
|
* {
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@arcblock/ux",
|
3
|
-
"version": "2.12.
|
3
|
+
"version": "2.12.55",
|
4
4
|
"description": "Common used react components for arcblock products",
|
5
5
|
"keywords": [
|
6
6
|
"react",
|
@@ -47,6 +47,7 @@
|
|
47
47
|
"@types/pako": "^2.0.3",
|
48
48
|
"@types/react": "^18.3.4",
|
49
49
|
"@types/react-helmet": "^6.1.11",
|
50
|
+
"@types/webfontloader": "^1.6.38",
|
50
51
|
"@typescript-eslint/eslint-plugin": "^8.7.0",
|
51
52
|
"@typescript-eslint/parser": "^8.7.0",
|
52
53
|
"babel-jest": "29",
|
@@ -69,14 +70,14 @@
|
|
69
70
|
"react": ">=18.2.0",
|
70
71
|
"react-router-dom": ">=6.22.3"
|
71
72
|
},
|
72
|
-
"gitHead": "
|
73
|
+
"gitHead": "33f46387b9d5664d83854183a51b9c7e6641c6bf",
|
73
74
|
"dependencies": {
|
74
75
|
"@arcblock/did-motif": "^1.1.13",
|
75
|
-
"@arcblock/icons": "^2.12.
|
76
|
-
"@arcblock/nft-display": "^2.12.
|
77
|
-
"@arcblock/react-hooks": "^2.12.
|
76
|
+
"@arcblock/icons": "^2.12.55",
|
77
|
+
"@arcblock/nft-display": "^2.12.55",
|
78
|
+
"@arcblock/react-hooks": "^2.12.55",
|
78
79
|
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
79
|
-
"@fontsource/
|
80
|
+
"@fontsource/roboto": "^5.2.5",
|
80
81
|
"@fontsource/ubuntu-mono": "^5.0.18",
|
81
82
|
"@iconify-icons/logos": "^1.2.36",
|
82
83
|
"@iconify-icons/material-symbols": "^1.2.58",
|
@@ -123,6 +124,7 @@
|
|
123
124
|
"topojson-client": "^3.1.0",
|
124
125
|
"type-fest": "^4.28.0",
|
125
126
|
"validator": "^13.9.0",
|
126
|
-
"versor": "^0.0.4"
|
127
|
+
"versor": "^0.0.4",
|
128
|
+
"webfontloader": "^1.6.28"
|
127
129
|
}
|
128
130
|
}
|
@@ -1,9 +1,15 @@
|
|
1
1
|
import { useMemoizedFn, useAsyncEffect } from 'ahooks';
|
2
2
|
import { createContext, useContext, useState } from 'react';
|
3
3
|
import type { Blocklet } from '../type';
|
4
|
-
import { getBlockletData } from '../Util/federated';
|
4
|
+
import { getBlockletData, getFederatedEnabled, getMaster } from '../Util/federated';
|
5
5
|
|
6
|
-
const BlockletContext = createContext<
|
6
|
+
const BlockletContext = createContext<{
|
7
|
+
blocklet: Blocklet | null;
|
8
|
+
masterBlocklet: Blocklet | null;
|
9
|
+
}>({
|
10
|
+
blocklet: null,
|
11
|
+
masterBlocklet: null,
|
12
|
+
});
|
7
13
|
|
8
14
|
const { Provider, Consumer } = BlockletContext;
|
9
15
|
|
@@ -20,6 +26,7 @@ function BlockletProvider({
|
|
20
26
|
loading?: React.ReactNode;
|
21
27
|
}) {
|
22
28
|
const [blockletData, setBlockletData] = useState<Blocklet | null>(null);
|
29
|
+
const [masterBlockletData, setMasterBlockletData] = useState<Blocklet | null>(null);
|
23
30
|
const getBlockleDataWithCache = useMemoizedFn(async () => {
|
24
31
|
if (!baseUrl || window.location.href.startsWith(baseUrl)) {
|
25
32
|
throw new Error('no blocklet data');
|
@@ -29,22 +36,37 @@ function BlockletProvider({
|
|
29
36
|
});
|
30
37
|
|
31
38
|
useAsyncEffect(async () => {
|
39
|
+
let result = null;
|
40
|
+
let resultMaster = null;
|
32
41
|
try {
|
33
|
-
|
34
|
-
setBlockletData(data);
|
42
|
+
result = await getBlockleDataWithCache();
|
35
43
|
} catch {
|
36
44
|
// NOTICE: 如果获取指定 blockletData 失败,则使用 window.blocklet
|
37
45
|
const data = globalThis.blocklet || globalThis.env;
|
38
|
-
|
46
|
+
result = data;
|
39
47
|
}
|
48
|
+
if (result) {
|
49
|
+
if (getFederatedEnabled(result)) {
|
50
|
+
const masterSite = getMaster(result);
|
51
|
+
if (masterSite) {
|
52
|
+
resultMaster = await getBlockletData(masterSite.appUrl);
|
53
|
+
}
|
54
|
+
}
|
55
|
+
}
|
56
|
+
setBlockletData(result);
|
57
|
+
setMasterBlockletData(resultMaster);
|
40
58
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
41
59
|
}, [baseUrl]);
|
42
|
-
return
|
60
|
+
return (
|
61
|
+
<Provider value={{ blocklet: blockletData, masterBlocklet: masterBlockletData }}>
|
62
|
+
{blockletData ? children : loading || null}
|
63
|
+
</Provider>
|
64
|
+
);
|
43
65
|
}
|
44
66
|
|
45
67
|
function useBlockletContext() {
|
46
|
-
const blocklet = useContext(BlockletContext);
|
47
|
-
return { blocklet };
|
68
|
+
const { blocklet, masterBlocklet } = useContext(BlockletContext);
|
69
|
+
return { blocklet, masterBlocklet };
|
48
70
|
}
|
49
71
|
|
50
72
|
export { BlockletContext, BlockletProvider, Consumer as BlockletConsumer, useBlockletContext };
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { createContext, useContext, ReactNode, useMemo, useState, useCallback } from 'react';
|
1
|
+
import { createContext, useContext, ReactNode, useMemo, useState, useCallback, useEffect } from 'react';
|
2
2
|
import type { ThemeOptions } from '@mui/material/styles';
|
3
3
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
4
4
|
import set from 'lodash/set';
|
@@ -49,11 +49,16 @@ export function ConfigProvider({
|
|
49
49
|
}: ConfigProviderProps) {
|
50
50
|
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
|
51
51
|
const [mode, setMode] = useState<ThemeMode>(() => {
|
52
|
-
|
53
|
-
|
54
|
-
return preferMode;
|
52
|
+
if (prefer) {
|
53
|
+
return prefer;
|
55
54
|
}
|
56
|
-
|
55
|
+
|
56
|
+
const localPrefer = localStorage.getItem(preferThemeModeKey) as ThemeMode;
|
57
|
+
if (localPrefer && (localPrefer === 'light' || localPrefer === 'dark')) {
|
58
|
+
return localPrefer;
|
59
|
+
}
|
60
|
+
|
61
|
+
return prefersDarkMode ? 'dark' : 'light';
|
57
62
|
});
|
58
63
|
|
59
64
|
const _themeOptions = useMemo(() => {
|
@@ -92,6 +97,13 @@ export function ConfigProvider({
|
|
92
97
|
[mode, _themeOptions, toggleMode]
|
93
98
|
);
|
94
99
|
|
100
|
+
// change prefer manually
|
101
|
+
useEffect(() => {
|
102
|
+
if (prefer) {
|
103
|
+
setMode(prefer);
|
104
|
+
}
|
105
|
+
}, [prefer, setMode]);
|
106
|
+
|
95
107
|
return (
|
96
108
|
<ConfigContext.Provider value={config}>
|
97
109
|
<LocaleProvider
|
package/src/Theme/theme.ts
CHANGED
@@ -3,15 +3,15 @@
|
|
3
3
|
import { createTheme as _createTheme, Components, responsiveFontSizes, type ThemeOptions } from '@mui/material/styles';
|
4
4
|
import { deepmerge } from '@mui/utils';
|
5
5
|
import type { Typography } from '@mui/material/styles/createTypography';
|
6
|
-
|
7
|
-
|
8
|
-
import
|
9
|
-
import '@fontsource/
|
10
|
-
import
|
11
|
-
import '@fontsource/
|
12
|
-
import
|
13
|
-
import '@fontsource/
|
14
|
-
|
6
|
+
import webfontloader from 'webfontloader';
|
7
|
+
// 为了避免加载全量的字体导致打包后体积太大,目前只选择了 MUI 默认的 Roboto 字体
|
8
|
+
// eslint-disable-next-line import/no-unresolved
|
9
|
+
import '@fontsource/roboto/400';
|
10
|
+
// eslint-disable-next-line import/no-unresolved
|
11
|
+
import '@fontsource/roboto/500';
|
12
|
+
// eslint-disable-next-line import/no-unresolved
|
13
|
+
import '@fontsource/roboto/700';
|
14
|
+
|
15
15
|
import colors from '../Colors';
|
16
16
|
import { ThemeMode } from '../type';
|
17
17
|
import { cleanedObj } from '../Util';
|
@@ -48,29 +48,64 @@ declare module '@mui/material/styles/createTypography' {
|
|
48
48
|
interface TypographyOptions {
|
49
49
|
useNextVariants?: boolean;
|
50
50
|
color?: Record<string, string>;
|
51
|
-
button?: {
|
52
|
-
fontWeight?: number;
|
53
|
-
};
|
54
51
|
}
|
55
52
|
}
|
56
53
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
'
|
63
|
-
|
64
|
-
'
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
54
|
+
// 默认深色主题
|
55
|
+
const defaultDarkTheme = _createTheme({ palette: { mode: 'dark' } });
|
56
|
+
|
57
|
+
// 收集字体配置
|
58
|
+
export function collectFontFamilies(obj?: { fontFamily?: string }, fontSet: Set<string> = new Set()): Set<string> {
|
59
|
+
if (!obj || typeof obj !== 'object') return fontSet;
|
60
|
+
|
61
|
+
if (typeof obj.fontFamily === 'string') {
|
62
|
+
obj.fontFamily
|
63
|
+
.replace(/"/g, '')
|
64
|
+
.split(',')
|
65
|
+
.map((font: string) => font.trim())
|
66
|
+
.filter(Boolean)
|
67
|
+
.forEach((font: string) => fontSet.add(font));
|
68
|
+
}
|
69
|
+
|
70
|
+
Object.values(obj).forEach((value) => {
|
71
|
+
if (typeof value === 'object') {
|
72
|
+
collectFontFamilies(value, fontSet);
|
73
|
+
}
|
74
|
+
});
|
75
|
+
|
76
|
+
return fontSet;
|
77
|
+
}
|
78
|
+
|
79
|
+
// 动态加载字体
|
80
|
+
const prevFonts = new Set<string>(['Roboto', 'inherit']);
|
81
|
+
export function loadFonts(fonts: string[]) {
|
82
|
+
// 过滤出未加载的字体
|
83
|
+
const unloadedFonts = fonts.filter((font) => !prevFonts.has(font));
|
73
84
|
|
85
|
+
// 如果所有字体都已加载,直接返回
|
86
|
+
if (unloadedFonts.length === 0) {
|
87
|
+
return Promise.resolve(true);
|
88
|
+
}
|
89
|
+
|
90
|
+
// record
|
91
|
+
unloadedFonts.forEach((font) => prevFonts.add(font));
|
92
|
+
|
93
|
+
return new Promise<boolean>((resolve) => {
|
94
|
+
webfontloader.load({
|
95
|
+
google: {
|
96
|
+
families: unloadedFonts,
|
97
|
+
},
|
98
|
+
active: () => resolve(true),
|
99
|
+
inactive: () => resolve(true),
|
100
|
+
fontinactive: (familyName, fvd) => {
|
101
|
+
prevFonts.delete(familyName);
|
102
|
+
console.warn(`font ${familyName} ${fvd} download failed`);
|
103
|
+
},
|
104
|
+
});
|
105
|
+
});
|
106
|
+
}
|
107
|
+
|
108
|
+
// 创建默认主题配置
|
74
109
|
export function createDefaultThemeOptions(mode: ThemeMode = 'light'): ThemeOptions {
|
75
110
|
const result: ThemeOptions = {
|
76
111
|
palette: {
|
@@ -91,7 +126,6 @@ export function createDefaultThemeOptions(mode: ThemeMode = 'light'): ThemeOptio
|
|
91
126
|
main: mode === 'light' ? '#222222' : colors.common.white,
|
92
127
|
gray: mode === 'light' ? colors.grey[500] : colors.grey[300],
|
93
128
|
},
|
94
|
-
fontFamily: DEFAULT_FONT_FAMILY,
|
95
129
|
// button 默认使用粗体
|
96
130
|
button: {
|
97
131
|
fontWeight: 700,
|
@@ -142,10 +176,11 @@ export function createDefaultThemeOptions(mode: ThemeMode = 'light'): ThemeOptio
|
|
142
176
|
},
|
143
177
|
},
|
144
178
|
};
|
179
|
+
|
145
180
|
// 深色主题
|
146
181
|
if (mode === 'dark') {
|
147
182
|
result.palette = {
|
148
|
-
...
|
183
|
+
...defaultDarkTheme.palette,
|
149
184
|
background: {
|
150
185
|
paper: colors.grey[900],
|
151
186
|
default: colors.grey[900],
|
@@ -215,6 +250,10 @@ export const create = ({
|
|
215
250
|
|
216
251
|
const theme = _createTheme(mergedThemeOptions);
|
217
252
|
|
253
|
+
// 异步加载字体
|
254
|
+
const fonts = collectFontFamilies(theme.typography);
|
255
|
+
loadFonts(Array.from(fonts));
|
256
|
+
|
218
257
|
/**
|
219
258
|
* 响应式字体,配置后,theme.typography 会变为下面的结构
|
220
259
|
* {
|