feat: compact theme (#1574)

* feat: compact theme

* fix: theme

* fix: styling

* fix: margin

* feat: improve

* fix: remove console.log
This commit is contained in:
chenos 2023-03-19 23:40:11 +08:00 committed by GitHub
parent 75c338b0f1
commit 4427c70087
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 214 additions and 68 deletions

View File

@ -1,3 +1,4 @@
import '@/theme';
import { Application } from '@nocobase/client'; import { Application } from '@nocobase/client';
export const app = new Application({ export const app = new Application({

View File

@ -0,0 +1,6 @@
if (localStorage.getItem('NOCOBASE_THEME') === 'compact') {
import('antd/dist/antd.compact.css');
} else {
import('antd/dist/antd.css');
}
window.document.documentElement.classList.add('theme-' + (localStorage.getItem('NOCOBASE_THEME') || 'default'));

View File

@ -1,5 +1,5 @@
import React, { createContext, useContext } from 'react';
import { Spin } from 'antd'; import { Spin } from 'antd';
import React, { createContext, useContext } from 'react';
import { useRequest } from '../api-client'; import { useRequest } from '../api-client';
export const CurrentAppInfoContext = createContext(null); export const CurrentAppInfoContext = createContext(null);
@ -8,17 +8,26 @@ export const useCurrentAppInfo = () => {
return useContext(CurrentAppInfoContext); return useContext(CurrentAppInfoContext);
}; };
export const CurrentAppInfoProvider = (props) => { export const CurrentAppInfoProvider = (props) => {
const result = useRequest({ const result = useRequest(
url: 'app:getInfo', {
}); url: 'app:getInfo',
if (result.loading) { },
return <Spin />; {
} onSuccess(data) {
return ( const localTheme = localStorage.getItem('NOCOBASE_THEME');
<CurrentAppInfoContext.Provider if (localTheme !== data?.data?.theme) {
value={result.data} localStorage.setItem('NOCOBASE_THEME', data?.data?.theme);
> window.location.reload();
{props.children} }
</CurrentAppInfoContext.Provider> },
},
); );
const localTheme = localStorage.getItem('NOCOBASE_THEME');
if (localTheme && localTheme !== result?.data?.data?.theme) {
return <Spin />;
}
if (result.loading) {
return <Spin />;
}
return <CurrentAppInfoContext.Provider value={result.data}>{props.children}</CurrentAppInfoContext.Provider>;
}; };

View File

@ -1,4 +1,3 @@
import 'antd/dist/antd.css';
import './global.less'; import './global.less';
export * from './acl'; export * from './acl';
@ -27,3 +26,4 @@ export * from './schema-templates';
export * from './settings-form'; export * from './settings-form';
export * from './system-settings'; export * from './system-settings';
export * from './user'; export * from './user';

View File

@ -680,5 +680,8 @@ export default {
'Use variable': '使用变量', 'Use variable': '使用变量',
'True': '真', 'True': '真',
'False': '假', 'False': '假',
'Prettify': '格式化' 'Prettify': '格式化',
'Theme': '主题',
'Default theme': '默认主题',
'Compact theme': '紧凑主题',
} }

View File

@ -219,7 +219,7 @@ const PluginList = (props) => {
</Tabs> </Tabs>
} }
/> />
<div style={{ margin: 24, display: 'flex', flexFlow: 'row wrap' }}> <div className={'m24'} style={{ margin: 24, display: 'flex', flexFlow: 'row wrap' }}>
{React.createElement( {React.createElement(
{ {
local: LocalPlugins, local: LocalPlugins,
@ -399,7 +399,7 @@ const SettingsCenter = (props) => {
} }
/> />
)} )}
<div style={{ margin: 24 }}> <div className={'m24'} style={{ margin: 24 }}>
{aclPluginTabCheck ? ( {aclPluginTabCheck ? (
component && React.createElement(component) component && React.createElement(component)
) : ( ) : (

View File

@ -2,3 +2,23 @@
color: #000000d9; color: #000000d9;
font-weight:600; font-weight:600;
} }
.ant-table-pagination.ant-pagination {
margin-bottom: 0 !important;
}
.ant-formily-item {
font-size: inherit !important;
}
.theme-compact {
.ant-formily-item {
margin-bottom: 16px;
}
.ant-card {
margin-bottom: 16px !important;
}
.m24 {
margin: 16px !important;
}
}

View File

@ -253,7 +253,7 @@ export const Page = (props) => {
/> />
)} )}
</div> </div>
<div style={{ margin: 24 }}> <div className={'m24'} style={{ margin: 24 }}>
{loading ? ( {loading ? (
<Spin /> <Spin />
) : !disablePageHeader && enablePageTabs ? ( ) : !disablePageHeader && enablePageTabs ? (

View File

@ -81,7 +81,7 @@ export const BlockTemplateDetails = () => {
ghost={false} ghost={false}
title={<EditableTitle filterByTk={key} title={data?.data?.name} />} title={<EditableTitle filterByTk={key} title={data?.data?.name} />}
/> />
<div style={{ margin: 24 }}> <div className={'m24'} style={{ margin: 24 }}>
<SchemaComponentContext.Provider value={{ ...value, designable: true }}> <SchemaComponentContext.Provider value={{ ...value, designable: true }}>
<RemoteSchemaComponent uid={data?.data?.uid} /> <RemoteSchemaComponent uid={data?.data?.uid} />
</SchemaComponentContext.Provider> </SchemaComponentContext.Provider>

View File

@ -11,7 +11,7 @@ export const BlockTemplatePage = () => {
return ( return (
<div> <div>
<AntdPageHeader ghost={false} title={t('Block templates')} /> <AntdPageHeader ghost={false} title={t('Block templates')} />
<div style={{ margin: 24 }}> <div className={'m24'} style={{ margin: 24 }}>
<CollectionManagerProvider collections={[uiSchemaTemplatesCollection]}> <CollectionManagerProvider collections={[uiSchemaTemplatesCollection]}>
<SchemaComponent schema={uiSchemaTemplatesSchema} /> <SchemaComponent schema={uiSchemaTemplatesSchema} />
</CollectionManagerProvider> </CollectionManagerProvider>

View File

@ -10,9 +10,25 @@ export const useSystemSettings = () => {
}; };
export const SystemSettingsProvider: React.FC = (props) => { export const SystemSettingsProvider: React.FC = (props) => {
const result = useRequest({ const result = useRequest(
url: 'systemSettings:get/1?appends=logo', {
}); url: 'systemSettings:get/1?appends=logo',
},
{
onSuccess(data) {
const localTheme = localStorage.getItem('NOCOBASE_THEME');
if (!localTheme) {
const theme = data?.data?.options?.theme;
localStorage.setItem('NOCOBASE_THEME', theme || 'default');
window.location.reload();
}
},
},
);
const localTheme = localStorage.getItem('NOCOBASE_THEME');
if (!localTheme) {
return <Spin />;
}
if (result.loading) { if (result.loading) {
return <Spin />; return <Spin />;
} }

View File

@ -192,6 +192,19 @@ const schema2: ISchema = {
// accept: 'jpg,png' // accept: 'jpg,png'
}, },
}, },
'options.theme': {
type: 'string',
title: '{{t("Theme")}}',
'x-component': 'Select',
'x-component-props': {
// mode: 'multiple',
},
'x-decorator': 'FormItem',
enum: [
{ label: '{{t("Default theme")}}', value: 'default' },
{ label: '{{t("Compact theme")}}', value: 'compact' },
],
},
enabledLanguages: { enabledLanguages: {
type: 'array', type: 'array',
title: '{{t("Enabled languages")}}', title: '{{t("Enabled languages")}}',

View File

@ -9,6 +9,7 @@ import { ChangePassword } from './ChangePassword';
import { EditProfile } from './EditProfile'; import { EditProfile } from './EditProfile';
import { LanguageSettings } from './LanguageSettings'; import { LanguageSettings } from './LanguageSettings';
import { SwitchRole } from './SwitchRole'; import { SwitchRole } from './SwitchRole';
import { ThemeSettings } from './ThemeSettings';
const ApplicationVersion = () => { const ApplicationVersion = () => {
const data = useCurrentAppInfo(); const data = useCurrentAppInfo();
@ -43,6 +44,7 @@ export const CurrentUser = () => {
<ChangePassword /> <ChangePassword />
<SwitchRole /> <SwitchRole />
<LanguageSettings /> <LanguageSettings />
<ThemeSettings />
<Menu.Divider /> <Menu.Divider />
<Menu.Item <Menu.Item
key="signout" key="signout"

View File

@ -1,3 +1,4 @@
import { css } from '@emotion/css';
import { Menu, Select } from 'antd'; import { Menu, Select } from 'antd';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -23,34 +24,42 @@ export const LanguageSettings = () => {
setOpen(true); setOpen(true);
}} }}
> >
{t('Language')}{' '} <div
<Select className={css`
style={{ minWidth: 100 }} display: flex;
bordered={false} align-items: center;
open={open} justify-content: space-between;
onDropdownVisibleChange={(open) => { `}
setOpen(open); >
}} {t('Language')}{' '}
options={Object.keys(locale) <Select
.filter((lang) => enabledLanguages.includes(lang)) style={{ minWidth: 100 }}
.map((lang) => { bordered={false}
return { open={open}
label: locale[lang].label, onDropdownVisibleChange={(open) => {
value: lang, setOpen(open);
}; }}
})} options={Object.keys(locale)
value={i18n.language} .filter((lang) => enabledLanguages.includes(lang))
onChange={async (lang) => { .map((lang) => {
await api.resource('users').updateProfile({ return {
values: { label: locale[lang].label,
appLang: lang, value: lang,
}, };
}); })}
api.auth.setLocale(lang); value={i18n.language}
await i18n.changeLanguage(lang); onChange={async (lang) => {
window.location.reload(); await api.resource('users').updateProfile({
}} values: {
/> appLang: lang,
},
});
api.auth.setLocale(lang);
await i18n.changeLanguage(lang);
window.location.reload();
}}
/>
</div>
</Menu.Item> </Menu.Item>
); );
}; };

View File

@ -1,3 +1,4 @@
import { css } from '@emotion/css';
import { Menu, Select } from 'antd'; import { Menu, Select } from 'antd';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -36,23 +37,31 @@ export const SwitchRole = () => {
} }
return ( return (
<Menu.Item key="role" eventKey={'SwitchRole'}> <Menu.Item key="role" eventKey={'SwitchRole'}>
{t('Switch role')}{' '} <div
<Select className={css`
style={{ minWidth: 100 }} display: flex;
bordered={false} align-items: center;
fieldNames={{ justify-content: space-between;
label: 'title', `}
value: 'name', >
}} {t('Switch role')}{' '}
options={roles} <Select
value={api.auth.role} style={{ minWidth: 100 }}
onChange={async (roleName) => { bordered={false}
api.auth.setRole(roleName); fieldNames={{
await api.resource('users').setDefaultRole({ values: { roleName } }); label: 'title',
history.push('/'); value: 'name',
window.location.reload(); }}
}} options={roles}
/> value={api.auth.role}
onChange={async (roleName) => {
api.auth.setRole(roleName);
await api.resource('users').setDefaultRole({ values: { roleName } });
history.push('/');
window.location.reload();
}}
/>
</div>
</Menu.Item> </Menu.Item>
); );
}; };

