From 8c1738db8380396f0eea470f5e3da3a05d799b6d Mon Sep 17 00:00:00 2001 From: YANG QIA <2013xile@gmail.com> Date: Tue, 12 Dec 2023 14:09:30 +0800 Subject: [PATCH] fix(theme-editor): "No permission" error when updating default theme of system (#3171) * fix: fix T-2703 * fix: bug * fix: migration * chore: switch type to radio * fix: collection.sync --------- Co-authored-by: chenos --- .../plugin-theme-editor/package.json | 3 +- .../src/client/components/InitializeTheme.tsx | 71 ++++++++++--------- .../src/client/components/ThemeCard.tsx | 59 ++++++--------- .../src/client/hooks/useThemeSettings.tsx | 29 ++------ .../client/hooks/useUpdateThemeSettings.tsx | 30 +------- .../src/server/builtinThemes.ts | 4 ++ .../20231210132610-add-default-field.ts | 46 ++++++++++++ .../plugin-theme-editor/src/server/plugin.ts | 23 +++--- .../plugin-theme-editor/src/types.ts | 1 + 9 files changed, 132 insertions(+), 134 deletions(-) create mode 100644 packages/plugins/@nocobase/plugin-theme-editor/src/server/migrations/20231210132610-add-default-field.ts diff --git a/packages/plugins/@nocobase/plugin-theme-editor/package.json b/packages/plugins/@nocobase/plugin-theme-editor/package.json index f02d2533a5..cf3aa70ac6 100644 --- a/packages/plugins/@nocobase/plugin-theme-editor/package.json +++ b/packages/plugins/@nocobase/plugin-theme-editor/package.json @@ -28,6 +28,7 @@ "@nocobase/database": "0.x", "@nocobase/server": "0.x", "@nocobase/test": "0.x", - "@nocobase/utils": "0.x" + "@nocobase/utils": "0.x", + "@nocobase/actions": "0.x" } } diff --git a/packages/plugins/@nocobase/plugin-theme-editor/src/client/components/InitializeTheme.tsx b/packages/plugins/@nocobase/plugin-theme-editor/src/client/components/InitializeTheme.tsx index 0a058173a3..b8b503e524 100644 --- a/packages/plugins/@nocobase/plugin-theme-editor/src/client/components/InitializeTheme.tsx +++ b/packages/plugins/@nocobase/plugin-theme-editor/src/client/components/InitializeTheme.tsx @@ -1,25 +1,28 @@ -import { defaultTheme, useAPIClient, useCurrentUserContext, useGlobalTheme, useSystemSettings } from '@nocobase/client'; +import { defaultTheme as presetTheme, useAPIClient, useCurrentUserContext, useGlobalTheme } from '@nocobase/client'; import { error } from '@nocobase/utils/client'; import { Spin } from 'antd'; -import React, { useEffect, useRef } from 'react'; +import React, { useEffect, useMemo, useRef } from 'react'; import { changeAlgorithmFromFunctionToString } from '../utils/changeAlgorithmFromFunctionToString'; import { changeAlgorithmFromStringToFunction } from '../utils/changeAlgorithmFromStringToFunction'; import { useThemeListContext } from './ThemeListProvider'; -const CurrentThemeIdContext = React.createContext(null); +const ThemeIdContext = React.createContext<{ + currentThemeId: number; + defaultThemeId: number; +}>({} as any); -export const useCurrentThemeId = () => { - return React.useContext(CurrentThemeIdContext); +export const useThemeId = () => { + return React.useContext(ThemeIdContext); }; /** * 用于在页面加载时初始化主题 */ const InitializeTheme: React.FC = ({ children }) => { - const systemSettings = useSystemSettings(); const currentUser = useCurrentUserContext(); const { setTheme } = useGlobalTheme(); const { run, data, loading } = useThemeListContext(); + const defaultTheme = useMemo(() => data?.find((item) => item.default), [data]); const themeId = useRef(null); const api = useAPIClient(); @@ -39,40 +42,44 @@ const InitializeTheme: React.FC = ({ children }) => { return run(); } - themeId.current = - // 这里不要使用 `===` 操作符 - currentUser?.data?.data?.systemSettings?.themeId == null - ? systemSettings?.data?.data?.options?.themeId - : currentUser?.data?.data?.systemSettings?.themeId; - - // 这里不要使用 `!==` 操作符 - if (themeId.current != null) { - const theme = data?.find((item) => item.id === themeId.current); - if (theme) { - setTheme(theme.config); - api.auth.setOption( - 'theme', - JSON.stringify(Object.assign({ ...theme }, { config: changeAlgorithmFromFunctionToString(theme.config) })), - ); - } + const currentThemeId = currentUser?.data?.data?.systemSettings?.themeId; + let theme: any; + if (currentThemeId !== null && currentThemeId !== undefined) { + // Use the theme from the current user's system settings + theme = data.find((item) => item.id === currentThemeId); + } + if (!theme) { + // Use the default theme if there is not an available theme in user's system settings + theme = defaultTheme; + } + if (theme) { + themeId.current = theme.id; + setTheme(theme.config); + api.auth.setOption( + 'theme', + JSON.stringify(Object.assign({ ...theme }, { config: changeAlgorithmFromFunctionToString(theme.config) })), + ); } else { - setTheme(defaultTheme); + // Use the preset theme if the theme is not found + setTheme(presetTheme); api.auth.setOption('theme', null); } - }, [ - api.auth, - currentUser?.data?.data?.systemSettings?.themeId, - data, - run, - setTheme, - systemSettings?.data?.data?.options?.themeId, - ]); + }, [api.auth, currentUser?.data?.data?.systemSettings?.themeId, data, run, setTheme, defaultTheme]); if (loading && !data) { return ; } - return {children}; + return ( + + {children} + + ); }; InitializeTheme.displayName = 'InitializeTheme'; diff --git a/packages/plugins/@nocobase/plugin-theme-editor/src/client/components/ThemeCard.tsx b/packages/plugins/@nocobase/plugin-theme-editor/src/client/components/ThemeCard.tsx index 655a758e9a..45607d1f55 100644 --- a/packages/plugins/@nocobase/plugin-theme-editor/src/client/components/ThemeCard.tsx +++ b/packages/plugins/@nocobase/plugin-theme-editor/src/client/components/ThemeCard.tsx @@ -1,12 +1,5 @@ import { DeleteOutlined, EditOutlined, EllipsisOutlined } from '@ant-design/icons'; -import { - compatOldTheme, - useAPIClient, - useCurrentUserContext, - useGlobalTheme, - useSystemSettings, - useToken, -} from '@nocobase/client'; +import { compatOldTheme, useAPIClient, useCurrentUserContext, useGlobalTheme, useToken } from '@nocobase/client'; import { error } from '@nocobase/utils/client'; import { App, Card, ConfigProvider, Dropdown, Space, Switch, Tag, message } from 'antd'; import React, { useCallback, useMemo } from 'react'; @@ -14,7 +7,7 @@ import { ThemeConfig, ThemeItem } from '../../types'; import { Primary } from '../antd-token-previewer'; import { useUpdateThemeSettings } from '../hooks/useUpdateThemeSettings'; import { useTranslation } from '../locale'; -import { useCurrentThemeId } from './InitializeTheme'; +import { useThemeId } from './InitializeTheme'; import { useThemeEditorContext } from './ThemeEditorProvider'; enum HandleTypes { @@ -69,17 +62,16 @@ const ThemeCard = (props: Props) => { } = useGlobalTheme(); const { setOpen } = useThemeEditorContext(); const currentUser = useCurrentUserContext(); - const systemSettings = useSystemSettings(); const { item, style = {}, onChange } = props; const api = useAPIClient(); - const { updateUserThemeSettings, updateSystemThemeSettings } = useUpdateThemeSettings(); + const { updateUserThemeSettings } = useUpdateThemeSettings(); const { modal } = App.useApp(); const [loading, setLoading] = React.useState(false); - const currentThemeId = useCurrentThemeId(); + const { currentThemeId, defaultThemeId } = useThemeId(); const { t } = useTranslation(); const { token } = useToken(); - const isDefault = item.id === systemSettings?.data?.data?.options?.themeId; + const isDefault = item.id === defaultThemeId; const handleDelete = useCallback(() => { if (isDefault) { @@ -97,9 +89,6 @@ const ThemeCard = (props: Props) => { if (item.id === currentUser?.data?.data?.systemSettings?.themeId) { updateUserThemeSettings(null); } - if (item.id === systemSettings?.data?.data?.options?.themeId) { - updateSystemThemeSettings(null); - } message.success(t('Deleted successfully')); onChange?.({ type: HandleTypes.delete, item }); }, @@ -111,9 +100,7 @@ const ThemeCard = (props: Props) => { item, modal, onChange, - systemSettings?.data?.data?.options?.themeId, t, - updateSystemThemeSettings, updateUserThemeSettings, ]); const handleSwitchOptional = useCallback( @@ -127,12 +114,11 @@ const ThemeCard = (props: Props) => { method: 'post', data: { optional: checked, + default: false, }, }), // 如果用户把当前设置的主题设置为不可选,那么就需要把当前设置的主题设置为默认主题 - item.id === currentThemeId && updateUserThemeSettings(null), - // 系统默认主题应该始终是可选的,所以当用户设置为不可选时,应该也同时把系统默认主题设置为空 - item.id === systemSettings?.data?.data?.options?.themeId && updateSystemThemeSettings(null), + item.id === currentUser?.data?.data?.systemSettings?.themeId && updateUserThemeSettings(null), ]); } else { await api.request({ @@ -150,16 +136,7 @@ const ThemeCard = (props: Props) => { message.success(t('Updated successfully')); onChange?.({ type: HandleTypes.optional, item }); }, - [ - api, - currentThemeId, - item, - onChange, - systemSettings?.data?.data?.options?.themeId, - t, - updateSystemThemeSettings, - updateUserThemeSettings, - ], + [api, currentUser?.data?.data?.systemSettings?.themeId, item, onChange, t, updateUserThemeSettings], ); const handleSwitchDefault = useCallback( async (checked: boolean) => { @@ -167,18 +144,24 @@ const ThemeCard = (props: Props) => { try { if (checked) { await Promise.all([ - updateSystemThemeSettings(item.id), // 用户在设置该主题为默认主题时,肯定也希望该主题可被用户选择 api.request({ url: `themeConfig:update/${item.id}`, method: 'post', data: { optional: true, + default: true, }, }), ]); } else { - await updateSystemThemeSettings(null); + await api.request({ + url: `themeConfig:update/${item.id}`, + method: 'post', + data: { + default: false, + }, + }); } } catch (err) { error(err); @@ -187,7 +170,7 @@ const ThemeCard = (props: Props) => { message.success(t('Updated successfully')); onChange?.({ type: HandleTypes.optional, item }); }, - [api, item, onChange, t, updateSystemThemeSettings], + [api, item, onChange, t], ); const handleEdit = useCallback(() => { @@ -269,13 +252,13 @@ const ThemeCard = (props: Props) => { }, [handleDelete, handleEdit, isDefault, menu, token.colorTextDisabled]); const extra = useMemo(() => { - if (item.id !== systemSettings?.data?.data?.options?.themeId && !item.optional) { + if (item.id !== defaultThemeId && !item.optional) { return null; } const text = item.id === currentThemeId ? t('Current') - : item.id === systemSettings?.data?.data?.options?.themeId + : item.id === defaultThemeId ? t('Default') : item.optional ? t('Optional') @@ -283,7 +266,7 @@ const ThemeCard = (props: Props) => { const color = item.id === currentThemeId ? 'processing' - : item.id === systemSettings?.data?.data?.options?.themeId + : item.id === defaultThemeId ? 'default' : item.optional ? 'success' @@ -294,7 +277,7 @@ const ThemeCard = (props: Props) => { {text} ); - }, [currentThemeId, item.id, item.optional, systemSettings?.data?.data?.options?.themeId, t]); + }, [currentThemeId, defaultThemeId, item.id, item.optional, t]); const cardStyle = useMemo(() => { if (getCurrentEditingTheme()?.id === item.id) { diff --git a/packages/plugins/@nocobase/plugin-theme-editor/src/client/hooks/useThemeSettings.tsx b/packages/plugins/@nocobase/plugin-theme-editor/src/client/hooks/useThemeSettings.tsx index 034d5324c4..7f565ab7a2 100644 --- a/packages/plugins/@nocobase/plugin-theme-editor/src/client/hooks/useThemeSettings.tsx +++ b/packages/plugins/@nocobase/plugin-theme-editor/src/client/hooks/useThemeSettings.tsx @@ -2,7 +2,7 @@ import { SelectWithTitle, useAPIClient, useCurrentUserContext, useSystemSettings import { error } from '@nocobase/utils/client'; import { MenuProps } from 'antd'; import React, { useEffect, useMemo } from 'react'; -import { useCurrentThemeId } from '../components/InitializeTheme'; +import { useThemeId } from '../components/InitializeTheme'; import { useThemeListContext } from '../components/ThemeListProvider'; import { useTranslation } from '../locale'; import { useUpdateThemeSettings } from './useUpdateThemeSettings'; @@ -21,11 +21,9 @@ function Label() { const { t } = useTranslation(); const currentUser = useCurrentUserContext(); const systemSettings = useSystemSettings(); - const { run, error: err, data, refresh } = useThemeListContext(); - const { updateUserThemeSettings, updateSystemThemeSettings } = useUpdateThemeSettings(); - const currentThemeId = useCurrentThemeId(); - const themeId = useCurrentThemeId(); - const api = useAPIClient(); + const { run, error: err, data } = useThemeListContext(); + const { updateUserThemeSettings } = useUpdateThemeSettings(); + const { currentThemeId } = useThemeId(); const options = useMemo(() => { return data @@ -44,25 +42,6 @@ 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; diff --git a/packages/plugins/@nocobase/plugin-theme-editor/src/client/hooks/useUpdateThemeSettings.tsx b/packages/plugins/@nocobase/plugin-theme-editor/src/client/hooks/useUpdateThemeSettings.tsx index 3fa6d94ff2..d4113928a4 100644 --- a/packages/plugins/@nocobase/plugin-theme-editor/src/client/hooks/useUpdateThemeSettings.tsx +++ b/packages/plugins/@nocobase/plugin-theme-editor/src/client/hooks/useUpdateThemeSettings.tsx @@ -37,33 +37,5 @@ export function useUpdateThemeSettings() { [api, currentUser], ); - const updateSystemThemeSettings = useCallback( - async (themeId: number | null) => { - if (themeId === systemSettings.data.data.options?.themeId) { - return; - } - await api.request({ - url: 'systemSettings:update/1', - method: 'post', - data: { - options: { - ...(systemSettings.data.data.options || {}), - themeId, - }, - }, - }); - systemSettings.mutate({ - data: { - ...systemSettings.data.data, - options: { - ...(systemSettings.data.data.options || {}), - themeId, - }, - }, - }); - }, - [api, systemSettings], - ); - - return { updateUserThemeSettings, updateSystemThemeSettings }; + return { updateUserThemeSettings }; } diff --git a/packages/plugins/@nocobase/plugin-theme-editor/src/server/builtinThemes.ts b/packages/plugins/@nocobase/plugin-theme-editor/src/server/builtinThemes.ts index 054f3fcb44..890aeec69c 100644 --- a/packages/plugins/@nocobase/plugin-theme-editor/src/server/builtinThemes.ts +++ b/packages/plugins/@nocobase/plugin-theme-editor/src/server/builtinThemes.ts @@ -8,6 +8,7 @@ export const defaultTheme: Omit = { optional: true, isBuiltIn: true, uid: 'default', + default: true, }; export const dark: Omit = { @@ -19,6 +20,7 @@ export const dark: Omit = { optional: true, isBuiltIn: true, uid: 'dark', + default: false, }; export const compact: Omit = { @@ -30,6 +32,7 @@ export const compact: Omit = { optional: true, isBuiltIn: true, uid: 'compact', + default: false, }; /** 同时包含 `紧凑` 和 `暗黑` 两种模式 */ @@ -42,4 +45,5 @@ export const compactDark: Omit = { optional: true, isBuiltIn: true, uid: 'compact_dark', + default: false, }; diff --git a/packages/plugins/@nocobase/plugin-theme-editor/src/server/migrations/20231210132610-add-default-field.ts b/packages/plugins/@nocobase/plugin-theme-editor/src/server/migrations/20231210132610-add-default-field.ts new file mode 100644 index 0000000000..33ad171fb4 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-theme-editor/src/server/migrations/20231210132610-add-default-field.ts @@ -0,0 +1,46 @@ +import { Migration } from '@nocobase/server'; + +export default class ThemeEditorMigration extends Migration { + async up() { + const result = await this.app.version.satisfies('<0.17.0-alpha.5'); + + if (!result) { + return; + } + + const repository = this.db.getRepository('themeConfig'); + if (!repository) { + return; + } + + await repository.collection.sync(); + + const systemSettings = await this.db.getRepository('systemSettings').findOne(); + const defaultThemeId = systemSettings.options?.themeId; + if (!defaultThemeId) { + return; + } + + await this.db.sequelize.transaction(async (t) => { + await repository.update({ + values: { + default: false, + }, + filter: { + default: true, + }, + transaction: t, + }); + await repository.update({ + values: { + default: true, + optional: true, + }, + filterByTk: defaultThemeId, + transaction: t, + }); + }); + } + + async down() {} +} diff --git a/packages/plugins/@nocobase/plugin-theme-editor/src/server/plugin.ts b/packages/plugins/@nocobase/plugin-theme-editor/src/server/plugin.ts index 3a65e1bb6f..4b5ab6335d 100644 --- a/packages/plugins/@nocobase/plugin-theme-editor/src/server/plugin.ts +++ b/packages/plugins/@nocobase/plugin-theme-editor/src/server/plugin.ts @@ -7,15 +7,7 @@ export class ThemeEditorPlugin extends Plugin { afterAdd() {} - async beforeLoad() { - this.db.addMigrations({ - namespace: 'theme-editor', - directory: resolve(__dirname, './migrations'), - context: { - plugin: this, - }, - }); - } + async beforeLoad() {} async load() { this.db.collection({ @@ -39,8 +31,21 @@ export class ThemeEditorPlugin extends Plugin { type: 'uid', name: 'uid', }, + { + type: 'radio', + name: 'default', + defaultValue: false, + }, ], }); + this.db.addMigrations({ + namespace: 'theme-editor', + directory: resolve(__dirname, './migrations'), + context: { + plugin: this, + }, + }); + this.app.acl.allow('themeConfig', 'list', 'public'); this.app.acl.registerSnippet({ name: `pm.${this.name}.themeConfig`, diff --git a/packages/plugins/@nocobase/plugin-theme-editor/src/types.ts b/packages/plugins/@nocobase/plugin-theme-editor/src/types.ts index 8bd44ab77b..8d69c9243b 100644 --- a/packages/plugins/@nocobase/plugin-theme-editor/src/types.ts +++ b/packages/plugins/@nocobase/plugin-theme-editor/src/types.ts @@ -10,6 +10,7 @@ export interface ThemeItem { optional: boolean; isBuiltIn?: boolean; uid?: string; + default?: boolean; // 当前系统默认主题 } export type Theme = {