feat: adjust of menu in upper right corner of page (#3548)

* feat: adjust of menu in upper right corner of page

* fix: uncomment

* chore: change License link

* chore: optimize text color

* chore: optimize text color

* test: fix e2e
This commit is contained in:
Zeke Zhang 2024-02-23 00:08:03 +08:00 committed by GitHub
parent 83e2d64da9
commit d02d24b484
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 194 additions and 41 deletions

View File

@ -782,5 +782,8 @@
"Automatically drop objects that depend on the collection (such as views), and in turn all objects that depend on those objects": "Automatically drop objects that depend on the collection (such as views), and in turn all objects that depend on those objects", "Automatically drop objects that depend on the collection (such as views), and in turn all objects that depend on those objects": "Automatically drop objects that depend on the collection (such as views), and in turn all objects that depend on those objects",
"Sign in with another account": "Sign in with another account", "Sign in with another account": "Sign in with another account",
"Return to the main application": "Return to the main application", "Return to the main application": "Return to the main application",
"Permission deined": "Permission denied" "Permission deined": "Permission denied",
"Home page": "Home page",
"User manual": "User manual",
"License": "License"
} }

View File

@ -731,5 +731,8 @@
"Enable link": "Activar enlace", "Enable link": "Activar enlace",
"Data template": "Plantilla de datos", "Data template": "Plantilla de datos",
"Not found": "No encontrado", "Not found": "No encontrado",
"Add": "Añadir" "Add": "Añadir",
"Home page": "Página de inicio",
"User manual": "Manual de usuario",
"License": "Licencia"
} }

View File

@ -751,5 +751,8 @@
"Linkage with form fields": "Lien avec les champs de formulaire", "Linkage with form fields": "Lien avec les champs de formulaire",
"Allow add new, update and delete actions": "Autoriser les actions d'ajout, de mise à jour et de suppression", "Allow add new, update and delete actions": "Autoriser les actions d'ajout, de mise à jour et de suppression",
"Allow add new":"Autoriser les ajouts", "Allow add new":"Autoriser les ajouts",
"Allow selection of existing records":"Permet de sélectionner des données existantes" "Allow selection of existing records":"Permet de sélectionner des données existantes",
"Home page": "Page d'accueil",
"User manual": "Manuel de l'utilisateur",
"License": "Licence"
} }

View File

@ -670,5 +670,8 @@
"Plugin loading failed. Please check the server logs.": "プラグインのロードに失敗しました。サーバーログを確認してください。", "Plugin loading failed. Please check the server logs.": "プラグインのロードに失敗しました。サーバーログを確認してください。",
"Automatically drop objects that depend on the collection (such as views), and in turn all objects that depend on those objects": "テーブルに依存するオブジェクト、およびそれらに依存するオブジェクトを自動的に削除する", "Automatically drop objects that depend on the collection (such as views), and in turn all objects that depend on those objects": "テーブルに依存するオブジェクト、およびそれらに依存するオブジェクトを自動的に削除する",
"Allow add new":"新規作成を許可", "Allow add new":"新規作成を許可",
"Allow selection of existing records":"既存のデータの選択を許可" "Allow selection of existing records":"既存のデータの選択を許可",
"Home page": "ホームページ",
"User manual": "ユーザーマニュアル",
"License": "ライセンス"
} }

View File

@ -842,5 +842,8 @@
"Return to the main application": "메인 애플리케이션으로 돌아가기", "Return to the main application": "메인 애플리케이션으로 돌아가기",
"Permission denied": "허가 거부", "Permission denied": "허가 거부",
"Allow add new": "신규 추가 허용", "Allow add new": "신규 추가 허용",
"Allow selection of existing records": "기존 레코드 선택 허용" "Allow selection of existing records": "기존 레코드 선택 허용",
"Home page": "홈페이지",
"User manual": "사용자 매뉴얼",
"License": "라이선스"
} }

View File

@ -711,5 +711,8 @@
"Add": "Adicionar", "Add": "Adicionar",
"Automatically drop objects that depend on the collection (such as views), and in turn all objects that depend on those objects": "Excluir automaticamente objetos que dependem desta tabela, bem como objetos que dependem desses objetos", "Automatically drop objects that depend on the collection (such as views), and in turn all objects that depend on those objects": "Excluir automaticamente objetos que dependem desta tabela, bem como objetos que dependem desses objetos",
"Allow add new":"Permitir novas adições", "Allow add new":"Permitir novas adições",
"Allow selection of existing records":"Permitir a selecção dos registos existentes" "Allow selection of existing records":"Permitir a selecção dos registos existentes",
"Home page": "Página inicial",
"User manual": "Manual do usuário",
"License": "Licença"
} }