View File

@ -0,0 +1,47 @@
import { css } from '@emotion/css';
import { Menu, Select } from 'antd';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useAPIClient } from '../api-client';
import { useCurrentUserContext } from './CurrentUserProvider';
export const ThemeSettings = () => {
const { t } = useTranslation();
const ctx = useCurrentUserContext();
const api = useAPIClient();
return (
<Menu.Item key="theme" eventKey={'theme'}>
<div
className={css`
display: flex;
align-items: center;
justify-content: space-between;
`}
>
{t('Theme')}{' '}
<Select
style={{ minWidth: 100 }}
bordered={false}
defaultValue={localStorage.getItem('NOCOBASE_THEME')}
options={[
{ label: t('Default theme'), value: 'default' },
{ label: t('Compact theme'), value: 'compact' },
]}
onChange={async (value) => {
await api.resource('users').update({
filterByTk: ctx.data.data.id,
values: {
systemSettings: {
...ctx.data.data.systemSettings,
theme: value,
},
},
});
localStorage.setItem('NOCOBASE_THEME', value);
window.location.reload();
}}
/>
</div>
</Menu.Item>
);
};

View File

@ -48,6 +48,7 @@ export class ClientPlugin extends Plugin {
}, },
version: await ctx.app.version.get(), version: await ctx.app.version.get(),
lang, lang,
theme: currentUser?.systemSettings?.theme || systemSetting?.options?.theme || 'default',
}; };
await next(); await next();
}, },

View File

@ -37,5 +37,10 @@ export default defineCollection({
type: 'string', type: 'string',
name: 'appLang', name: 'appLang',
}, },
{
type: 'json',
name: 'options',
defaultValue: {},
},
], ],
}); });

View File

@ -80,5 +80,10 @@ export default {
unique: true, unique: true,
hidden: true, hidden: true,
}, },
{
type: 'json',
name: 'systemSettings',
defaultValue: {},
},
], ],
} as CollectionOptions; } as CollectionOptions;