feat: compact theme (#1574)
* feat: compact theme * fix: theme * fix: styling * fix: margin * feat: improve * fix: remove console.log
This commit is contained in:
		
							parent
							
								
									75c338b0f1
								
							
						
					
					
						commit
						4427c70087
					
				| @ -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({ | ||||||
|  | |||||||
							
								
								
									
										6
									
								
								packages/app/client/src/theme.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								packages/app/client/src/theme.ts
									
									
									
									
									
										Normal 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')); | ||||||
| @ -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>; | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -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'; | ||||||
|  | 
 | ||||||
|  | |||||||
| @ -680,5 +680,8 @@ export default { | |||||||
|   'Use variable': '使用变量', |   'Use variable': '使用变量', | ||||||
|   'True': '真', |   'True': '真', | ||||||
|   'False': '假', |   'False': '假', | ||||||
|   'Prettify': '格式化' |   'Prettify': '格式化', | ||||||
|  |   'Theme': '主题', | ||||||
|  |   'Default theme': '默认主题', | ||||||
|  |   'Compact theme': '紧凑主题', | ||||||
| } | } | ||||||
|  | |||||||
| @ -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) | ||||||
|             ) : ( |             ) : ( | ||||||
|  | |||||||
| @ -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; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -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 ? ( | ||||||
|  | |||||||
| @ -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> | ||||||
|  | |||||||
| @ -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> | ||||||
|  | |||||||
| @ -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 />; | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -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")}}', | ||||||
|  | |||||||
| @ -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" | ||||||
|  | |||||||
| @ -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> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -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> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  | |||||||
							
								
								
									
										47
									
								
								packages/core/client/src/user/ThemeSettings.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								packages/core/client/src/user/ThemeSettings.tsx
									
									
									
									
									
										Normal 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> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
| @ -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(); | ||||||
|         }, |         }, | ||||||
|  | |||||||
| @ -37,5 +37,10 @@ export default defineCollection({ | |||||||
|       type: 'string', |       type: 'string', | ||||||
|       name: 'appLang', |       name: 'appLang', | ||||||
|     }, |     }, | ||||||
|  |     { | ||||||
|  |       type: 'json', | ||||||
|  |       name: 'options', | ||||||
|  |       defaultValue: {}, | ||||||
|  |     }, | ||||||
|   ], |   ], | ||||||
| }); | }); | ||||||
|  | |||||||
| @ -80,5 +80,10 @@ export default { | |||||||
|       unique: true, |       unique: true, | ||||||
|       hidden: true, |       hidden: true, | ||||||
|     }, |     }, | ||||||
|  |     { | ||||||
|  |       type: 'json', | ||||||
|  |       name: 'systemSettings', | ||||||
|  |       defaultValue: {}, | ||||||
|  |     }, | ||||||
|   ], |   ], | ||||||
| } as CollectionOptions; | } as CollectionOptions; | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user