View File

@ -546,5 +546,8 @@
"Not enabled": "Не включено", "Not enabled": "Не включено",
"Search plugin": "Поиск плагина", "Search plugin": "Поиск плагина",
"Author": "Автор", "Author": "Автор",
"Plugin loading failed. Please check the server logs.": "Не удалось загрузить плагин. Пожалуйста, проверьте журналы сервера." "Plugin loading failed. Please check the server logs.": "Не удалось загрузить плагин. Пожалуйста, проверьте журналы сервера.",
"Home page": "Домашняя страница",
"User manual": "Руководство пользователя",
"License": "Лицензия"
} }

View File

@ -543,5 +543,8 @@
"Not enabled": "Etkin değil", "Not enabled": "Etkin değil",
"Search plugin": "Eklenti ara", "Search plugin": "Eklenti ara",
"Author": "Yazar", "Author": "Yazar",
"Plugin loading failed. Please check the server logs.": "Eklenti yüklenemedi. Lütfen sunucu günlüklerini kontrol edin." "Plugin loading failed. Please check the server logs.": "Eklenti yüklenemedi. Lütfen sunucu günlüklerini kontrol edin.",
"Home page": "Anasayfa",
"User manual": "Kullanıcı kılavuzu",
"License": "Lisans"
} }

View File

@ -751,5 +751,8 @@
"Feedback": "Зворотній зв'язок", "Feedback": "Зворотній зв'язок",
"Try again": "Спробуйте ще раз", "Try again": "Спробуйте ще раз",
"Data template": "Шаблон даних", "Data template": "Шаблон даних",
"Template fields have been removed and need to be reconfigured": "Поля шаблону були видалені і потребують повторної конфігурації" "Template fields have been removed and need to be reconfigured": "Поля шаблону були видалені і потребують повторної конфігурації",
"Home page": "Домашня сторінка",
"User manual": "Посібник користувача",
"License": "Ліцензія"
} }

View File

@ -857,5 +857,8 @@
"Collections": "数据表", "Collections": "数据表",
"Collection fields": "数据表字段", "Collection fields": "数据表字段",
"Authentication": "用户认证", "Authentication": "用户认证",
"Logging and monitoring": "日志与监控" "Logging and monitoring": "日志与监控",
"Home page": "主页",
"User manual": "用户手册",
"License": "许可证"
} }

View File

@ -842,5 +842,8 @@
"Return to the main application": "返回主App", "Return to the main application": "返回主App",
"Permission denied": "沒有權限", "Permission denied": "沒有權限",
"Allow add new":"允許新增", "Allow add new":"允許新增",
"Allow selection of existing records":"允許選擇已有資料" "Allow selection of existing records":"允許選擇已有資料",
"Home page": "主頁",
"User manual": "使用手冊",
"License": "許可證"
} }

View File

