mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-24 04:27:27 +08:00
feat: move theme switch and language switch to editor option menu (#2025)
Co-authored-by: himself65 <himself65@outlook.com>
This commit is contained in:
@@ -51,6 +51,7 @@ test('app theme', async () => {
|
|||||||
await page.screenshot({
|
await page.screenshot({
|
||||||
path: resolve(testResultDir, 'affine-light-theme-electron.png'),
|
path: resolve(testResultDir, 'affine-light-theme-electron.png'),
|
||||||
});
|
});
|
||||||
|
await page.getByTestId('editor-option-menu').click();
|
||||||
await page.getByTestId('change-theme-dark').click();
|
await page.getByTestId('change-theme-dark').click();
|
||||||
await page.waitForTimeout(50);
|
await page.waitForTimeout(50);
|
||||||
{
|
{
|
||||||
|
|||||||
+95
-44
@@ -14,6 +14,7 @@ import {
|
|||||||
usePageMetaHelper,
|
usePageMetaHelper,
|
||||||
} from '@toeverything/hooks/use-block-suite-page-meta';
|
} from '@toeverything/hooks/use-block-suite-page-meta';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { workspacePreferredModeAtom } from '../../../../atoms';
|
import { workspacePreferredModeAtom } from '../../../../atoms';
|
||||||
@@ -22,10 +23,41 @@ import { useCurrentPageId } from '../../../../hooks/current/use-current-page-id'
|
|||||||
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
|
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
|
||||||
import { toast } from '../../../../utils';
|
import { toast } from '../../../../utils';
|
||||||
import { Export, MoveToTrash } from '../../../affine/operation-menu-items';
|
import { Export, MoveToTrash } from '../../../affine/operation-menu-items';
|
||||||
|
import { MenuThemeModeSwitch } from '../header-right-items/theme-mode-switch';
|
||||||
export const EditorOptionMenu = () => {
|
import {
|
||||||
|
StyledHorizontalDivider,
|
||||||
|
StyledHorizontalDividerContainer,
|
||||||
|
} from '../styles';
|
||||||
|
import { LanguageMenu } from './LanguageMenu';
|
||||||
|
const CommonMenu = () => {
|
||||||
|
const content = (
|
||||||
|
<div
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MenuThemeModeSwitch />
|
||||||
|
<LanguageMenu />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<FlexWrapper alignItems="center" justifyContent="center">
|
||||||
|
<Menu
|
||||||
|
width={276}
|
||||||
|
content={content}
|
||||||
|
// placement="bottom-end"
|
||||||
|
disablePortal={true}
|
||||||
|
trigger="click"
|
||||||
|
>
|
||||||
|
<IconButton data-testid="editor-option-menu" iconSize={[24, 24]}>
|
||||||
|
<MoreVerticalIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Menu>
|
||||||
|
</FlexWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const PageMenu = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// fixme(himself65): remove these hooks ASAP
|
// fixme(himself65): remove these hooks ASAP
|
||||||
const [workspace] = useCurrentWorkspace();
|
const [workspace] = useCurrentWorkspace();
|
||||||
const [pageId] = useCurrentPageId();
|
const [pageId] = useCurrentPageId();
|
||||||
@@ -35,55 +67,70 @@ export const EditorOptionMenu = () => {
|
|||||||
const pageMeta = useBlockSuitePageMeta(blockSuiteWorkspace).find(
|
const pageMeta = useBlockSuitePageMeta(blockSuiteWorkspace).find(
|
||||||
meta => meta.id === pageId
|
meta => meta.id === pageId
|
||||||
);
|
);
|
||||||
|
assertExists(pageMeta);
|
||||||
const [record, set] = useAtom(workspacePreferredModeAtom);
|
const [record, set] = useAtom(workspacePreferredModeAtom);
|
||||||
const mode = record[pageId] ?? 'page';
|
const mode = record[pageId] ?? 'page';
|
||||||
assertExists(pageMeta);
|
|
||||||
const { favorite } = pageMeta;
|
const favorite = pageMeta.favorite ?? false;
|
||||||
const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace);
|
const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace);
|
||||||
const [openConfirm, setOpenConfirm] = useState(false);
|
const [openConfirm, setOpenConfirm] = useState(false);
|
||||||
const { removeToTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace);
|
const { removeToTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace);
|
||||||
const EditMenu = (
|
const EditMenu = (
|
||||||
<>
|
<>
|
||||||
<MenuItem
|
<>
|
||||||
data-testid="editor-option-menu-favorite"
|
<MenuItem
|
||||||
onClick={() => {
|
data-testid="editor-option-menu-favorite"
|
||||||
setPageMeta(pageId, { favorite: !favorite });
|
onClick={() => {
|
||||||
toast(
|
setPageMeta(pageId, { favorite: !favorite });
|
||||||
favorite ? t('Removed from Favorites') : t('Added to Favorites')
|
toast(
|
||||||
);
|
favorite ? t('Removed from Favorites') : t('Added to Favorites')
|
||||||
}}
|
);
|
||||||
icon={
|
|
||||||
favorite ? (
|
|
||||||
<FavoritedIcon style={{ color: 'var(--affine-primary-color)' }} />
|
|
||||||
) : (
|
|
||||||
<FavoriteIcon />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{favorite ? t('Remove from favorites') : t('Add to Favorites')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem
|
|
||||||
icon={mode === 'page' ? <EdgelessIcon /> : <PageIcon />}
|
|
||||||
data-testid="editor-option-menu-edgeless"
|
|
||||||
onClick={() => {
|
|
||||||
set(record => ({
|
|
||||||
...record,
|
|
||||||
[pageId]: mode === 'page' ? 'edgeless' : 'page',
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('Convert to ')}
|
|
||||||
{mode === 'page' ? t('Edgeless') : t('Page')}
|
|
||||||
</MenuItem>
|
|
||||||
<Export />
|
|
||||||
{!pageMeta.isRootPinboard && (
|
|
||||||
<MoveToTrash
|
|
||||||
testId="editor-option-menu-delete"
|
|
||||||
onItemClick={() => {
|
|
||||||
setOpenConfirm(true);
|
|
||||||
}}
|
}}
|
||||||
/>
|
icon={
|
||||||
)}
|
favorite ? (
|
||||||
|
<FavoritedIcon style={{ color: 'var(--affine-primary-color)' }} />
|
||||||
|
) : (
|
||||||
|
<FavoriteIcon />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{favorite ? t('Remove from favorites') : t('Add to Favorites')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
icon={mode === 'page' ? <EdgelessIcon /> : <PageIcon />}
|
||||||
|
data-testid="editor-option-menu-edgeless"
|
||||||
|
onClick={() => {
|
||||||
|
set(record => ({
|
||||||
|
...record,
|
||||||
|
[pageId]: mode === 'page' ? 'edgeless' : 'page',
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Convert to ')}
|
||||||
|
{mode === 'page' ? t('Edgeless') : t('Page')}
|
||||||
|
</MenuItem>
|
||||||
|
<Export />
|
||||||
|
{!pageMeta.isRootPinboard && (
|
||||||
|
<MoveToTrash
|
||||||
|
testId="editor-option-menu-delete"
|
||||||
|
onItemClick={() => {
|
||||||
|
setOpenConfirm(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<StyledHorizontalDividerContainer>
|
||||||
|
<StyledHorizontalDivider />
|
||||||
|
</StyledHorizontalDividerContainer>
|
||||||
|
</>
|
||||||
|
|
||||||
|
<div
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MenuThemeModeSwitch />
|
||||||
|
<LanguageMenu />
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -116,3 +163,7 @@ export const EditorOptionMenu = () => {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
export const EditorOptionMenu = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
return router.query.pageId ? <PageMenu /> : <CommonMenu />;
|
||||||
|
};
|
||||||
|
|||||||
+133
@@ -0,0 +1,133 @@
|
|||||||
|
import { Button, displayFlex, Menu, MenuItem, styled } from '@affine/component';
|
||||||
|
import { LOCALES } from '@affine/i18n';
|
||||||
|
import { useTranslation } from '@affine/i18n';
|
||||||
|
import { ArrowDownSmallIcon, PublishIcon } from '@blocksuite/icons';
|
||||||
|
import type { FC, ReactElement } from 'react';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
const LanguageMenuContent: FC = () => {
|
||||||
|
const { i18n } = useTranslation();
|
||||||
|
const changeLanguage = useCallback(
|
||||||
|
(event: string) => {
|
||||||
|
i18n.changeLanguage(event);
|
||||||
|
},
|
||||||
|
[i18n]
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{LOCALES.map(option => {
|
||||||
|
return (
|
||||||
|
<StyledListItem
|
||||||
|
key={option.name}
|
||||||
|
title={option.name}
|
||||||
|
onClick={() => {
|
||||||
|
changeLanguage(option.tag);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{option.originalName}
|
||||||
|
</StyledListItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export const LanguageMenu: React.FC = () => {
|
||||||
|
const { i18n } = useTranslation();
|
||||||
|
|
||||||
|
const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledContainer>
|
||||||
|
<StyledIconContainer>
|
||||||
|
<PublishIcon />
|
||||||
|
</StyledIconContainer>
|
||||||
|
<StyledButtonContainer>
|
||||||
|
<Menu
|
||||||
|
content={(<LanguageMenuContent />) as ReactElement}
|
||||||
|
placement="bottom"
|
||||||
|
trigger="click"
|
||||||
|
disablePortal={true}
|
||||||
|
>
|
||||||
|
<StyledButton
|
||||||
|
icon={
|
||||||
|
<StyledArrowDownContainer>
|
||||||
|
<ArrowDownSmallIcon />
|
||||||
|
</StyledArrowDownContainer>
|
||||||
|
}
|
||||||
|
iconPosition="end"
|
||||||
|
noBorder={true}
|
||||||
|
data-testid="language-menu-button"
|
||||||
|
>
|
||||||
|
<StyledCurrentLanguage>
|
||||||
|
{currentLanguage?.originalName}
|
||||||
|
</StyledCurrentLanguage>
|
||||||
|
</StyledButton>
|
||||||
|
</Menu>
|
||||||
|
</StyledButtonContainer>
|
||||||
|
</StyledContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledListItem = styled(MenuItem)(() => ({
|
||||||
|
width: '132px',
|
||||||
|
height: '38px',
|
||||||
|
fontSize: 'var(--affine-font-base)',
|
||||||
|
textTransform: 'capitalize',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledContainer = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
width: '100%',
|
||||||
|
height: '48px',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
...displayFlex('flex-start', 'center'),
|
||||||
|
padding: '0 14px',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const StyledIconContainer = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
width: '20px',
|
||||||
|
height: '20px',
|
||||||
|
color: 'var(--affine-icon-color)',
|
||||||
|
fontSize: '20px',
|
||||||
|
...displayFlex('flex-start', 'center'),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const StyledButtonContainer = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
width: '100%',
|
||||||
|
height: '32px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
border: `1px solid var(--affine-border-color)`,
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
...displayFlex('flex-start', 'center'),
|
||||||
|
marginLeft: '12px',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const StyledButton = styled(Button)(() => {
|
||||||
|
return {
|
||||||
|
width: '100%',
|
||||||
|
height: '32px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
...displayFlex('space-between', 'center'),
|
||||||
|
textTransform: 'capitalize',
|
||||||
|
padding: '0',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const StyledArrowDownContainer = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
height: '32px',
|
||||||
|
borderLeft: `1px solid var(--affine-border-color)`,
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
...displayFlex('flex-start', 'center'),
|
||||||
|
padding: '4px 6px',
|
||||||
|
fontSize: '24px',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const StyledCurrentLanguage = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
marginLeft: '12px',
|
||||||
|
color: 'var(--affine-text-color)',
|
||||||
|
};
|
||||||
|
});
|
||||||
+52
-37
@@ -1,50 +1,65 @@
|
|||||||
import { DarkModeIcon, LightModeIcon } from '@blocksuite/icons';
|
import { DarkModeIcon, LightModeIcon } from '@blocksuite/icons';
|
||||||
import { useTheme } from 'next-themes';
|
import { useTheme } from 'next-themes';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import { StyledSwitchItem, StyledThemeModeSwitch } from './style';
|
import {
|
||||||
export const ThemeModeSwitch = () => {
|
StyledSwitchItem,
|
||||||
const { setTheme, resolvedTheme } = useTheme();
|
StyledThemeButton,
|
||||||
|
StyledThemeButtonContainer,
|
||||||
|
StyledThemeModeContainer,
|
||||||
|
StyledThemeModeSwitch,
|
||||||
|
StyledVerticalDivider,
|
||||||
|
} from './style';
|
||||||
|
|
||||||
|
export const MenuThemeModeSwitch = () => {
|
||||||
|
const { setTheme, resolvedTheme, theme } = useTheme();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (environment.isDesktop) {
|
if (environment.isDesktop) {
|
||||||
window.apis?.onThemeChange(resolvedTheme === 'dark' ? 'dark' : 'light');
|
window.apis?.onThemeChange(resolvedTheme === 'dark' ? 'dark' : 'light');
|
||||||
}
|
}
|
||||||
}, [resolvedTheme]);
|
}, [resolvedTheme]);
|
||||||
|
|
||||||
const [isHover, setIsHover] = useState(false);
|
|
||||||
return (
|
return (
|
||||||
<StyledThemeModeSwitch
|
<StyledThemeModeContainer>
|
||||||
data-testid="change-theme-container"
|
<StyledThemeModeSwitch data-testid="change-theme-container" inMenu={true}>
|
||||||
onMouseEnter={() => {
|
<StyledSwitchItem active={resolvedTheme === 'light'} inMenu={true}>
|
||||||
setIsHover(true);
|
<LightModeIcon />
|
||||||
}}
|
</StyledSwitchItem>
|
||||||
onMouseLeave={() => {
|
<StyledSwitchItem active={resolvedTheme === 'dark'} inMenu={true}>
|
||||||
setIsHover(false);
|
<DarkModeIcon />
|
||||||
}}
|
</StyledSwitchItem>
|
||||||
>
|
</StyledThemeModeSwitch>
|
||||||
<StyledSwitchItem
|
<StyledThemeButtonContainer>
|
||||||
data-testid="change-theme-light"
|
<StyledThemeButton
|
||||||
active={resolvedTheme === 'light'}
|
data-testid="change-theme-light"
|
||||||
isHover={isHover}
|
active={theme === 'light'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTheme('light');
|
setTheme('light');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<LightModeIcon />
|
light
|
||||||
</StyledSwitchItem>
|
</StyledThemeButton>
|
||||||
<StyledSwitchItem
|
<StyledVerticalDivider />
|
||||||
data-testid="change-theme-dark"
|
<StyledThemeButton
|
||||||
active={resolvedTheme === 'dark'}
|
data-testid="change-theme-dark"
|
||||||
isHover={isHover}
|
active={theme === 'dark'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTheme('dark');
|
setTheme('dark');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DarkModeIcon />
|
dark
|
||||||
</StyledSwitchItem>
|
</StyledThemeButton>
|
||||||
</StyledThemeModeSwitch>
|
<StyledVerticalDivider />
|
||||||
|
<StyledThemeButton
|
||||||
|
active={theme === 'system'}
|
||||||
|
onClick={() => {
|
||||||
|
setTheme('system');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
system
|
||||||
|
</StyledThemeButton>
|
||||||
|
</StyledThemeButtonContainer>
|
||||||
|
</StyledThemeModeContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ThemeModeSwitch;
|
export default MenuThemeModeSwitch;
|
||||||
|
|||||||
+47
-8
@@ -3,24 +3,63 @@ import { css, displayFlex, keyframes, styled } from '@affine/component';
|
|||||||
import spring, { toString } from 'css-spring';
|
import spring, { toString } from 'css-spring';
|
||||||
|
|
||||||
const ANIMATE_DURATION = 400;
|
const ANIMATE_DURATION = 400;
|
||||||
|
export const StyledThemeModeContainer = styled('div')(() => {
|
||||||
export const StyledThemeModeSwitch = styled('button')(() => {
|
|
||||||
return {
|
return {
|
||||||
width: '32px',
|
width: '100%',
|
||||||
|
height: '48px',
|
||||||
|
borderRadius: '6px',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
color: 'var(--affine-icon-color)',
|
||||||
|
fontSize: '16px',
|
||||||
|
...displayFlex('flex-start', 'center'),
|
||||||
|
padding: '0 14px',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
export const StyledThemeButtonContainer = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
border: `1px solid var(--affine-border-color)`,
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
...displayFlex('space-evenly', 'center'),
|
||||||
|
flexGrow: 1,
|
||||||
|
marginLeft: '12px',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
export const StyledThemeButton = styled('button')<{
|
||||||
|
active: boolean;
|
||||||
|
}>(({ active }) => {
|
||||||
|
return {
|
||||||
|
cursor: 'pointer',
|
||||||
|
color: active ? 'var(--affine-primary-color)' : 'var(--affine-icon-color)',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
export const StyledVerticalDivider = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
width: '1px',
|
||||||
height: '32px',
|
height: '32px',
|
||||||
|
borderLeft: `1px solid var(--affine-border-color)`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
export const StyledThemeModeSwitch = styled('button')<{
|
||||||
|
inMenu?: boolean;
|
||||||
|
}>(({ inMenu }) => {
|
||||||
|
return {
|
||||||
|
width: inMenu ? '20px' : '32px',
|
||||||
|
height: inMenu ? '20px' : '32px',
|
||||||
borderRadius: '6px',
|
borderRadius: '6px',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
WebkitAppRegion: 'no-drag',
|
WebkitAppRegion: 'no-drag',
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
color: 'var(--affine-icon-color)',
|
color: 'var(--affine-icon-color)',
|
||||||
fontSize: '24px',
|
fontSize: inMenu ? '20px' : '24px',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
export const StyledSwitchItem = styled('div')<{
|
export const StyledSwitchItem = styled('div')<{
|
||||||
active: boolean;
|
active: boolean;
|
||||||
isHover: boolean;
|
isHover?: boolean;
|
||||||
}>(({ active, isHover }) => {
|
inMenu?: boolean;
|
||||||
|
}>(({ active, isHover, inMenu }) => {
|
||||||
const activeRaiseAnimate = toString(
|
const activeRaiseAnimate = toString(
|
||||||
spring({ top: '0' }, { top: '-100%' }, { preset: 'gentle' })
|
spring({ top: '0' }, { top: '-100%' }, { preset: 'gentle' })
|
||||||
);
|
);
|
||||||
@@ -58,8 +97,8 @@ export const StyledSwitchItem = styled('div')<{
|
|||||||
};
|
};
|
||||||
return css`
|
return css`
|
||||||
${css(displayFlex('center', 'center'))}
|
${css(displayFlex('center', 'center'))}
|
||||||
width: 32px;
|
width:${inMenu ? '20px' : '32px'} ;
|
||||||
height: 32px;
|
height: ${inMenu ? '20px' : '32px'} ;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import { EditorOptionMenu } from './header-right-items/EditorOptionMenu';
|
|||||||
import EditPage from './header-right-items/EditPage';
|
import EditPage from './header-right-items/EditPage';
|
||||||
import { HeaderShareMenu } from './header-right-items/ShareMenu';
|
import { HeaderShareMenu } from './header-right-items/ShareMenu';
|
||||||
import SyncUser from './header-right-items/SyncUser';
|
import SyncUser from './header-right-items/SyncUser';
|
||||||
import ThemeModeSwitch from './header-right-items/theme-mode-switch';
|
|
||||||
import TrashButtonGroup from './header-right-items/TrashButtonGroup';
|
import TrashButtonGroup from './header-right-items/TrashButtonGroup';
|
||||||
import UserAvatar from './header-right-items/UserAvatar';
|
import UserAvatar from './header-right-items/UserAvatar';
|
||||||
import {
|
import {
|
||||||
@@ -66,7 +65,6 @@ export type BaseHeaderProps<
|
|||||||
export const enum HeaderRightItemName {
|
export const enum HeaderRightItemName {
|
||||||
EditorOptionMenu = 'editorOptionMenu',
|
EditorOptionMenu = 'editorOptionMenu',
|
||||||
TrashButtonGroup = 'trashButtonGroup',
|
TrashButtonGroup = 'trashButtonGroup',
|
||||||
ThemeModeSwitch = 'themeModeSwitch',
|
|
||||||
SyncUser = 'syncUser',
|
SyncUser = 'syncUser',
|
||||||
ShareMenu = 'shareMenu',
|
ShareMenu = 'shareMenu',
|
||||||
EditPage = 'editPage',
|
EditPage = 'editPage',
|
||||||
@@ -98,12 +96,6 @@ const HeaderRightItems: Record<HeaderRightItemName, HeaderItem> = {
|
|||||||
return !isPublic && !isPreview;
|
return !isPublic && !isPreview;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[HeaderRightItemName.ThemeModeSwitch]: {
|
|
||||||
Component: ThemeModeSwitch,
|
|
||||||
availableWhen: (_, currentPage) => {
|
|
||||||
return currentPage?.meta.trash !== true;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[HeaderRightItemName.ShareMenu]: {
|
[HeaderRightItemName.ShareMenu]: {
|
||||||
Component: HeaderShareMenu,
|
Component: HeaderShareMenu,
|
||||||
availableWhen: (workspace, currentPage) => {
|
availableWhen: (workspace, currentPage) => {
|
||||||
@@ -125,7 +117,7 @@ const HeaderRightItems: Record<HeaderRightItemName, HeaderItem> = {
|
|||||||
[HeaderRightItemName.EditorOptionMenu]: {
|
[HeaderRightItemName.EditorOptionMenu]: {
|
||||||
Component: EditorOptionMenu,
|
Component: EditorOptionMenu,
|
||||||
availableWhen: (_, currentPage, { isPublic, isPreview }) => {
|
availableWhen: (_, currentPage, { isPublic, isPreview }) => {
|
||||||
return !!currentPage && !isPublic && !isPreview;
|
return !isPublic && !isPreview;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -167,3 +167,16 @@ export const StyledQuickSearchTipContent = styled('div')(() => {
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const StyledHorizontalDivider = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
width: '100%',
|
||||||
|
borderTop: `1px solid var(--affine-border-color)`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
export const StyledHorizontalDividerContainer = styled('div')(() => {
|
||||||
|
return {
|
||||||
|
width: '100%',
|
||||||
|
padding: '14px',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import { useCallback } from 'react';
|
|||||||
|
|
||||||
import type { AllWorkspace } from '../../../shared';
|
import type { AllWorkspace } from '../../../shared';
|
||||||
import { Footer } from '../footer';
|
import { Footer } from '../footer';
|
||||||
import { LanguageMenu } from './language-menu';
|
|
||||||
import {
|
import {
|
||||||
StyledCreateWorkspaceCard,
|
StyledCreateWorkspaceCard,
|
||||||
StyledHelperContainer,
|
StyledHelperContainer,
|
||||||
@@ -24,7 +23,6 @@ import {
|
|||||||
StyledModalHeaderLeft,
|
StyledModalHeaderLeft,
|
||||||
StyledModalTitle,
|
StyledModalTitle,
|
||||||
StyledOperationWrapper,
|
StyledOperationWrapper,
|
||||||
StyledSplitLine,
|
|
||||||
StyleWorkspaceAdd,
|
StyleWorkspaceAdd,
|
||||||
StyleWorkspaceInfo,
|
StyleWorkspaceInfo,
|
||||||
StyleWorkspaceTitle,
|
StyleWorkspaceTitle,
|
||||||
@@ -86,8 +84,6 @@ export const WorkspaceListModal = ({
|
|||||||
</StyledModalHeaderLeft>
|
</StyledModalHeaderLeft>
|
||||||
|
|
||||||
<StyledOperationWrapper>
|
<StyledOperationWrapper>
|
||||||
<LanguageMenu />
|
|
||||||
<StyledSplitLine />
|
|
||||||
<ModalCloseButton
|
<ModalCloseButton
|
||||||
data-testid="close-workspace-modal"
|
data-testid="close-workspace-modal"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
import { Button, Menu, MenuItem, styled } from '@affine/component';
|
|
||||||
import { LOCALES } from '@affine/i18n';
|
|
||||||
import { useTranslation } from '@affine/i18n';
|
|
||||||
import { ArrowDownSmallIcon } from '@blocksuite/icons';
|
|
||||||
import type { FC, ReactElement } from 'react';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
|
|
||||||
const LanguageMenuContent: FC = () => {
|
|
||||||
const { i18n } = useTranslation();
|
|
||||||
const changeLanguage = useCallback(
|
|
||||||
(event: string) => {
|
|
||||||
i18n.changeLanguage(event);
|
|
||||||
},
|
|
||||||
[i18n]
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{LOCALES.map(option => {
|
|
||||||
return (
|
|
||||||
<ListItem
|
|
||||||
key={option.name}
|
|
||||||
title={option.name}
|
|
||||||
onClick={() => {
|
|
||||||
changeLanguage(option.tag);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{option.originalName}
|
|
||||||
</ListItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export const LanguageMenu: React.FC = () => {
|
|
||||||
const { i18n } = useTranslation();
|
|
||||||
|
|
||||||
const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Menu
|
|
||||||
content={(<LanguageMenuContent />) as ReactElement}
|
|
||||||
placement="bottom"
|
|
||||||
trigger="click"
|
|
||||||
disablePortal={true}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
icon={<ArrowDownSmallIcon />}
|
|
||||||
iconPosition="end"
|
|
||||||
noBorder={true}
|
|
||||||
style={{ textTransform: 'capitalize', padding: '0 12px' }}
|
|
||||||
data-testid="language-menu-button"
|
|
||||||
>
|
|
||||||
{currentLanguage?.originalName}
|
|
||||||
</Button>
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ListItem = styled(MenuItem)(() => ({
|
|
||||||
height: '38px',
|
|
||||||
fontSize: 'var(--affine-font-base)',
|
|
||||||
textTransform: 'capitalize',
|
|
||||||
padding: '0 24px',
|
|
||||||
}));
|
|
||||||
@@ -9,7 +9,7 @@ test('Switch to edgeless by switch edgeless item', async ({ page }) => {
|
|||||||
await waitMarkdownImported(page);
|
await waitMarkdownImported(page);
|
||||||
const btn = await page.getByTestId('switch-edgeless-mode-button');
|
const btn = await page.getByTestId('switch-edgeless-mode-button');
|
||||||
await btn.click();
|
await btn.click();
|
||||||
|
await page.waitForTimeout(100);
|
||||||
const edgeless = page.locator('affine-edgeless-page');
|
const edgeless = page.locator('affine-edgeless-page');
|
||||||
expect(await edgeless.isVisible()).toBe(true);
|
expect(await edgeless.isVisible()).toBe(true);
|
||||||
|
|
||||||
@@ -27,7 +27,6 @@ test('Convert to edgeless by editor header items', async ({ page }) => {
|
|||||||
await clickPageMoreActions(page);
|
await clickPageMoreActions(page);
|
||||||
const menusEdgelessItem = page.getByTestId('editor-option-menu-edgeless');
|
const menusEdgelessItem = page.getByTestId('editor-option-menu-edgeless');
|
||||||
await menusEdgelessItem.click({ delay: 100 });
|
await menusEdgelessItem.click({ delay: 100 });
|
||||||
|
|
||||||
const edgeless = page.locator('affine-edgeless-page');
|
const edgeless = page.locator('affine-edgeless-page');
|
||||||
expect(await edgeless.isVisible()).toBe(true);
|
expect(await edgeless.isVisible()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { expect } from '@playwright/test';
|
|||||||
|
|
||||||
import { openHomePage } from '../libs/load-page';
|
import { openHomePage } from '../libs/load-page';
|
||||||
import { waitMarkdownImported } from '../libs/page-logic';
|
import { waitMarkdownImported } from '../libs/page-logic';
|
||||||
import { clickSideBarCurrentWorkspaceBanner } from '../libs/sidebar';
|
|
||||||
import { assertCurrentWorkspaceFlavour } from '../libs/workspace';
|
import { assertCurrentWorkspaceFlavour } from '../libs/workspace';
|
||||||
|
|
||||||
test('preset workspace name', async ({ page }) => {
|
test('preset workspace name', async ({ page }) => {
|
||||||
@@ -24,7 +23,9 @@ test('preset workspace name', async ({ page }) => {
|
|||||||
test('Open language switch menu', async ({ page }) => {
|
test('Open language switch menu', async ({ page }) => {
|
||||||
await openHomePage(page);
|
await openHomePage(page);
|
||||||
await waitMarkdownImported(page);
|
await waitMarkdownImported(page);
|
||||||
await clickSideBarCurrentWorkspaceBanner(page);
|
const editorOptionMenuButton = page.getByTestId('editor-option-menu');
|
||||||
|
await expect(editorOptionMenuButton).toBeVisible();
|
||||||
|
await editorOptionMenuButton.click();
|
||||||
const languageMenuButton = page.getByTestId('language-menu-button');
|
const languageMenuButton = page.getByTestId('language-menu-button');
|
||||||
await expect(languageMenuButton).toBeVisible();
|
await expect(languageMenuButton).toBeVisible();
|
||||||
const actual = await languageMenuButton.innerText();
|
const actual = await languageMenuButton.innerText();
|
||||||
|
|||||||
@@ -22,32 +22,10 @@ test('default white', async ({ browser }) => {
|
|||||||
await page.screenshot({
|
await page.screenshot({
|
||||||
path: resolve(testResultDir, 'affine-light-theme.png'),
|
path: resolve(testResultDir, 'affine-light-theme.png'),
|
||||||
});
|
});
|
||||||
|
await page.getByTestId('editor-option-menu').click();
|
||||||
await page.getByTestId('change-theme-dark').click();
|
await page.getByTestId('change-theme-dark').click();
|
||||||
await page.waitForTimeout(50);
|
await page.waitForTimeout(50);
|
||||||
await page.screenshot({
|
await page.screenshot({
|
||||||
path: resolve(testResultDir, 'affine-dark-theme.png'),
|
path: resolve(testResultDir, 'affine-dark-theme.png'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// test('change theme to dark', async ({ page }) => {
|
|
||||||
// const changeThemeContainer = page.locator(
|
|
||||||
// '[data-testid=change-theme-container]'
|
|
||||||
// );
|
|
||||||
// const box = await changeThemeContainer.boundingBox();
|
|
||||||
// expect(box?.x).not.toBeUndefined();
|
|
||||||
//
|
|
||||||
// await page.mouse.move((box?.x ?? 0) + 5, (box?.y ?? 0) + 5);
|
|
||||||
// await page.waitForTimeout(1000);
|
|
||||||
// const darkButton = page.locator('[data-testid=change-theme-dark]');
|
|
||||||
// const darkButtonPositionTop = await darkButton.evaluate(
|
|
||||||
// element => element.getBoundingClientRect().y
|
|
||||||
// );
|
|
||||||
// expect(darkButtonPositionTop).toBe(box?.y);
|
|
||||||
//
|
|
||||||
// await page.mouse.click((box?.x ?? 0) + 5, (box?.y ?? 0) + 5);
|
|
||||||
// const root = page.locator('html');
|
|
||||||
// const themeMode = await root.evaluate(element =>
|
|
||||||
// element.getAttribute('data-theme')
|
|
||||||
// );
|
|
||||||
// expect(themeMode).toBe('dark');
|
|
||||||
// });
|
|
||||||
|
|||||||
Reference in New Issue
Block a user