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:
被雨水过滤的空气-Rain 2023-07-21 10:42:50 +08:00 committed by GitHub
parent a4d8ef4b71
commit eb9ee38a2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 111 additions and 19 deletions

View File

@ -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 }}

View File

@ -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;

View File

@ -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: '保存',

View 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,
};

View File

@ -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() {}