@ -1,18 +1,17 @@
import React from 'react'; import React from 'react';
import { Plugin } from '../application/Plugin';
import { PluginManagerLink, SettingsCenterDropdown } from './PluginManagerLink';
import { AdminSettingsLayout } from './PluginSetting';
import { PluginManager } from './PluginManager';
import { ACLPane } from '../acl/ACLShortcut'; import { ACLPane } from '../acl/ACLShortcut';
import { ADMIN_SETTINGS_PATH } from '../application';
import { Plugin } from '../application/Plugin';
import { CollectionManagerPane } from '../collection-manager'; import { CollectionManagerPane } from '../collection-manager';
import { BlockTemplatesPane } from '../schema-templates'; import { BlockTemplatesPane } from '../schema-templates';
import { SystemSettingsPane } from '../system-settings'; import { SystemSettingsPane } from '../system-settings';
import { ADMIN_SETTINGS_PATH } from '../application'; import { PluginManager } from './PluginManager';
import { Outlet } from 'react-router-dom'; import { PluginManagerLink, SettingsCenterDropdown } from './PluginManagerLink';
import { AdminSettingsLayout } from './PluginSetting';
export * from './PluginManager';
export * from './PluginManagerLink'; export * from './PluginManagerLink';
export * from './PluginSetting'; export * from './PluginSetting';
export * from './PluginManager';
export class PMPlugin extends Plugin { export class PMPlugin extends Plugin {
async load() { async load() {

View File

@ -1,6 +1,6 @@
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import { useSessionStorageState } from 'ahooks'; import { useSessionStorageState } from 'ahooks';
import { App, ConfigProvider, Layout } from 'antd'; import { App, ConfigProvider, Divider, Layout } from 'antd';
import { createGlobalStyle } from 'antd-style'; import { createGlobalStyle } from 'antd-style';
import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react'; import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { Link, Outlet, useMatch, useNavigate, useParams } from 'react-router-dom'; import { Link, Outlet, useMatch, useNavigate, useParams } from 'react-router-dom';
@ -26,6 +26,7 @@ import {
import { Plugin } from '../../../application/Plugin'; import { Plugin } from '../../../application/Plugin';
import { useAppSpin } from '../../../application/hooks/useAppSpin'; import { useAppSpin } from '../../../application/hooks/useAppSpin';
import { useCollectionManager } from '../../../collection-manager'; import { useCollectionManager } from '../../../collection-manager';
import { Help } from '../../../user/Help';
import { VariablesProvider } from '../../../variables'; import { VariablesProvider } from '../../../variables';
const filterByACL = (schema, options) => { const filterByACL = (schema, options) => {
@ -363,6 +364,16 @@ export const InternalAdminLayout = (props: any) => {
`} `}
> >
<PinnedPluginList /> <PinnedPluginList />
<ConfigProvider
theme={{
token: {
colorSplit: 'rgba(255, 255, 255, 0.1)',
},
}}
>
<Divider type="vertical" />
</ConfigProvider>
<Help />
<CurrentUser /> <CurrentUser />
</div> </div>
</div> </div>

View File

@ -1,3 +1,4 @@
import { UserOutlined } from '@ant-design/icons';
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import { error } from '@nocobase/utils/client'; import { error } from '@nocobase/utils/client';
import { App, Dropdown, Menu, MenuProps } from 'antd'; import { App, Dropdown, Menu, MenuProps } from 'antd';
@ -5,28 +6,27 @@ import React, { createContext, useCallback, useMemo as useEffect, useState } fro
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useACLRoleContext, useAPIClient, useCurrentUserContext, useToken } from '..'; import { useACLRoleContext, useAPIClient, useCurrentUserContext, useToken } from '..';
import { useCurrentAppInfo } from '../appInfo/CurrentAppInfoProvider';
import { useChangePassword } from './ChangePassword'; import { useChangePassword } from './ChangePassword';
import { useCurrentUserSettingsMenu } from './CurrentUserSettingsMenuProvider'; import { useCurrentUserSettingsMenu } from './CurrentUserSettingsMenuProvider';
import { useEditProfile } from './EditProfile'; import { useEditProfile } from './EditProfile';
import { useLanguageSettings } from './LanguageSettings'; import { useLanguageSettings } from './LanguageSettings';
import { useSwitchRole } from './SwitchRole'; import { useSwitchRole } from './SwitchRole';
const useApplicationVersion = () => {
const data = useCurrentAppInfo(); const useNickname = () => {
const { data } = useCurrentUserContext();
const { token } = useToken();
return useEffect(() => { return useEffect(() => {
return { return {
key: 'version', key: 'nickname',
disabled: true, disabled: true,
label: ( label: (
<span <span aria-disabled="false" style={{ cursor: 'text', color: token.colorTextDescription }}>
role="button" {data?.data?.nickname || data?.data?.username || data?.data?.email}
aria-label="app-version" </span>
aria-disabled="false"
style={{ cursor: 'text' }}
>{`Version ${data?.data?.version}`}</span>
), ),
}; };
}, [data?.data?.version]); }, [data?.data?.email, data?.data?.nickname, data?.data?.username, data?.data.version, token.colorTextDescription]);
}; };
/** /**
@ -63,7 +63,7 @@ export const SettingsMenu: React.FC<{
}, 3000); }, 3000);
}); });
}, [silenceApi]); }, [silenceApi]);
const appVersion = useApplicationVersion(); const nickname = useNickname();
const editProfile = useEditProfile(); const editProfile = useEditProfile();
const changePassword = useChangePassword(); const changePassword = useChangePassword();
const switchRole = useSwitchRole(); const switchRole = useSwitchRole();
@ -109,7 +109,7 @@ export const SettingsMenu: React.FC<{
useEffect(() => { useEffect(() => {
const items = [ const items = [
appVersion, nickname,
{ {
key: 'divider_1', key: 'divider_1',
type: 'divider', type: 'divider',
@ -147,7 +147,6 @@ export const SettingsMenu: React.FC<{
}, [ }, [
addMenuItem, addMenuItem,
api.auth, api.auth,
appVersion,
changePassword, changePassword,
controlApp, controlApp,
editProfile, editProfile,
@ -156,6 +155,7 @@ export const SettingsMenu: React.FC<{
redirectUrl, redirectUrl,
switchRole, switchRole,
t, t,
nickname,
]); ]);
return <Menu items={getMenuItems()} />; return <Menu items={getMenuItems()} />;
@ -164,11 +164,18 @@ export const SettingsMenu: React.FC<{
export const DropdownVisibleContext = createContext(null); export const DropdownVisibleContext = createContext(null);
export const CurrentUser = () => { export const CurrentUser = () => {
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const { data } = useCurrentUserContext();
const { token } = useToken(); const { token } = useToken();
return ( return (
<div style={{ display: 'inline-flex', verticalAlign: 'top' }}> <div
className={css`
display: inline-block;
vertical-align: top;
&:hover {
background: rgba(255, 255, 255, 0.1) !important;
}
`}
>
<DropdownVisibleContext.Provider value={{ visible, setVisible }}> <DropdownVisibleContext.Provider value={{ visible, setVisible }}>
<Dropdown <Dropdown
open={visible} open={visible}
@ -189,9 +196,9 @@ export const CurrentUser = () => {
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
`} `}
style={{ cursor: 'pointer', border: 0, padding: '16px', color: token.colorTextHeaderMenu }} style={{ cursor: 'pointer', padding: '16px', color: token.colorTextHeaderMenu }}
> >
{data?.data?.nickname || data?.data?.username || data?.data?.email} <UserOutlined />
</span> </span>
</Dropdown> </Dropdown>
</DropdownVisibleContext.Provider> </DropdownVisibleContext.Provider>

View File

@ -0,0 +1,105 @@
import { QuestionCircleOutlined } from '@ant-design/icons';
import { css } from '@emotion/css';
import { Dropdown, Menu } from 'antd';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { DropdownVisibleContext, useToken } from '..';
import { useCurrentAppInfo } from '../appInfo/CurrentAppInfoProvider';
/**
* @note If you want to change here, Note the Setting block on the mobile side
*/
const SettingsMenu: React.FC<{
redirectUrl?: string;
}> = () => {
const { t } = useTranslation();
const data = useCurrentAppInfo();
const { token } = useToken();
const items = [
{
key: 'nocobase',
disabled: true,
label: (
<div style={{ cursor: 'text' }}>
<div style={{ color: token.colorText }}>NocoBase</div>
<div style={{ fontSize: '0.8em', color: token.colorTextDescription }}>v{data?.data?.version}</div>
</div>
),
},
{
key: 'divider_1',
type: 'divider',
},
{
key: 'homePage',
label: (
<a href="https://www.nocobase.com" target="__blank">
{t('Home page')}
</a>
),
},
{
key: 'userManual',
label: (
<a href="https://docs.nocobase.com/manual/quick-start/the-first-app" target="__blank">
{t('User manual')}
</a>
),
},
{
key: 'license',
label: (
<a href="https://github.com/nocobase/nocobase/blob/main/LICENSE-AGPL" target="__blank">
{t('License')}
</a>
),
},
];
return <Menu items={items} />;
};
export const Help = () => {
const [visible, setVisible] = useState(false);
const { token } = useToken();
return (
<div
className={css`
display: inline-block;
vertical-align: top;
&:hover {
background: rgba(255, 255, 255, 0.1) !important;
}
`}
>
<DropdownVisibleContext.Provider value={{ visible, setVisible }}>
<Dropdown
open={visible}
onOpenChange={(visible) => {
setVisible(visible);
}}
dropdownRender={() => {
return <SettingsMenu />;
}}
>
<span
data-testid="user-center-button"
className={css`
max-width: 160px;
overflow: hidden;
display: inline-block;
line-height: 12px;
white-space: nowrap;
text-overflow: ellipsis;
`}
style={{ cursor: 'pointer', padding: '16px', color: token.colorTextHeaderMenu }}
>
<QuestionCircleOutlined />
</span>
</Dropdown>
</DropdownVisibleContext.Provider>
</div>
);
};

View File

@ -9,8 +9,6 @@ setup('admin', async ({ page }) => {
await page.getByPlaceholder('Password').fill('admin123'); await page.getByPlaceholder('Password').fill('admin123');
await page.getByRole('button', { name: 'Sign in' }).click(); await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.getByTestId('user-center-button').getByText('Super Admin')).toBeVisible();
// 开启配置状态 // 开启配置状态
await page.evaluate(() => { await page.evaluate(() => {
localStorage.setItem('NOCOBASE_DESIGNABLE', 'true'); localStorage.setItem('NOCOBASE_DESIGNABLE', 'true');