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';
 | 
			
		||||
 | 
			
		||||
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 React, { createContext, useContext } from 'react';
 | 
			
		||||
import { useRequest } from '../api-client';
 | 
			
		||||
 | 
			
		||||
export const CurrentAppInfoContext = createContext(null);
 | 
			
		||||
@ -8,17 +8,26 @@ export const useCurrentAppInfo = () => {
 | 
			
		||||
  return useContext(CurrentAppInfoContext);
 | 
			
		||||
};
 | 
			
		||||
export const CurrentAppInfoProvider = (props) => {
 | 
			
		||||
    const result = useRequest({
 | 
			
		||||
        url: 'app:getInfo',
 | 
			
		||||
      });
 | 
			
		||||
      if (result.loading) {
 | 
			
		||||
        return <Spin />;
 | 
			
		||||
      }
 | 
			
		||||
  return (
 | 
			
		||||
    <CurrentAppInfoContext.Provider
 | 
			
		||||
      value={result.data}
 | 
			
		||||
    >
 | 
			
		||||
      {props.children}
 | 
			
		||||
    </CurrentAppInfoContext.Provider>
 | 
			
		||||
  const result = useRequest(
 | 
			
		||||
    {
 | 
			
		||||
      url: 'app:getInfo',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      onSuccess(data) {
 | 
			
		||||
        const localTheme = localStorage.getItem('NOCOBASE_THEME');
 | 
			
		||||
        if (localTheme !== data?.data?.theme) {
 | 
			
		||||
          localStorage.setItem('NOCOBASE_THEME', data?.data?.theme);
 | 
			
		||||
          window.location.reload();
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  );
 | 
			
		||||
  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';
 | 
			
		||||
 | 
			
		||||
export * from './acl';
 | 
			
		||||
@ -27,3 +26,4 @@ export * from './schema-templates';
 | 
			
		||||
export * from './settings-form';
 | 
			
		||||
export * from './system-settings';
 | 
			
		||||
export * from './user';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -680,5 +680,8 @@ export default {
 | 
			
		||||
  'Use variable': '使用变量',
 | 
			
		||||
  'True': '真',
 | 
			
		||||
  'False': '假',
 | 
			
		||||
  'Prettify': '格式化'
 | 
			
		||||
  'Prettify': '格式化',
 | 
			
		||||
  'Theme': '主题',
 | 
			
		||||
  'Default theme': '默认主题',
 | 
			
		||||
  'Compact theme': '紧凑主题',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -219,7 +219,7 @@ const PluginList = (props) => {
 | 
			
		||||
          </Tabs>
 | 
			
		||||
        }
 | 
			
		||||
      />
 | 
			
		||||
      <div style={{ margin: 24, display: 'flex', flexFlow: 'row wrap' }}>
 | 
			
		||||
      <div className={'m24'} style={{ margin: 24, display: 'flex', flexFlow: 'row wrap' }}>
 | 
			
		||||
        {React.createElement(
 | 
			
		||||
          {
 | 
			
		||||
            local: LocalPlugins,
 | 
			
		||||
@ -399,7 +399,7 @@ const SettingsCenter = (props) => {
 | 
			
		||||
              }
 | 
			
		||||
            />
 | 
			
		||||
          )}
 | 
			
		||||
          <div style={{ margin: 24 }}>
 | 
			
		||||
          <div className={'m24'} style={{ margin: 24 }}>
 | 
			
		||||
            {aclPluginTabCheck ? (
 | 
			
		||||
              component && React.createElement(component)
 | 
			
		||||
            ) : (
 | 
			
		||||
 | 
			
		||||
@ -2,3 +2,23 @@
 | 
			
		||||
  color: #000000d9;
 | 
			
		||||
  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 style={{ margin: 24 }}>
 | 
			
		||||
      <div className={'m24'} style={{ margin: 24 }}>
 | 
			
		||||
        {loading ? (
 | 
			
		||||
          <Spin />
 | 
			
		||||
        ) : !disablePageHeader && enablePageTabs ? (
 | 
			
		||||
 | 
			
		||||
@ -81,7 +81,7 @@ export const BlockTemplateDetails = () => {
 | 
			
		||||
        ghost={false}
 | 
			
		||||
        title={<EditableTitle filterByTk={key} title={data?.data?.name} />}
 | 
			
		||||
      />
 | 
			
		||||
      <div style={{ margin: 24 }}>
 | 
			
		||||
      <div className={'m24'} style={{ margin: 24 }}>
 | 
			
		||||
        <SchemaComponentContext.Provider value={{ ...value, designable: true }}>
 | 
			
		||||
          <RemoteSchemaComponent uid={data?.data?.uid} />
 | 
			
		||||
        </SchemaComponentContext.Provider>
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ export const BlockTemplatePage = () => {
 | 
			
		||||
  return (
 | 
			
		||||
    <div>
 | 
			
		||||
      <AntdPageHeader ghost={false} title={t('Block templates')} />
 | 
			
		||||
      <div style={{ margin: 24 }}>
 | 
			
		||||
      <div className={'m24'} style={{ margin: 24 }}>
 | 
			
		||||
        <CollectionManagerProvider collections={[uiSchemaTemplatesCollection]}>
 | 
			
		||||
          <SchemaComponent schema={uiSchemaTemplatesSchema} />
 | 
			
		||||
        </CollectionManagerProvider>
 | 
			
		||||
 | 
			
		||||
@ -10,9 +10,25 @@ export const useSystemSettings = () => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const SystemSettingsProvider: React.FC = (props) => {
 | 
			
		||||
  const result = useRequest({
 | 
			
		||||
    url: 'systemSettings:get/1?appends=logo',
 | 
			
		||||
  });
 | 
			
		||||
  const result = useRequest(
 | 
			
		||||
    {
 | 
			
		||||
      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) {
 | 
			
		||||
    return <Spin />;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -192,6 +192,19 @@ const schema2: ISchema = {
 | 
			
		||||
            // 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: {
 | 
			
		||||
          type: 'array',
 | 
			
		||||
          title: '{{t("Enabled languages")}}',
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,7 @@ import { ChangePassword } from './ChangePassword';
 | 
			
		||||
import { EditProfile } from './EditProfile';
 | 
			
		||||
import { LanguageSettings } from './LanguageSettings';
 | 
			
		||||
import { SwitchRole } from './SwitchRole';
 | 
			
		||||
import { ThemeSettings } from './ThemeSettings';
 | 
			
		||||
 | 
			
		||||
const ApplicationVersion = () => {
 | 
			
		||||
  const data = useCurrentAppInfo();
 | 
			
		||||
@ -43,6 +44,7 @@ export const CurrentUser = () => {
 | 
			
		||||
              <ChangePassword />
 | 
			
		||||
              <SwitchRole />
 | 
			
		||||
              <LanguageSettings />
 | 
			
		||||
              <ThemeSettings />
 | 
			
		||||
              <Menu.Divider />
 | 
			
		||||
              <Menu.Item
 | 
			
		||||
                key="signout"
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
import { css } from '@emotion/css';
 | 
			
		||||
import { Menu, Select } from 'antd';
 | 
			
		||||
import React, { useState } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
@ -23,34 +24,42 @@ export const LanguageSettings = () => {
 | 
			
		||||
        setOpen(true);
 | 
			
		||||
      }}
 | 
			
		||||
    >
 | 
			
		||||
      {t('Language')}{' '}
 | 
			
		||||
      <Select
 | 
			
		||||
        style={{ minWidth: 100 }}
 | 
			
		||||
        bordered={false}
 | 
			
		||||
        open={open}
 | 
			
		||||
        onDropdownVisibleChange={(open) => {
 | 
			
		||||
          setOpen(open);
 | 
			
		||||
        }}
 | 
			
		||||
        options={Object.keys(locale)
 | 
			
		||||
          .filter((lang) => enabledLanguages.includes(lang))
 | 
			
		||||
          .map((lang) => {
 | 
			
		||||
            return {
 | 
			
		||||
              label: locale[lang].label,
 | 
			
		||||
              value: lang,
 | 
			
		||||
            };
 | 
			
		||||
          })}
 | 
			
		||||
        value={i18n.language}
 | 
			
		||||
        onChange={async (lang) => {
 | 
			
		||||
          await api.resource('users').updateProfile({
 | 
			
		||||
            values: {
 | 
			
		||||
              appLang: lang,
 | 
			
		||||
            },
 | 
			
		||||
          });
 | 
			
		||||
          api.auth.setLocale(lang);
 | 
			
		||||
          await i18n.changeLanguage(lang);
 | 
			
		||||
          window.location.reload();
 | 
			
		||||
        }}
 | 
			
		||||
      />
 | 
			
		||||
      <div
 | 
			
		||||
        className={css`
 | 
			
		||||
          display: flex;
 | 
			
		||||
          align-items: center;
 | 
			
		||||
          justify-content: space-between;
 | 
			
		||||
        `}
 | 
			
		||||
      >
 | 
			
		||||
        {t('Language')}{' '}
 | 
			
		||||
        <Select
 | 
			
		||||
          style={{ minWidth: 100 }}
 | 
			
		||||
          bordered={false}
 | 
			
		||||
          open={open}
 | 
			
		||||
          onDropdownVisibleChange={(open) => {
 | 
			
		||||
            setOpen(open);
 | 
			
		||||
          }}
 | 
			
		||||
          options={Object.keys(locale)
 | 
			
		||||
            .filter((lang) => enabledLanguages.includes(lang))
 | 
			
		||||
            .map((lang) => {
 | 
			
		||||
              return {
 | 
			
		||||
                label: locale[lang].label,
 | 
			
		||||
                value: lang,
 | 
			
		||||
              };
 | 
			
		||||
            })}
 | 
			
		||||
          value={i18n.language}
 | 
			
		||||
          onChange={async (lang) => {
 | 
			
		||||
            await api.resource('users').updateProfile({
 | 
			
		||||
              values: {
 | 
			
		||||
                appLang: lang,
 | 
			
		||||
              },
 | 
			
		||||
            });
 | 
			
		||||
            api.auth.setLocale(lang);
 | 
			
		||||
            await i18n.changeLanguage(lang);
 | 
			
		||||
            window.location.reload();
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
    </Menu.Item>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
import { css } from '@emotion/css';
 | 
			
		||||
import { Menu, Select } from 'antd';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
@ -36,23 +37,31 @@ export const SwitchRole = () => {
 | 
			
		||||
  }
 | 
			
		||||
  return (
 | 
			
		||||
    <Menu.Item key="role" eventKey={'SwitchRole'}>
 | 
			
		||||
      {t('Switch role')}{' '}
 | 
			
		||||
      <Select
 | 
			
		||||
        style={{ minWidth: 100 }}
 | 
			
		||||
        bordered={false}
 | 
			
		||||
        fieldNames={{
 | 
			
		||||
          label: 'title',
 | 
			
		||||
          value: 'name',
 | 
			
		||||
        }}
 | 
			
		||||
        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
 | 
			
		||||
        className={css`
 | 
			
		||||
          display: flex;
 | 
			
		||||
          align-items: center;
 | 
			
		||||
          justify-content: space-between;
 | 
			
		||||
        `}
 | 
			
		||||
      >
 | 
			
		||||
        {t('Switch role')}{' '}
 | 
			
		||||
        <Select
 | 
			
		||||
          style={{ minWidth: 100 }}
 | 
			
		||||
          bordered={false}
 | 
			
		||||
          fieldNames={{
 | 
			
		||||
            label: 'title',
 | 
			
		||||
            value: 'name',
 | 
			
		||||
          }}
 | 
			
		||||
          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>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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(),
 | 
			
		||||
            lang,
 | 
			
		||||
            theme: currentUser?.systemSettings?.theme || systemSetting?.options?.theme || 'default',
 | 
			
		||||
          };
 | 
			
		||||
          await next();
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
@ -37,5 +37,10 @@ export default defineCollection({
 | 
			
		||||
      type: 'string',
 | 
			
		||||
      name: 'appLang',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      type: 'json',
 | 
			
		||||
      name: 'options',
 | 
			
		||||
      defaultValue: {},
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -80,5 +80,10 @@ export default {
 | 
			
		||||
      unique: true,
 | 
			
		||||
      hidden: true,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      type: 'json',
 | 
			
		||||
      name: 'systemSettings',
 | 
			
		||||
      defaultValue: {},
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
} as CollectionOptions;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user