refactor: header options menu (#3615)

This commit is contained in:
JimmFly
2023-08-09 01:14:24 +08:00
committed by GitHub
parent 7d16a8348c
commit 4e84b9a121
21 changed files with 183 additions and 128 deletions

View File

@@ -5,12 +5,15 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { assertExists } from '@blocksuite/global/utils';
import {
EdgelessIcon,
EditIcon,
FavoritedIcon,
FavoriteIcon,
ImportIcon,
MoreVerticalIcon,
PageIcon,
} from '@blocksuite/icons';
import { IconButton } from '@toeverything/components/button';
import { Divider } from '@toeverything/components/divider';
import {
useBlockSuitePageMeta,
usePageMetaHelper,
@@ -24,6 +27,8 @@ import { pageSettingFamily } from '../../../../atoms';
import { useBlockSuiteMetaHelper } from '../../../../hooks/affine/use-block-suite-meta-helper';
import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace';
import { toast } from '../../../../utils';
import { HeaderDropDownButton } from '../../../pure/header-drop-down-button';
import { usePageHelper } from '../../block-suite-page-list/utils';
import { LanguageMenu } from './language-menu';
import { MenuThemeModeSwitch } from './theme-mode-switch';
const CommonMenu = () => {
@@ -52,7 +57,12 @@ const CommonMenu = () => {
</FlexWrapper>
);
};
const PageMenu = () => {
type PageMenuProps = {
rename?: () => void;
};
export const PageMenu = ({ rename }: PageMenuProps) => {
const t = useAFFiNEI18N();
// fixme(himself65): remove these hooks ASAP
const [workspace] = useCurrentWorkspace();
@@ -71,6 +81,7 @@ const PageMenu = () => {
const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace);
const [openConfirm, setOpenConfirm] = useState(false);
const { removeToTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace);
const { importFile } = usePageHelper(blockSuiteWorkspace);
const handleFavorite = useCallback(() => {
setPageMeta(pageId, { favorite: !favorite });
toast(favorite ? t['Removed from Favorites']() : t['Added to Favorites']());
@@ -90,12 +101,32 @@ const PageMenu = () => {
toast(t['Moved to Trash']());
setOpenConfirm(false);
}, [pageMeta.id, removeToTrash, t]);
const menuItemStyle = {
padding: '4px 12px',
};
const EditMenu = (
<>
<MenuItem
icon={<EditIcon />}
data-testid="editor-option-menu-rename"
onClick={rename}
style={menuItemStyle}
>
{t['Rename']()}
</MenuItem>
<MenuItem
icon={mode === 'page' ? <EdgelessIcon /> : <PageIcon />}
data-testid="editor-option-menu-edgeless"
onClick={handleSwitchMode}
style={menuItemStyle}
>
{t['Convert to ']()}
{mode === 'page' ? t['Edgeless']() : t['Page']()}
</MenuItem>
<MenuItem
data-testid="editor-option-menu-favorite"
onClick={handleFavorite}
style={menuItemStyle}
icon={
favorite ? (
<FavoritedIcon style={{ color: 'var(--affine-primary-color)' }} />
@@ -106,15 +137,34 @@ const PageMenu = () => {
>
{favorite ? t['Remove from favorites']() : t['Add to Favorites']()}
</MenuItem>
<MenuItem
icon={mode === 'page' ? <EdgelessIcon /> : <PageIcon />}
data-testid="editor-option-menu-edgeless"
onClick={handleSwitchMode}
{/* {TODO: add tag and duplicate function support} */}
{/* <MenuItem
icon={<TagsIcon />}
data-testid="editor-option-menu-add-tag"
onClick={() => {}}
style={menuItemStyle}
>
{t['Convert to ']()}
{mode === 'page' ? t['Edgeless']() : t['Page']()}
{t['com.affine.header.option.add-tag']()}
</MenuItem> */}
<Divider />
{/* <MenuItem
icon={<DuplicateIcon />}
data-testid="editor-option-menu-duplicate"
onClick={() => {}}
style={menuItemStyle}
>
{t['com.affine.header.option.duplicate']()}
</MenuItem> */}
<MenuItem
icon={<ImportIcon />}
data-testid="editor-option-menu-import"
onClick={importFile}
style={menuItemStyle}
>
{t['Import']()}
</MenuItem>
<Export />
<Divider />
<MoveToTrash
data-testid="editor-option-menu-delete"
onItemClick={() => {
@@ -132,10 +182,15 @@ const PageMenu = () => {
placement="bottom-end"
disablePortal={true}
trigger="click"
menuStyles={{
borderRadius: '8px',
padding: '8px',
background: 'var(--affine-background-overlay-panel-color)',
}}
>
<IconButton data-testid="editor-option-menu">
<MoreVerticalIcon />
</IconButton>
<div>
<HeaderDropDownButton />
</div>
</Menu>
<MoveToTrash.ConfirmModal
open={openConfirm}

View File

@@ -60,10 +60,8 @@ interface HeaderItem {
const HeaderRightItems: Record<HeaderRightItemName, HeaderItem> = {
[HeaderRightItemName.EditorOptionMenu]: {
Component: EditorOptionMenu,
availableWhen: (_, currentPage, { isPublic }) => {
return (
!isPublic && currentPage?.meta.trash !== true && currentPage !== null
);
availableWhen: () => {
return false;
},
},
};

View File

@@ -1,18 +1,15 @@
import { assertExists } from '@blocksuite/global/utils';
import { Button } from '@toeverything/components/button';
import {
useBlockSuitePageMeta,
usePageMetaHelper,
} from '@toeverything/hooks/use-block-suite-page-meta';
import { useSetAtom } from 'jotai';
import type { HTMLAttributes, ReactElement, ReactNode } from 'react';
import { useCallback, useRef, useState } from 'react';
import { openQuickSearchModalAtom } from '../../../atoms';
import { QuickSearchButton } from '../../pure/quick-search-button';
import { EditorModeSwitch } from './editor-mode-switch';
import type { BaseHeaderProps } from './header';
import { Header } from './header';
import { PageMenu } from './header-right-items/editor-option-menu';
import * as styles from './styles.css';
export interface WorkspaceHeaderProps
@@ -26,14 +23,12 @@ export const BlockSuiteEditorHeader = (
): ReactElement => {
const { workspace, currentPage, children, isPublic } = props;
// fixme(himself65): remove this atom and move it to props
const setOpenQuickSearch = useSetAtom(openQuickSearchModalAtom);
const pageMeta = useBlockSuitePageMeta(workspace.blockSuiteWorkspace).find(
meta => meta.id === currentPage?.id
);
const pageTitleMeta = usePageMetaHelper(workspace.blockSuiteWorkspace);
const [isEditable, setIsEditable] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const handleClick = useCallback(() => {
if (isEditable) {
setIsEditable(!isEditable);
@@ -45,7 +40,14 @@ export const BlockSuiteEditorHeader = (
setIsEditable(!isEditable);
}
}, [currentPage, isEditable, pageMeta?.title, pageTitleMeta]);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter' || e.key === 'Escape') {
handleClick();
}
},
[handleClick]
);
const headerRef = useRef<HTMLDivElement>(null);
assertExists(pageMeta);
const title = pageMeta?.title;
@@ -76,16 +78,8 @@ export const BlockSuiteEditorHeader = (
defaultValue={pageMeta?.title}
onBlur={handleClick}
ref={inputRef}
onKeyDown={handleKeyDown}
/>
<Button
onClick={handleClick}
data-testid="save-edit-button"
style={{
marginLeft: '12px',
}}
>
Save
</Button>
</div>
) : (
<span data-testid="title-edit-button" onClick={handleClick}>
@@ -94,11 +88,7 @@ export const BlockSuiteEditorHeader = (
)}
</div>
<div className={styles.searchArrowWrapper}>
<QuickSearchButton
onClick={() => {
setOpenQuickSearch(true);
}}
/>
<PageMenu rename={handleClick} />
</div>
</div>
</div>

View File

@@ -205,6 +205,7 @@ export const searchArrowWrapper = style({
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
marginLeft: '4px',
});
export const pageListTitleWrapper = style({

View File

@@ -14,21 +14,19 @@ const StyledIconButtonWithAnimate = styled(IconButton)(() => {
svg: {
transform: 'translateY(3px)',
},
'::after': {
background: 'var(--affine-background-primary-color)',
},
backgroundColor: 'transparent !important',
},
};
});
// fixme(himself65): need to refactor
export const QuickSearchButton = ({
export const HeaderDropDownButton = ({
onClick,
...props
}: Omit<IconButtonProps, 'children'>) => {
return (
<StyledIconButtonWithAnimate
data-testid="header-quickSearchButton"
data-testid="header-dropDownButton"
{...props}
onClick={e => {
onClick?.(e);

View File

@@ -1,45 +1,19 @@
import { RadioButton, RadioButtonGroup } from '@affine/component';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useSetAtom } from 'jotai';
import { useAtom } from 'jotai';
import type { ReactNode } from 'react';
import type React from 'react';
import {
allPageModeSelectAtom,
openQuickSearchModalAtom,
} from '../../../atoms';
import { allPageModeSelectAtom } from '../../../atoms';
import type { HeaderProps } from '../../blocksuite/workspace-header/header';
import { Header } from '../../blocksuite/workspace-header/header';
import * as styles from '../../blocksuite/workspace-header/styles.css';
import { QuickSearchButton } from '../quick-search-button';
export interface WorkspaceTitleProps
extends React.PropsWithChildren<HeaderProps> {
icon?: ReactNode;
}
export const WorkspaceTitle = ({
icon,
children,
...props
}: WorkspaceTitleProps) => {
const setOpenQuickSearch = useSetAtom(openQuickSearchModalAtom);
return (
<Header {...props}>
<div className={styles.pageListTitleWrapper}>
<div className={styles.pageListTitleIcon}>{icon}</div>
{children}
<QuickSearchButton
onClick={() => {
setOpenQuickSearch(true);
}}
/>
</div>
</Header>
);
};
export const WorkspaceModeFilterTab = ({ ...props }: WorkspaceTitleProps) => {
const t = useAFFiNEI18N();
const [value, setMode] = useAtom(allPageModeSelectAtom);

View File

@@ -7,8 +7,6 @@ import {
import type { Collection } from '@affine/env/filter';
import type { WorkspaceHeaderProps } from '@affine/env/workspace';
import { WorkspaceFlavour, WorkspaceSubPath } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { SettingsIcon } from '@blocksuite/icons';
import type { ReactElement } from 'react';
import { useCallback } from 'react';
@@ -16,14 +14,13 @@ import { useGetPageInfoById } from '../hooks/use-get-page-info';
import { useWorkspace } from '../hooks/use-workspace';
import { BlockSuiteEditorHeader } from './blocksuite/workspace-header';
import { filterContainerStyle } from './filter-container.css';
import { WorkspaceModeFilterTab, WorkspaceTitle } from './pure/workspace-title';
import { WorkspaceModeFilterTab } from './pure/workspace-title';
export function WorkspaceHeader({
currentWorkspaceId,
currentEntry,
}: WorkspaceHeaderProps<WorkspaceFlavour>): ReactElement {
const setting = useCollectionManager(currentWorkspaceId);
const t = useAFFiNEI18N();
const saveToCollection = useCallback(
async (collection: Collection) => {
await setting.saveCollection(collection);
@@ -89,17 +86,6 @@ export function WorkspaceHeader({
{filterContainer}
</>
);
} else if (currentEntry.subPath === WorkspaceSubPath.SETTING) {
return (
<WorkspaceTitle
workspace={currentWorkspace}
currentPage={null}
isPublic={false}
icon={<SettingsIcon />}
>
{t['Workspace Settings']()}
</WorkspaceTitle>
);
} else if (
currentEntry.subPath === WorkspaceSubPath.SHARED ||
currentEntry.subPath === WorkspaceSubPath.TRASH