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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 201 additions and 60 deletions

View File

@ -1,18 +1,21 @@
import { WorkspaceSubPath } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { assertExists } from '@blocksuite/global/utils';
import { DeleteIcon, ResetIcon } from '@blocksuite/icons';
import { Button } from '@toeverything/components/button';
import { ConfirmModal } from '@toeverything/components/modal';
import { Tooltip } from '@toeverything/components/tooltip';
import { useBlockSuitePageMeta } from '@toeverything/hooks/use-block-suite-page-meta';
import { currentPageIdAtom } from '@toeverything/infra/atom';
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 { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace';
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
import { toast } from '../../../utils';
import { buttonContainer, group } from './styles.css';
import * as styles from './styles.css';
export const TrashButtonGroup = () => {
// fixme(himself65): remove these hooks ASAP
@ -26,57 +29,124 @@ export const TrashButtonGroup = () => {
);
assertExists(pageMeta);
const t = useAFFiNEI18N();
const [appSettings] = useAppSetting();
const { jumpToSubPath } = useNavigateHelper();
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 [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 (
<div className={group}>
<div className={buttonContainer}>
<Button
data-testid="page-restore-button"
type="primary"
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'](),
<div ref={wrapperRef} style={{ width: '100%' }}>
<div
className={styles.deleteHintContainer}
style={{
width: `${width}px`,
}}
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}
/>
data-has-background={!appSettings.clientBorder}
>
<Tooltip
content={hintText}
portalOptions={{
container: hintTextRef.current,
}}
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>
);
};

View File

@ -1,16 +1,46 @@
import { style } from '@vanilla-extract/css';
export const group = style({
width: '100%',
position: 'absolute',
bottom: '100px',
left: '0',
display: 'flex',
gap: '24px',
gap: '16px',
justifyContent: 'center',
});
export const deleteHintContainer = style({
position: 'fixed',
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({
boxShadow: 'var(--affine-float-button-shadow-2)',
borderRadius: '8px',
color: 'var(--affine-pure-white)',
padding: '8px 18px',
fontSize: '20px',
height: '36px',
});
export const icon = style({
display: 'flex',
alignContent: 'center',
});

View File

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

View File

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

View File

@ -86,6 +86,12 @@ export const mainContainerStyle = style({
'&[data-show-padding="true"][data-is-macos="true"]': {
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': {
content: '""',
position: 'absolute',
@ -151,4 +157,20 @@ export const toolStyle = style({
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> {
className?: string;
padding?: boolean;
inTrashPage?: boolean;
}
export const MainContainer = forwardRef<
HTMLDivElement,
PropsWithChildren<MainContainerProps>
>(function MainContainer(
{ className, padding, children, ...props },
{ className, padding, inTrashPage, children, ...props },
ref
): ReactElement {
return (
@ -50,6 +51,7 @@ export const MainContainer = forwardRef<
className={clsx(mainContainerStyle, className)}
data-is-macos={environment.isDesktop && environment.isMacOs}
data-show-padding={!!padding}
data-in-trash-page={!!inTrashPage}
ref={ref}
>
{children}
@ -59,8 +61,14 @@ export const MainContainer = forwardRef<
MainContainer.displayName = 'MainContainer';
export const ToolContainer = (props: PropsWithChildren): ReactElement => {
return <div className={toolStyle}>{props.children}</div>;
export const ToolContainer = (
props: PropsWithChildren & { inTrashPage: boolean }
): ReactElement => {
return (
<div className={toolStyle} data-in-trash-page={!!props.inTrashPage}>
{props.children}
</div>
);
};
export const WorkspaceFallback = (): ReactElement => {

View File

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

View File

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