feat: add built-in themes (#2284)
* feat: add built-in themes * chore: add translation * fix: should be optional in initial
This commit is contained in:
parent
a4d8ef4b71
commit
eb9ee38a2b
@ -71,8 +71,15 @@ const ThemeCard = (props: Props) => {
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const currentThemeId = useCurrentThemeId();
|
||||
const { t } = useTranslation();
|
||||
const { token } = useToken();
|
||||
|
||||
const isDefault = item.id === systemSettings?.data?.data?.options?.themeId;
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
if (isDefault) {
|
||||
return;
|
||||
}
|
||||
|
||||
modal.confirm({
|
||||
title: t('Delete theme'),
|
||||
content: t('Deletion is unrecoverable. Confirm deletion?'),
|
||||
@ -94,6 +101,7 @@ const ThemeCard = (props: Props) => {
|
||||
}, [
|
||||
api,
|
||||
currentUser?.data?.data?.systemSettings?.themeId,
|
||||
isDefault,
|
||||
item,
|
||||
modal,
|
||||
onChange,
|
||||
@ -197,6 +205,7 @@ const ThemeCard = (props: Props) => {
|
||||
>
|
||||
<span>{t('User selectable')}</span>
|
||||
<Switch
|
||||
disabled={isDefault}
|
||||
style={{ transform: 'translateY(-2px)' }}
|
||||
checked={item.optional}
|
||||
size={'small'}
|
||||
@ -217,8 +226,9 @@ const ThemeCard = (props: Props) => {
|
||||
>
|
||||
<span>{t('Default theme')}</span>
|
||||
<Switch
|
||||
disabled={isDefault}
|
||||
style={{ transform: 'translateY(-2px)' }}
|
||||
checked={item.id === systemSettings?.data?.data?.options?.themeId}
|
||||
checked={isDefault}
|
||||
size={'small'}
|
||||
loading={loading}
|
||||
onChange={handleSwitchDefault}
|
||||
@ -228,25 +238,29 @@ const ThemeCard = (props: Props) => {
|
||||
},
|
||||
],
|
||||
};
|
||||
}, [
|
||||
handleSwitchDefault,
|
||||
handleSwitchOptional,
|
||||
item.id,
|
||||
item.optional,
|
||||
loading,
|
||||
systemSettings?.data?.data?.options?.themeId,
|
||||
t,
|
||||
]);
|
||||
}, [handleSwitchDefault, handleSwitchOptional, isDefault, item.optional, loading, t]);
|
||||
|
||||
const actions = useMemo(() => {
|
||||
return [
|
||||
<EditOutlined key="edit" onClick={handleEdit} />,
|
||||
<DeleteOutlined key="delete" onClick={handleDelete} />,
|
||||
<DeleteOutlined
|
||||
key="delete"
|
||||
style={
|
||||
isDefault
|
||||
? {
|
||||
color: token.colorTextDisabled,
|
||||
cursor: 'not-allowed',
|
||||
}
|
||||
: null
|
||||
}
|
||||
disabled={isDefault}
|
||||
onClick={handleDelete}
|
||||
/>,
|
||||
<Dropdown key="ellipsis" menu={menu}>
|
||||
<EllipsisOutlined />
|
||||
</Dropdown>,
|
||||
];
|
||||
}, [handleDelete, handleEdit, menu]);
|
||||
}, [handleDelete, handleEdit, isDefault, menu, token.colorTextDisabled]);
|
||||
|
||||
const extra = useMemo(() => {
|
||||
if (item.id !== systemSettings?.data?.data?.options?.themeId && !item.optional) {
|
||||
@ -294,7 +308,7 @@ const ThemeCard = (props: Props) => {
|
||||
<Card
|
||||
hoverable
|
||||
extra={extra}
|
||||
title={item.config.name}
|
||||
title={t(item.config.name)}
|
||||
size="small"
|
||||
style={cardStyle}
|
||||
headStyle={{ minHeight: 38 }}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useCurrentUserContext, useSystemSettings } from '@nocobase/client';
|
||||
import { useAPIClient, useCurrentUserContext, useSystemSettings } from '@nocobase/client';
|
||||
import { error } from '@nocobase/utils/client';
|
||||
import { MenuProps, Select } from 'antd';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
@ -21,9 +21,11 @@ function Label() {
|
||||
const { t } = useTranslation();
|
||||
const currentUser = useCurrentUserContext();
|
||||
const systemSettings = useSystemSettings();
|
||||
const { run, error: err, data } = useThemeListContext();
|
||||
const { updateUserThemeSettings } = useUpdateThemeSettings();
|
||||
const { run, error: err, data, refresh } = useThemeListContext();
|
||||
const { updateUserThemeSettings, updateSystemThemeSettings } = useUpdateThemeSettings();
|
||||
const currentThemeId = useCurrentThemeId();
|
||||
const themeId = useCurrentThemeId();
|
||||
const api = useAPIClient();
|
||||
|
||||
const options = useMemo(() => {
|
||||
return data
|
||||
@ -34,7 +36,7 @@ function Label() {
|
||||
value: item.id,
|
||||
};
|
||||
});
|
||||
}, [data]);
|
||||
}, [data, t]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!data) {
|
||||
@ -42,6 +44,25 @@ function Label() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const init = async () => {
|
||||
// 当 themeId 为空时表示插件是第一次被启用
|
||||
if (themeId == null && data) {
|
||||
const firstTheme = data[0];
|
||||
|
||||
try {
|
||||
// 避免并发请求,在本地存储中容易出问题
|
||||
await updateSystemThemeSettings(firstTheme.id);
|
||||
await updateUserThemeSettings(firstTheme.id);
|
||||
} catch (err) {
|
||||
error(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
init();
|
||||
}, [themeId, updateSystemThemeSettings, updateUserThemeSettings, data, api, refresh]);
|
||||
|
||||
if (err) {
|
||||
error(err);
|
||||
return null;
|
||||
|
@ -16,6 +16,12 @@ const locale = {
|
||||
'Edit based on current theme': '基于当前主题进行编辑',
|
||||
'Create a brand new theme': '创建一个全新的主题',
|
||||
|
||||
// 内置主题的名字
|
||||
'Default theme of antd': 'antd 默认主题',
|
||||
Dark: '暗黑',
|
||||
Compact: '紧凑',
|
||||
'Compact dark': '紧凑暗黑',
|
||||
|
||||
// 主题编辑器
|
||||
'Theme Editor': '主题编辑器',
|
||||
Save: '保存',
|
||||
|
41
packages/plugins/theme-editor/src/server/builtinThemes.ts
Normal file
41
packages/plugins/theme-editor/src/server/builtinThemes.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { ThemeItem } from '../types';
|
||||
|
||||
/** antd 默认主题 */
|
||||
export const antd: Omit<ThemeItem, 'id'> = {
|
||||
config: {
|
||||
name: 'Default theme of antd',
|
||||
},
|
||||
optional: true,
|
||||
isBuiltIn: true,
|
||||
};
|
||||
|
||||
export const dark: Omit<ThemeItem, 'id'> = {
|
||||
config: {
|
||||
name: 'Dark',
|
||||
// @ts-ignore
|
||||
algorithm: 'darkAlgorithm',
|
||||
},
|
||||
optional: true,
|
||||
isBuiltIn: true,
|
||||
};
|
||||
|
||||
export const compact: Omit<ThemeItem, 'id'> = {
|
||||
config: {
|
||||
name: 'Compact',
|
||||
// @ts-ignore
|
||||
algorithm: 'compactAlgorithm',
|
||||
},
|
||||
optional: true,
|
||||
isBuiltIn: true,
|
||||
};
|
||||
|
||||
/** 同时包含 `紧凑` 和 `暗黑` 两种模式 */
|
||||
export const compactDark: Omit<ThemeItem, 'id'> = {
|
||||
config: {
|
||||
name: 'Compact dark',
|
||||
// @ts-ignore
|
||||
algorithm: ['compactAlgorithm', 'darkAlgorithm'],
|
||||
},
|
||||
optional: true,
|
||||
isBuiltIn: true,
|
||||
};
|
@ -1,12 +1,16 @@
|
||||
import { Collection } from '@nocobase/database';
|
||||
import { InstallOptions, Plugin } from '@nocobase/server';
|
||||
import { antd, compact, compactDark, dark } from './builtinThemes';
|
||||
|
||||
export class ThemeEditorPlugin extends Plugin {
|
||||
theme: Collection<any, any>;
|
||||
|
||||
afterAdd() {}
|
||||
|
||||
beforeLoad() {}
|
||||
|
||||
async load() {
|
||||
this.db.collection({
|
||||
this.theme = this.db.collection({
|
||||
name: 'themeConfig',
|
||||
fields: [
|
||||
// 主题配置内容,一个 JSON 字符串
|
||||
@ -27,7 +31,13 @@ export class ThemeEditorPlugin extends Plugin {
|
||||
});
|
||||
}
|
||||
|
||||
async install(options?: InstallOptions) {}
|
||||
async install(options?: InstallOptions) {
|
||||
if ((await this.theme.repository.count()) === 0) {
|
||||
await this.theme.repository.create({
|
||||
values: [antd, dark, compact, compactDark],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async afterEnable() {}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user