refactor: delete page style (#4347)

Co-authored-by: Alex Yang <himself65@outlook.com>
This commit is contained in:
JimmFly
2023-09-16 08:55:56 +08:00
committed by GitHub
parent b9656b1e22
commit d3635208f6
8 changed files with 201 additions and 60 deletions

View File

@@ -1,18 +1,21 @@
import { WorkspaceSubPath } from '@affine/env/workspace'; import { WorkspaceSubPath } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { assertExists } from '@blocksuite/global/utils'; import { assertExists } from '@blocksuite/global/utils';
import { DeleteIcon, ResetIcon } from '@blocksuite/icons';
import { Button } from '@toeverything/components/button'; import { Button } from '@toeverything/components/button';
import { ConfirmModal } from '@toeverything/components/modal'; import { ConfirmModal } from '@toeverything/components/modal';
import { Tooltip } from '@toeverything/components/tooltip';
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta'; import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
import { currentPageIdAtom } from '@toeverything/infra/atom'; import { currentPageIdAtom } from '@toeverything/infra/atom';
import { useAtomValue } from 'jotai'; import { useAtomValue } from 'jotai';
import { useCallback, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import { useAppSetting } from '../../../atoms/settings';
import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper'; import { useBlockSuiteMetaHelper } from '../../../hooks/affine/use-block-suite-meta-helper';
import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace'; import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
import { useNavigateHelper } from '../../../hooks/use-navigate-helper'; import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
import { toast } from '../../../utils'; import { toast } from '../../../utils';
import { buttonContainer, group } from './styles.css'; import * as styles from './styles.css';
export const TrashButtonGroup = () => { export const TrashButtonGroup = () => {
// fixme(himself65): remove these hooks ASAP // fixme(himself65): remove these hooks ASAP
@@ -26,57 +29,124 @@ export const TrashButtonGroup = () => {
); );
assertExists(pageMeta); assertExists(pageMeta);
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const [appSettings] = useAppSetting();
const { jumpToSubPath } = useNavigateHelper(); const { jumpToSubPath } = useNavigateHelper();
const { restoreFromTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace); const { restoreFromTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace);
const restoreRef = useRef(null);
const deleteRef = useRef(null);
const hintTextRef = useRef(null);
const wrapperRef = useRef<HTMLDivElement | null>(null);
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [width, setWidth] = useState(0);
const hintText =
'This page has been moved to the trash, you can either restore or permanently delete it.';
useEffect(() => {
const currentRef = wrapperRef.current;
if (!currentRef) {
return;
}
const handleResize = () => {
if (!currentRef) {
return;
}
const wrapperWidth = currentRef?.offsetWidth || 0;
setWidth(wrapperWidth);
};
const resizeObserver = new ResizeObserver(handleResize);
resizeObserver.observe(currentRef);
return () => {
resizeObserver.unobserve(currentRef);
};
}, []);
return ( return (
<div className={group}> <div ref={wrapperRef} style={{ width: '100%' }}>
<div className={buttonContainer}> <div
<Button className={styles.deleteHintContainer}
data-testid="page-restore-button" style={{
type="primary" width: `${width}px`,
onClick={() => {
restoreFromTrash(pageId);
toast(
t['com.affine.toastMessage.restored']({
title: pageMeta.title || 'Untitled',
})
);
}}
size="large"
>
{t['com.affine.trashOperation.restoreIt']()}
</Button>
</div>
<div className={buttonContainer}>
<Button
type="error"
onClick={() => {
setOpen(true);
}}
size="large"
>
{t['com.affine.trashOperation.deletePermanently']()}
</Button>
</div>
<ConfirmModal
title={t['com.affine.trashOperation.delete.title']()}
cancelText={t['com.affine.confirmModal.button.cancel']()}
description={t['com.affine.trashOperation.delete.description']()}
confirmButtonOptions={{
type: 'error',
children: t['com.affine.trashOperation.delete'](),
}} }}
open={open} data-has-background={!appSettings.clientBorder}
onConfirm={useCallback(() => { >
jumpToSubPath(workspace.id, WorkspaceSubPath.ALL); <Tooltip
blockSuiteWorkspace.removePage(pageId); content={hintText}
toast(t['com.affine.toastMessage.permanentlyDeleted']()); portalOptions={{
}, [blockSuiteWorkspace, jumpToSubPath, pageId, workspace.id, t])} container: hintTextRef.current,
onOpenChange={setOpen} }}
/> options={{ style: { whiteSpace: 'break-spaces' } }}
>
<div ref={hintTextRef} className={styles.deleteHintText}>
{hintText}
</div>
</Tooltip>
<div className={styles.group}>
<Tooltip
content={t['com.affine.trashOperation.restoreIt']()}
portalOptions={{
container: restoreRef.current,
}}
>
<Button
ref={restoreRef}
data-testid="page-restore-button"
type="primary"
onClick={() => {
restoreFromTrash(pageId);
toast(
t['com.affine.toastMessage.restored']({
title: pageMeta.title || 'Untitled',
})
);
}}
className={styles.buttonContainer}
>
<div className={styles.icon}>
<ResetIcon />
</div>
</Button>
</Tooltip>
<Tooltip
content={t['com.affine.trashOperation.deletePermanently']()}
portalOptions={{ container: deleteRef.current }}
>
<Button
ref={deleteRef}
type="error"
onClick={() => {
setOpen(true);
}}
style={{ color: 'var(--affine-pure-white)' }}
className={styles.buttonContainer}
>
<div className={styles.icon}>
<DeleteIcon />
</div>
</Button>
</Tooltip>
</div>
<ConfirmModal
title={t['com.affine.trashOperation.delete.title']()}
cancelText={t['com.affine.confirmModal.button.cancel']()}
description={t['com.affine.trashOperation.delete.description']()}
confirmButtonOptions={{
type: 'error',
children: t['com.affine.trashOperation.delete'](),
}}
open={open}
onConfirm={useCallback(() => {
jumpToSubPath(workspace.id, WorkspaceSubPath.ALL);
blockSuiteWorkspace.removePage(pageId);
toast(t['com.affine.toastMessage.permanentlyDeleted']());
}, [blockSuiteWorkspace, jumpToSubPath, pageId, workspace.id, t])}
onOpenChange={setOpen}
/>
</div>
</div> </div>
); );
}; };

View File

@@ -1,16 +1,46 @@
import { style } from '@vanilla-extract/css'; import { style } from '@vanilla-extract/css';
export const group = style({ export const group = style({
width: '100%',
position: 'absolute',
bottom: '100px',
left: '0',
display: 'flex', display: 'flex',
gap: '24px', gap: '16px',
justifyContent: 'center', justifyContent: 'center',
});
export const deleteHintContainer = style({
position: 'fixed',
zIndex: 2, zIndex: 2,
padding: '14px 20px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
bottom: '0',
gap: '16px',
backgroundColor: 'var(--affine-background-primary-color)',
borderTop: '1px solid var(--affine-border-color)',
selectors: {
'&[data-has-background="false"]': {
backgroundColor: 'transparent',
borderTop: 'none',
padding: '14px 0',
},
},
});
export const deleteHintText = style({
fontSize: '15px',
fontWeight: '500',
lineHeight: '24px',
color: 'var(--affine-text-secondary-color)',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
cursor: 'pointer',
}); });
export const buttonContainer = style({ export const buttonContainer = style({
boxShadow: 'var(--affine-float-button-shadow-2)', color: 'var(--affine-pure-white)',
borderRadius: '8px', padding: '8px 18px',
fontSize: '20px',
height: '36px',
});
export const icon = style({
display: 'flex',
alignContent: 'center',
}); });

View File

@@ -28,6 +28,7 @@ import {
useSensor, useSensor,
useSensors, useSensors,
} from '@dnd-kit/core'; } from '@dnd-kit/core';
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
import { usePassiveWorkspaceEffect } from '@toeverything/infra/__internal__/react'; import { usePassiveWorkspaceEffect } from '@toeverything/infra/__internal__/react';
import { currentWorkspaceIdAtom } from '@toeverything/infra/atom'; import { currentWorkspaceIdAtom } from '@toeverything/infra/atom';
import { useAtom, useAtomValue, useSetAtom } from 'jotai'; import { useAtom, useAtomValue, useSetAtom } from 'jotai';
@@ -228,7 +229,10 @@ export const WorkspaceLayoutInner = ({
const [appSetting] = useAppSetting(); const [appSetting] = useAppSetting();
const location = useLocation(); const location = useLocation();
const { pageId } = useParams(); const { pageId } = useParams();
const pageMeta = useBlockSuitePageMeta(
currentWorkspace.blockSuiteWorkspace
).find(meta => meta.id === pageId);
const inTrashPage = pageMeta?.trash ?? false;
const setMainContainer = useSetAtom(mainContainerAtom); const setMainContainer = useSetAtom(mainContainerAtom);
return ( return (
@@ -262,9 +266,10 @@ export const WorkspaceLayoutInner = ({
<MainContainer <MainContainer
ref={setMainContainer} ref={setMainContainer}
padding={appSetting.clientBorder} padding={appSetting.clientBorder}
inTrashPage={inTrashPage}
> >
{incompatible ? <MigrationFallback /> : children} {incompatible ? <MigrationFallback /> : children}
<ToolContainer> <ToolContainer inTrashPage={inTrashPage}>
<BlockHubWrapper blockHubAtom={rootBlockHubAtom} /> <BlockHubWrapper blockHubAtom={rootBlockHubAtom} />
<HelpIsland showList={pageId ? undefined : showList} /> <HelpIsland showList={pageId ? undefined : showList} />
</ToolContainer> </ToolContainer>

View File

@@ -11,6 +11,7 @@ export const sidebarSwitch = style({
opacity: 1, opacity: 1,
width: '32px', width: '32px',
flexShrink: 0, flexShrink: 0,
fontSize: '24px',
pointerEvents: 'auto', pointerEvents: 'auto',
}, },
}, },

View File

@@ -86,6 +86,12 @@ export const mainContainerStyle = style({
'&[data-show-padding="true"][data-is-macos="true"]': { '&[data-show-padding="true"][data-is-macos="true"]': {
borderRadius: '6px', borderRadius: '6px',
}, },
'&[data-in-trash-page="true"]': {
marginBottom: '66px',
},
'&[data-in-trash-page="true"][data-show-padding="true"]': {
marginBottom: '66px',
},
'&[data-show-padding="true"]:before': { '&[data-show-padding="true"]:before': {
content: '""', content: '""',
position: 'absolute', position: 'absolute',
@@ -151,4 +157,20 @@ export const toolStyle = style({
display: 'none', display: 'none',
}, },
}, },
selectors: {
'&[data-in-trash-page="true"]': {
bottom: '70px',
'@media': {
[breakpoints.down('md', true)]: {
bottom: '80px',
},
[breakpoints.down('sm', true)]: {
bottom: '85px',
},
print: {
display: 'none',
},
},
},
},
}); });

View File

@@ -35,13 +35,14 @@ export const AppContainer = ({
export interface MainContainerProps extends HTMLAttributes<HTMLDivElement> { export interface MainContainerProps extends HTMLAttributes<HTMLDivElement> {
className?: string; className?: string;
padding?: boolean; padding?: boolean;
inTrashPage?: boolean;
} }
export const MainContainer = forwardRef< export const MainContainer = forwardRef<
HTMLDivElement, HTMLDivElement,
PropsWithChildren<MainContainerProps> PropsWithChildren<MainContainerProps>
>(function MainContainer( >(function MainContainer(
{ className, padding, children, ...props }, { className, padding, inTrashPage, children, ...props },
ref ref
): ReactElement { ): ReactElement {
return ( return (
@@ -50,6 +51,7 @@ export const MainContainer = forwardRef<
className={clsx(mainContainerStyle, className)} className={clsx(mainContainerStyle, className)}
data-is-macos={environment.isDesktop && environment.isMacOs} data-is-macos={environment.isDesktop && environment.isMacOs}
data-show-padding={!!padding} data-show-padding={!!padding}
data-in-trash-page={!!inTrashPage}
ref={ref} ref={ref}
> >
{children} {children}
@@ -59,8 +61,14 @@ export const MainContainer = forwardRef<
MainContainer.displayName = 'MainContainer'; MainContainer.displayName = 'MainContainer';
export const ToolContainer = (props: PropsWithChildren): ReactElement => { export const ToolContainer = (
return <div className={toolStyle}>{props.children}</div>; props: PropsWithChildren & { inTrashPage: boolean }
): ReactElement => {
return (
<div className={toolStyle} data-in-trash-page={!!props.inTrashPage}>
{props.children}
</div>
);
}; };
export const WorkspaceFallback = (): ReactElement => { export const WorkspaceFallback = (): ReactElement => {

View File

@@ -70,7 +70,7 @@ export function useIsTinyScreen({
resizeObserver.observe(mainContainer); resizeObserver.observe(mainContainer);
return () => { return () => {
resizeObserver.disconnect(); resizeObserver.unobserve(mainContainer);
}; };
}, [ }, [
centerDom, centerDom,

View File

@@ -60,7 +60,12 @@ export const HeaderItem = ({
}} }}
> >
<IconButton <IconButton
size="large"
ref={setContainer} ref={setContainer}
style={{
width: '32px',
fontSize: '24px',
}}
onClick={useCallback(() => { onClick={useCallback(() => {
if (!open) { if (!open) {
setOpen(true); setOpen(true);