From ba875a120fabcc5a9a55b26984dc167f693f400b Mon Sep 17 00:00:00 2001 From: zzj3720 <17165520+zzj3720@users.noreply.github.com> Date: Wed, 9 Apr 2025 10:50:41 +0000 Subject: [PATCH] feat(editor): support image preview for attachment columns (#11544) close: BS-2634 --- .../database-block/properties/file/view.tsx | 105 +++- .../modules/peek-view/entities/peek-view.ts | 23 +- .../peek-view/view/image-preview/index.tsx | 491 ++++++++++-------- .../peek-view/view/peek-view-manager.tsx | 15 +- 4 files changed, 386 insertions(+), 248 deletions(-) diff --git a/packages/frontend/core/src/blocksuite/database-block/properties/file/view.tsx b/packages/frontend/core/src/blocksuite/database-block/properties/file/view.tsx index 8f2d04ac91..75ac7f2192 100644 --- a/packages/frontend/core/src/blocksuite/database-block/properties/file/view.tsx +++ b/packages/frontend/core/src/blocksuite/database-block/properties/file/view.tsx @@ -1,6 +1,7 @@ import { Popover, uniReactRoot } from '@affine/component'; import { Button } from '@affine/component/ui/button'; import { Menu, MenuItem } from '@affine/component/ui/menu'; +import { PeekViewService } from '@affine/core/modules/peek-view'; import { type Cell, type CellRenderProps, @@ -40,6 +41,7 @@ import { import { WorkspaceDialogService } from '../../../../modules/dialogs'; import { useSignalValue } from '../../../../modules/doc-info/utils'; +import type { ImageData } from '../../../../modules/peek-view/view/image-preview'; import { CircularProgress } from '../../components/loading'; import { progressIconContainer } from '../../components/loading.css'; import type { @@ -216,7 +218,8 @@ class FileCellManager { } constructor( - props: CellRenderProps<{}, FileCellRawValueType, FileCellJsonValueType> + props: CellRenderProps<{}, FileCellRawValueType, FileCellJsonValueType>, + private readonly peekViewService: PeekViewService ) { this.cell = props.cell; this.selectCurrentCell = props.selectCurrentCell; @@ -298,6 +301,46 @@ class FileCellManager { a.order > b.order ? 1 : -1 ); }); + + openPreview = (id: string) => { + const imageList = this.fileList.value + .filter(v => v.type === 'done') + .map(v => ({ + ...v, + ...this.fileUploadManager?.getFileInfo(v.id).value, + })) + .filter(v => SUPPORTED_IMAGE_MIME_TYPES.has(v?.fileType?.mime ?? '')); + const getImageData = (index: number): ImageData | undefined => { + const file = imageList[index]; + if (!file) return; + const previousIndex = index - 1; + const nextIndex = index + 1; + const hasPrevious = previousIndex >= 0; + const hasNext = nextIndex < imageList.length; + return { + index, + url: file.url ?? '', + caption: file.name, + previous: hasPrevious ? () => getImageData(previousIndex) : undefined, + next: hasNext ? () => getImageData(nextIndex) : undefined, + }; + }; + const currentIndex = imageList.findIndex(v => v.id === id); + if (currentIndex === -1) return; + const imageData = getImageData(currentIndex); + if (!imageData) return; + this.peekViewService.peekView + .open({ + type: 'image-list', + data: { + image: imageData, + total: imageList.length, + }, + }) + .catch(error => { + console.error('Failed to open image list', error); + }); + }; } const SUPPORTED_IMAGE_MIME_TYPES = new Set([ @@ -317,8 +360,9 @@ const FileCellComponent: ForwardRefRenderFunction< DataViewCellLifeCycle, CellRenderProps<{}, FileCellRawValueType, FileCellJsonValueType> > = (props, ref): ReactNode => { + const peekView = useService(PeekViewService); // eslint-disable-next-line react-hooks/exhaustive-deps - const manager = useMemo(() => new FileCellManager(props), []); + const manager = useMemo(() => new FileCellManager(props, peekView), []); useEffect(() => { return () => { @@ -396,7 +440,7 @@ const FileCellComponent: ForwardRefRenderFunction< key={file.id} file={file} handleRemoveFile={manager.removeFile} - fileUploadManager={manager.fileUploadManager} + manager={manager} /> ))} @@ -440,10 +484,7 @@ const FileCellComponent: ForwardRefRenderFunction<