mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-11-27 17:32:37 +03:00
refactor: delete page style (#4347)
Co-authored-by: Alex Yang <himself65@outlook.com>
This commit is contained in:
parent
b9656b1e22
commit
d3635208f6
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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',
|
||||
});
|
||||
|
@ -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>
|
||||
|
@ -11,6 +11,7 @@ export const sidebarSwitch = style({
|
||||
opacity: 1,
|
||||
width: '32px',
|
||||
flexShrink: 0,
|
||||
fontSize: '24px',
|
||||
pointerEvents: 'auto',
|
||||
},
|
||||
},
|
||||
|
@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -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 => {
|
||||
|
@ -70,7 +70,7 @@ export function useIsTinyScreen({
|
||||
resizeObserver.observe(mainContainer);
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
resizeObserver.unobserve(mainContainer);
|
||||
};
|
||||
}, [
|
||||
centerDom,
|
||||
|
@ -60,7 +60,12 @@ export const HeaderItem = ({
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
size="large"
|
||||
ref={setContainer}
|
||||
style={{
|
||||
width: '32px',
|
||||
fontSize: '24px',
|
||||
}}
|
||||
onClick={useCallback(() => {
|
||||
if (!open) {
|
||||
setOpen(true);
|
||||
|
Loading…
Reference in New Issue
Block a user