From b29cb5945aad1a5755002d843c2c5905d16cf24c Mon Sep 17 00:00:00 2001 From: Saul-Mirone Date: Tue, 24 Dec 2024 02:20:03 +0000 Subject: [PATCH] refactor(editor): move file drop manager to components (#9264) --- .../src/drag-indicator/drag-indicator.ts | 44 ++++ .../src/drag-indicator}/file-drop-manager.ts | 193 ++++++++++-------- .../components/src/drag-indicator/index.ts | 51 +---- .../blocks/src/_common/components/index.ts | 1 - .../attachment-block/attachment-service.ts | 81 ++++---- .../src/attachment-block/attachment-spec.ts | 2 + .../blocks/src/image-block/image-service.ts | 87 ++++---- .../blocks/src/image-block/image-spec.ts | 7 +- .../root-block/edgeless/edgeless-root-spec.ts | 2 + .../src/root-block/page/page-root-spec.ts | 2 + .../blocks/src/root-block/root-service.ts | 22 -- .../watchers/drag-event-watcher.ts | 6 + .../block-std/src/event/control/pointer.ts | 18 ++ .../block-std/src/event/dispatcher.ts | 14 +- blocksuite/tests-legacy/drag.spec.ts | 46 ++--- .../tests-legacy/embed-synced-doc.spec.ts | 6 +- 16 files changed, 321 insertions(+), 261 deletions(-) create mode 100644 blocksuite/affine/components/src/drag-indicator/drag-indicator.ts rename blocksuite/{blocks/src/_common/components => affine/components/src/drag-indicator}/file-drop-manager.ts (50%) diff --git a/blocksuite/affine/components/src/drag-indicator/drag-indicator.ts b/blocksuite/affine/components/src/drag-indicator/drag-indicator.ts new file mode 100644 index 0000000000..5a877ba3c4 --- /dev/null +++ b/blocksuite/affine/components/src/drag-indicator/drag-indicator.ts @@ -0,0 +1,44 @@ +import type { Rect } from '@blocksuite/global/utils'; +import { css, html, LitElement } from 'lit'; +import { property } from 'lit/decorators.js'; +import { styleMap } from 'lit/directives/style-map.js'; + +export class DragIndicator extends LitElement { + static override styles = css` + .affine-drag-indicator { + position: absolute; + top: 0; + left: 0; + background: var(--affine-primary-color); + transition-property: width, height, transform; + transition-duration: 100ms; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-delay: 0s; + transform-origin: 0 0; + pointer-events: none; + z-index: 2; + } + `; + + override render() { + if (!this.rect) { + return null; + } + const { left, top, width, height } = this.rect; + const style = styleMap({ + width: `${width}px`, + height: `${height}px`, + transform: `translate(${left}px, ${top}px)`, + }); + return html`
`; + } + + @property({ attribute: false }) + accessor rect: Rect | null = null; +} + +declare global { + interface HTMLElementTagNameMap { + 'affine-drag-indicator': DragIndicator; + } +} diff --git a/blocksuite/blocks/src/_common/components/file-drop-manager.ts b/blocksuite/affine/components/src/drag-indicator/file-drop-manager.ts similarity index 50% rename from blocksuite/blocks/src/_common/components/file-drop-manager.ts rename to blocksuite/affine/components/src/drag-indicator/file-drop-manager.ts index de31ed5607..15e5bfd73c 100644 --- a/blocksuite/blocks/src/_common/components/file-drop-manager.ts +++ b/blocksuite/affine/components/src/drag-indicator/file-drop-manager.ts @@ -1,4 +1,3 @@ -import type { DragIndicator } from '@blocksuite/affine-components/drag-indicator'; import { calcDropTarget, type DropResult, @@ -6,12 +5,21 @@ import { isInsidePageEditor, matchFlavours, } from '@blocksuite/affine-shared/utils'; -import type { BlockService, EditorHost } from '@blocksuite/block-std'; +import { + type BlockStdScope, + type EditorHost, + type ExtensionType, + LifeCycleWatcher, +} from '@blocksuite/block-std'; +import { createIdentifier } from '@blocksuite/global/di'; import type { IVec } from '@blocksuite/global/utils'; -import { assertExists, Point } from '@blocksuite/global/utils'; +import { Point } from '@blocksuite/global/utils'; import type { BlockModel } from '@blocksuite/store'; +import type { DragIndicator } from './index.js'; + export type onDropProps = { + std: BlockStdScope; files: File[]; targetModel: BlockModel | null; place: 'before' | 'after'; @@ -20,54 +28,32 @@ export type onDropProps = { export type FileDropOptions = { flavour: string; - onDrop?: ({ - files, - targetModel, - place, - point, - }: onDropProps) => Promise | void; + onDrop?: (onDropProps: onDropProps) => boolean; }; -export class FileDropManager { - private static _dropResult: DropResult | null = null; +export class FileDropExtension extends LifeCycleWatcher { + static override readonly key = 'FileDropExtension'; - private readonly _blockService: BlockService; + static dropResult: DropResult | null = null; - private readonly _fileDropOptions: FileDropOptions; + static get indicator() { + let indicator = document.querySelector( + 'affine-drag-indicator' + ); - private readonly _indicator!: DragIndicator; + if (!indicator) { + indicator = document.createElement( + 'affine-drag-indicator' + ) as DragIndicator; + document.body.append(indicator); + } - private readonly _onDrop = (event: DragEvent) => { - this._indicator.rect = null; - - const { onDrop } = this._fileDropOptions; - if (!onDrop) return; - - const dataTransfer = event.dataTransfer; - if (!dataTransfer) return; - - const effectAllowed = dataTransfer.effectAllowed; - if (effectAllowed === 'none') return; - - const droppedFiles = dataTransfer.files; - if (!droppedFiles || !droppedFiles.length) return; - - event.preventDefault(); - - const { targetModel, type: place } = this; - const { x, y } = event; - - onDrop({ - files: [...droppedFiles], - targetModel, - place, - point: [x, y], - })?.catch(console.error); - }; + return indicator; + } onDragLeave = () => { - FileDropManager._dropResult = null; - this._indicator.rect = null; + FileDropExtension.dropResult = null; + FileDropExtension.indicator.rect = null; }; onDragOver = (event: DragEvent) => { @@ -86,40 +72,32 @@ export class FileDropManager { let result: DropResult | null = null; if (element) { const model = element.model; - const parent = this.doc.getParent(model); - if (!matchFlavours(parent, ['affine:surface'])) { + const parent = this.std.doc.getParent(model); + if (!matchFlavours(parent, ['affine:surface' as BlockSuite.Flavour])) { result = calcDropTarget(point, model, element); } } if (result) { - FileDropManager._dropResult = result; - this._indicator.rect = result.rect; + FileDropExtension.dropResult = result; + FileDropExtension.indicator.rect = result.rect; } else { - FileDropManager._dropResult = null; - this._indicator.rect = null; + FileDropExtension.dropResult = null; + FileDropExtension.indicator.rect = null; } }; - get doc() { - return this._blockService.doc; - } - - get editorHost(): EditorHost { - return this._blockService.std.host; - } - get targetModel(): BlockModel | null { - let targetModel = FileDropManager._dropResult?.modelState.model || null; + let targetModel = FileDropExtension.dropResult?.modelState.model || null; if (!targetModel && isInsidePageEditor(this.editorHost)) { const rootModel = this.doc.root; - assertExists(rootModel); + if (!rootModel) return null; let lastNote = rootModel.children[rootModel.children.length - 1]; if (!lastNote || !matchFlavours(lastNote, ['affine:note'])) { const newNoteId = this.doc.addBlock('affine:note', {}, rootModel.id); const newNote = this.doc.getBlockById(newNoteId); - assertExists(newNote); + if (!newNote) return null; lastNote = newNote; } @@ -134,40 +112,93 @@ export class FileDropManager { 0 ); const newParagraph = this.doc.getBlockById(newParagraphId); - assertExists(newParagraph); + if (!newParagraph) return null; targetModel = newParagraph; } } return targetModel; } + get doc() { + return this.std.doc; + } + + get editorHost(): EditorHost { + return this.std.host; + } + get type(): 'before' | 'after' { - return !FileDropManager._dropResult || - FileDropManager._dropResult.type !== 'before' + return !FileDropExtension.dropResult || + FileDropExtension.dropResult.type !== 'before' ? 'after' : 'before'; } - constructor(blockService: BlockService, fileDropOptions: FileDropOptions) { - this._blockService = blockService; - this._fileDropOptions = fileDropOptions; + private readonly _onDrop = (event: DragEvent, options: FileDropOptions) => { + FileDropExtension.indicator.rect = null; - this._indicator = document.querySelector( - 'affine-drag-indicator' - ) as DragIndicator; - if (!this._indicator) { - this._indicator = document.createElement( - 'affine-drag-indicator' - ) as DragIndicator; - document.body.append(this._indicator); - } + const { onDrop } = options; + if (!onDrop) return; - if (fileDropOptions.onDrop) { - this._blockService.disposables.addFromEvent( - this._blockService.std.host, - 'drop', - this._onDrop - ); + const dataTransfer = event.dataTransfer; + if (!dataTransfer) return; + + const effectAllowed = dataTransfer.effectAllowed; + if (effectAllowed === 'none') return; + + const droppedFiles = dataTransfer.files; + if (!droppedFiles || !droppedFiles.length) return; + + const { targetModel, type: place } = this; + const { x, y } = event; + + const drop = onDrop({ + std: this.std, + files: [...droppedFiles], + targetModel, + place, + point: [x, y], + }); + + if (drop) { + event.preventDefault(); } + return drop; + }; + + override mounted() { + super.mounted(); + const std = this.std; + + std.event.add('nativeDragOver', context => { + const event = context.get('dndState'); + this.onDragOver(event.raw); + }); + std.event.add('nativeDragLeave', () => { + this.onDragLeave(); + }); + std.provider.getAll(FileDropConfigExtensionIdentifier).forEach(options => { + if (options.onDrop) { + std.event.add('nativeDrop', context => { + const event = context.get('dndState'); + return this._onDrop(event.raw, options); + }); + } + }); } } + +const FileDropConfigExtensionIdentifier = createIdentifier( + 'FileDropConfigExtension' +); + +export const FileDropConfigExtension = ( + options: FileDropOptions +): ExtensionType => { + const identifier = FileDropConfigExtensionIdentifier(options.flavour); + return { + setup: di => { + di.addImpl(identifier, () => options); + }, + }; +}; diff --git a/blocksuite/affine/components/src/drag-indicator/index.ts b/blocksuite/affine/components/src/drag-indicator/index.ts index e71ec34685..db2f29909a 100644 --- a/blocksuite/affine/components/src/drag-indicator/index.ts +++ b/blocksuite/affine/components/src/drag-indicator/index.ts @@ -1,47 +1,12 @@ -import type { Rect } from '@blocksuite/global/utils'; -import { css, html, LitElement } from 'lit'; -import { property } from 'lit/decorators.js'; -import { styleMap } from 'lit/directives/style-map.js'; +import { DragIndicator } from './drag-indicator.js'; +export { + FileDropConfigExtension, + FileDropExtension, + type FileDropOptions, + type onDropProps, +} from './file-drop-manager.js'; -export class DragIndicator extends LitElement { - static override styles = css` - .affine-drag-indicator { - position: absolute; - top: 0; - left: 0; - background: var(--affine-primary-color); - transition-property: width, height, transform; - transition-duration: 100ms; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-delay: 0s; - transform-origin: 0 0; - pointer-events: none; - z-index: 2; - } - `; - - override render() { - if (!this.rect) { - return null; - } - const { left, top, width, height } = this.rect; - const style = styleMap({ - width: `${width}px`, - height: `${height}px`, - transform: `translate(${left}px, ${top}px)`, - }); - return html`
`; - } - - @property({ attribute: false }) - accessor rect: Rect | null = null; -} - -declare global { - interface HTMLElementTagNameMap { - 'affine-drag-indicator': DragIndicator; - } -} +export { DragIndicator }; export function effects() { customElements.define('affine-drag-indicator', DragIndicator); diff --git a/blocksuite/blocks/src/_common/components/index.ts b/blocksuite/blocks/src/_common/components/index.ts index 51fd439976..19d52716ce 100644 --- a/blocksuite/blocks/src/_common/components/index.ts +++ b/blocksuite/blocks/src/_common/components/index.ts @@ -1,6 +1,5 @@ export * from './ai-item/index.js'; export * from './block-selection.js'; export * from './block-zero-width.js'; -export * from './file-drop-manager.js'; export * from './menu-divider.js'; export { scrollbarStyle } from './utils.js'; diff --git a/blocksuite/blocks/src/attachment-block/attachment-service.ts b/blocksuite/blocks/src/attachment-block/attachment-service.ts index d4d7ec14ef..c40c2cf9f6 100644 --- a/blocksuite/blocks/src/attachment-block/attachment-service.ts +++ b/blocksuite/blocks/src/attachment-block/attachment-service.ts @@ -1,3 +1,4 @@ +import { FileDropConfigExtension } from '@blocksuite/affine-components/drag-indicator'; import { AttachmentBlockSchema } from '@blocksuite/affine-model'; import { DragHandleConfigExtension, @@ -13,65 +14,63 @@ import { import { BlockService } from '@blocksuite/block-std'; import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx'; -import { - FileDropManager, - type FileDropOptions, -} from '../_common/components/file-drop-manager.js'; import { EMBED_CARD_HEIGHT, EMBED_CARD_WIDTH } from '../_common/consts.js'; import { addAttachments } from '../root-block/edgeless/utils/common.js'; import type { AttachmentBlockComponent } from './attachment-block.js'; import { AttachmentEdgelessBlockComponent } from './attachment-edgeless-block.js'; import { addSiblingAttachmentBlocks } from './utils.js'; +// bytes.parse('2GB') +const maxFileSize = 2147483648; + export class AttachmentBlockService extends BlockService { static override readonly flavour = AttachmentBlockSchema.model.flavour; - private readonly _fileDropOptions: FileDropOptions = { - flavour: this.flavour, - onDrop: async ({ files, targetModel, place, point }) => { - if (!files.length) return false; + maxFileSize = maxFileSize; +} - // generic attachment block for all files except images - const attachmentFiles = files.filter( - file => !file.type.startsWith('image/') - ); +export const AttachmentDropOption = FileDropConfigExtension({ + flavour: AttachmentBlockSchema.model.flavour, + onDrop: ({ files, targetModel, place, point, std }) => { + // generic attachment block for all files except images + const attachmentFiles = files.filter( + file => !file.type.startsWith('image/') + ); - if (targetModel && !matchFlavours(targetModel, ['affine:surface'])) { - await addSiblingAttachmentBlocks( - this.host, - attachmentFiles, - this.maxFileSize, - targetModel, - place - ); - } else if (isInsideEdgelessEditor(this.host)) { - const gfx = this.std.get(GfxControllerIdentifier); - point = gfx.viewport.toViewCoordFromClientCoord(point); - await addAttachments(this.std, attachmentFiles, point); + if (!attachmentFiles.length) return false; - this.std.getOptional(TelemetryProvider)?.track('CanvasElementAdded', { - control: 'canvas:drop', - page: 'whiteboard editor', - module: 'toolbar', - segment: 'toolbar', - type: 'attachment', - }); - } + if (targetModel && !matchFlavours(targetModel, ['affine:surface'])) { + addSiblingAttachmentBlocks( + std.host, + attachmentFiles, + // TODO: use max file size from service + maxFileSize, + targetModel, + place + ).catch(console.error); return true; - }, - }; + } - fileDropManager!: FileDropManager; + if (isInsideEdgelessEditor(std.host)) { + const gfx = std.get(GfxControllerIdentifier); + point = gfx.viewport.toViewCoordFromClientCoord(point); + addAttachments(std, attachmentFiles, point).catch(console.error); - maxFileSize = 10 * 1000 * 1000; // 10MB (default) + std.getOptional(TelemetryProvider)?.track('CanvasElementAdded', { + control: 'canvas:drop', + page: 'whiteboard editor', + module: 'toolbar', + segment: 'toolbar', + type: 'attachment', + }); - override mounted(): void { - super.mounted(); + return true; + } - this.fileDropManager = new FileDropManager(this, this._fileDropOptions); - } -} + return false; + }, +}); export const AttachmentDragHandleOption = DragHandleConfigExtension({ flavour: AttachmentBlockSchema.model.flavour, diff --git a/blocksuite/blocks/src/attachment-block/attachment-spec.ts b/blocksuite/blocks/src/attachment-block/attachment-spec.ts index c968a5b49f..c603dfa566 100644 --- a/blocksuite/blocks/src/attachment-block/attachment-spec.ts +++ b/blocksuite/blocks/src/attachment-block/attachment-spec.ts @@ -9,6 +9,7 @@ import { AttachmentBlockNotionHtmlAdapterExtension } from './adapters/notion-htm import { AttachmentBlockService, AttachmentDragHandleOption, + AttachmentDropOption, } from './attachment-service.js'; import { AttachmentEmbedConfigExtension, @@ -23,6 +24,7 @@ export const AttachmentBlockSpec: ExtensionType[] = [ ? literal`affine-edgeless-attachment` : literal`affine-attachment`; }), + AttachmentDropOption, AttachmentDragHandleOption, AttachmentEmbedConfigExtension(), AttachmentEmbedService, diff --git a/blocksuite/blocks/src/image-block/image-service.ts b/blocksuite/blocks/src/image-block/image-service.ts index f4c8f5d67c..01a26515fc 100644 --- a/blocksuite/blocks/src/image-block/image-service.ts +++ b/blocksuite/blocks/src/image-block/image-service.ts @@ -1,3 +1,4 @@ +import { FileDropConfigExtension } from '@blocksuite/affine-components/drag-indicator'; import { ImageBlockSchema } from '@blocksuite/affine-model'; import { DragHandleConfigExtension, @@ -13,64 +14,60 @@ import { import { BlockService } from '@blocksuite/block-std'; import { GfxControllerIdentifier } from '@blocksuite/block-std/gfx'; -import { - FileDropManager, - type FileDropOptions, -} from '../_common/components/file-drop-manager.js'; import { setImageProxyMiddlewareURL } from '../_common/transformers/middlewares.js'; import { addImages } from '../root-block/edgeless/utils/common.js'; import type { ImageBlockComponent } from './image-block.js'; import { ImageEdgelessBlockComponent } from './image-edgeless-block.js'; import { addSiblingImageBlock } from './utils.js'; +// bytes.parse('2GB') +const maxFileSize = 2147483648; + export class ImageBlockService extends BlockService { static override readonly flavour = ImageBlockSchema.model.flavour; static setImageProxyURL = setImageProxyMiddlewareURL; - private readonly _fileDropOptions: FileDropOptions = { - flavour: this.flavour, - onDrop: async ({ files, targetModel, place, point }) => { - const imageFiles = files.filter(file => file.type.startsWith('image/')); - if (!imageFiles.length) return false; - - if (targetModel && !matchFlavours(targetModel, ['affine:surface'])) { - addSiblingImageBlock( - this.host, - imageFiles, - this.maxFileSize, - targetModel, - place - ); - } else if (isInsideEdgelessEditor(this.host)) { - const gfx = this.std.get(GfxControllerIdentifier); - point = gfx.viewport.toViewCoordFromClientCoord(point); - await addImages(this.std, files, point); - - this.std.getOptional(TelemetryProvider)?.track('CanvasElementAdded', { - control: 'canvas:drop', - page: 'whiteboard editor', - module: 'toolbar', - segment: 'toolbar', - type: 'image', - }); - } - - return true; - }, - }; - - fileDropManager!: FileDropManager; - - maxFileSize = 10 * 1000 * 1000; // 10MB (default) - - override mounted(): void { - super.mounted(); - - this.fileDropManager = new FileDropManager(this, this._fileDropOptions); - } + maxFileSize = maxFileSize; } +export const ImageDropOption = FileDropConfigExtension({ + flavour: ImageBlockSchema.model.flavour, + onDrop: ({ files, targetModel, place, point, std }) => { + const imageFiles = files.filter(file => file.type.startsWith('image/')); + if (!imageFiles.length) return false; + + if (targetModel && !matchFlavours(targetModel, ['affine:surface'])) { + addSiblingImageBlock( + std.host, + imageFiles, + // TODO: use max file size from service + maxFileSize, + targetModel, + place + ); + return true; + } + + if (isInsideEdgelessEditor(std.host)) { + const gfx = std.get(GfxControllerIdentifier); + point = gfx.viewport.toViewCoordFromClientCoord(point); + addImages(std, files, point).catch(console.error); + + std.getOptional(TelemetryProvider)?.track('CanvasElementAdded', { + control: 'canvas:drop', + page: 'whiteboard editor', + module: 'toolbar', + segment: 'toolbar', + type: 'image', + }); + return true; + } + + return false; + }, +}); + export const ImageDragHandleOption = DragHandleConfigExtension({ flavour: ImageBlockSchema.model.flavour, edgeless: true, diff --git a/blocksuite/blocks/src/image-block/image-spec.ts b/blocksuite/blocks/src/image-block/image-spec.ts index cc3409744b..b4292937de 100644 --- a/blocksuite/blocks/src/image-block/image-spec.ts +++ b/blocksuite/blocks/src/image-block/image-spec.ts @@ -10,7 +10,11 @@ import { literal } from 'lit/static-html.js'; import { ImageBlockAdapterExtensions } from './adapters/extension.js'; import { commands } from './commands/index.js'; -import { ImageBlockService, ImageDragHandleOption } from './image-service.js'; +import { + ImageBlockService, + ImageDragHandleOption, + ImageDropOption, +} from './image-service.js'; export const ImageBlockSpec: ExtensionType[] = [ FlavourExtension('affine:image'), @@ -29,6 +33,7 @@ export const ImageBlockSpec: ExtensionType[] = [ imageToolbar: literal`affine-image-toolbar-widget`, }), ImageDragHandleOption, + ImageDropOption, ImageSelectionExtension, ImageBlockAdapterExtensions, ].flat(); diff --git a/blocksuite/blocks/src/root-block/edgeless/edgeless-root-spec.ts b/blocksuite/blocks/src/root-block/edgeless/edgeless-root-spec.ts index b74d354beb..e792d10e72 100644 --- a/blocksuite/blocks/src/root-block/edgeless/edgeless-root-spec.ts +++ b/blocksuite/blocks/src/root-block/edgeless/edgeless-root-spec.ts @@ -1,3 +1,4 @@ +import { FileDropExtension } from '@blocksuite/affine-components/drag-indicator'; import { DNDAPIExtension, DocDisplayMetaService, @@ -98,6 +99,7 @@ const EdgelessCommonExtension: ExtensionType[] = [ DNDAPIExtension, DocDisplayMetaService, RootBlockAdapterExtensions, + FileDropExtension, ].flat(); export const EdgelessRootBlockSpec: ExtensionType[] = [ diff --git a/blocksuite/blocks/src/root-block/page/page-root-spec.ts b/blocksuite/blocks/src/root-block/page/page-root-spec.ts index 21a340555d..ff03dfcbb0 100644 --- a/blocksuite/blocks/src/root-block/page/page-root-spec.ts +++ b/blocksuite/blocks/src/root-block/page/page-root-spec.ts @@ -1,3 +1,4 @@ +import { FileDropExtension } from '@blocksuite/affine-components/drag-indicator'; import { DNDAPIExtension, DocDisplayMetaService, @@ -75,4 +76,5 @@ export const PageRootBlockSpec: ExtensionType[] = [ DNDAPIExtension, DocDisplayMetaService, RootBlockAdapterExtensions, + FileDropExtension, ].flat(); diff --git a/blocksuite/blocks/src/root-block/root-service.ts b/blocksuite/blocks/src/root-block/root-service.ts index fe51a1f388..31f52b78b9 100644 --- a/blocksuite/blocks/src/root-block/root-service.ts +++ b/blocksuite/blocks/src/root-block/root-service.ts @@ -2,10 +2,6 @@ import { RootBlockSchema } from '@blocksuite/affine-model'; import type { BlockComponent } from '@blocksuite/block-std'; import { BlockService } from '@blocksuite/block-std'; -import { - FileDropManager, - type FileDropOptions, -} from '../_common/components/file-drop-manager.js'; import { HtmlTransformer, MarkdownTransformer, @@ -16,12 +12,6 @@ import type { RootBlockComponent } from './types.js'; export abstract class RootService extends BlockService { static override readonly flavour = RootBlockSchema.model.flavour; - private readonly _fileDropOptions: FileDropOptions = { - flavour: this.flavour, - }; - - readonly fileDropManager = new FileDropManager(this, this._fileDropOptions); - transformers = { markdown: MarkdownTransformer, html: HtmlTransformer, @@ -64,18 +54,6 @@ export abstract class RootService extends BlockService { override mounted() { super.mounted(); - this.disposables.addFromEvent( - this.host, - 'dragover', - this.fileDropManager.onDragOver - ); - - this.disposables.addFromEvent( - this.host, - 'dragleave', - this.fileDropManager.onDragLeave - ); - this.disposables.add( this.std.event.add('pointerDown', ctx => { const state = ctx.get('pointerState'); diff --git a/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/drag-event-watcher.ts b/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/drag-event-watcher.ts index 60b4a5622a..a15ed98715 100644 --- a/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/drag-event-watcher.ts +++ b/blocksuite/blocks/src/root-block/widgets/drag-handle/watchers/drag-event-watcher.ts @@ -109,6 +109,12 @@ export class DragEventWatcher { }; private readonly _dropHandler = (context: UIEventStateContext) => { + const raw = context.get('dndState').raw; + const fileLength = raw.dataTransfer?.files.length ?? 0; + // If drop files, should let file drop extension handle it + if (fileLength > 0) { + return; + } this._onDrop(context); this._std.selection.setGroup('gfx', []); this.widget.clearRaf(); diff --git a/blocksuite/framework/block-std/src/event/control/pointer.ts b/blocksuite/framework/block-std/src/event/control/pointer.ts index 78a98b7179..9550f87591 100644 --- a/blocksuite/framework/block-std/src/event/control/pointer.ts +++ b/blocksuite/framework/block-std/src/event/control/pointer.ts @@ -272,6 +272,22 @@ class DragController extends PointerControllerBase { ); }; + private readonly _nativeDragOver = (event: DragEvent) => { + const dndEventState = new DndEventState({ event }); + this._dispatcher.run( + 'nativeDragOver', + this._createContext(event, dndEventState) + ); + }; + + private readonly _nativeDragLeave = (event: DragEvent) => { + const dndEventState = new DndEventState({ event }); + this._dispatcher.run( + 'nativeDragLeave', + this._createContext(event, dndEventState) + ); + }; + private readonly _nativeDrop = (event: DragEvent) => { this._reset(); this._nativeDragging = false; @@ -354,6 +370,8 @@ class DragController extends PointerControllerBase { disposables.addFromEvent(host, 'dragend', this._nativeDragEnd); disposables.addFromEvent(host, 'drag', this._nativeDragMove); disposables.addFromEvent(host, 'drop', this._nativeDrop); + disposables.addFromEvent(host, 'dragover', this._nativeDragOver); + disposables.addFromEvent(host, 'dragleave', this._nativeDragLeave); } } diff --git a/blocksuite/framework/block-std/src/event/dispatcher.ts b/blocksuite/framework/block-std/src/event/dispatcher.ts index 5f77628087..1fb446389b 100644 --- a/blocksuite/framework/block-std/src/event/dispatcher.ts +++ b/blocksuite/framework/block-std/src/event/dispatcher.ts @@ -58,6 +58,8 @@ const eventNames = [ 'nativeDragMove', 'nativeDragEnd', 'nativeDrop', + 'nativeDragOver', + 'nativeDragLeave', ...bypassEventNames, ] as const; @@ -175,12 +177,22 @@ export class UIEventDispatcher extends LifeCycleWatcher { this._setActive(false); }); this.disposables.addFromEvent(this.host, 'dragover', () => { + _dragging = true; + this._setActive(true); + }); + this.disposables.addFromEvent(this.host, 'dragenter', () => { + _dragging = true; + this._setActive(true); + }); + this.disposables.addFromEvent(this.host, 'dragstart', () => { + _dragging = true; this._setActive(true); }); this.disposables.addFromEvent(this.host, 'dragend', () => { - this._setActive(false); + _dragging = false; }); this.disposables.addFromEvent(this.host, 'drop', () => { + _dragging = false; this._setActive(true); }); this.disposables.addFromEvent(this.host, 'pointerenter', () => { diff --git a/blocksuite/tests-legacy/drag.spec.ts b/blocksuite/tests-legacy/drag.spec.ts index 1c9146371e..a2cc26fe60 100644 --- a/blocksuite/tests-legacy/drag.spec.ts +++ b/blocksuite/tests-legacy/drag.spec.ts @@ -200,30 +200,30 @@ test('move to the last block of each level in multi-level nesting', async ({ `${testInfo.title}_drag_3_9.json` ); + await dragHandleFromBlockToBlockBottomById( + page, + '4', + '3', + true, + -(1 * BLOCK_CHILDREN_CONTAINER_PADDING_LEFT) + ); + await expect(page.locator('.affine-drag-indicator')).toBeHidden(); + + expect(await getPageSnapshot(page, true)).toMatchSnapshot( + `${testInfo.title}_drag_4_3.json` + ); + + await assertRichTexts(page, ['C', 'D', 'E', 'F', 'G', 'A', 'B']); + await dragHandleFromBlockToBlockBottomById( + page, + '3', + '4', + true, + -(2 * BLOCK_CHILDREN_CONTAINER_PADDING_LEFT) + ); + await expect(page.locator('.affine-drag-indicator')).toBeHidden(); + // FIXME(DND) - // await dragHandleFromBlockToBlockBottomById( - // page, - // '4', - // '3', - // true, - // -(1 * BLOCK_CHILDREN_CONTAINER_PADDING_LEFT) - // ); - // await expect(page.locator('.affine-drag-indicator')).toBeHidden(); - // - // expect(await getPageSnapshot(page, true)).toMatchSnapshot( - // `${testInfo.title}_drag_4_3.json` - // ); - // - // await assertRichTexts(page, ['C', 'D', 'E', 'F', 'G', 'A', 'B']); - // await dragHandleFromBlockToBlockBottomById( - // page, - // '3', - // '4', - // true, - // -(2 * BLOCK_CHILDREN_CONTAINER_PADDING_LEFT) - // ); - // await expect(page.locator('.affine-drag-indicator')).toBeHidden(); - // // expect(await getPageSnapshot(page, true)).toMatchSnapshot( // `${testInfo.title}_drag_3_4.json` // ); diff --git a/blocksuite/tests-legacy/embed-synced-doc.spec.ts b/blocksuite/tests-legacy/embed-synced-doc.spec.ts index 9fd6b8f1a6..ab8c4e828a 100644 --- a/blocksuite/tests-legacy/embed-synced-doc.spec.ts +++ b/blocksuite/tests-legacy/embed-synced-doc.spec.ts @@ -259,10 +259,10 @@ test.describe('Embed synced doc', () => { '.affine-database-column-header.database-row' ); await databaseFirstCell.click({ force: true }); - const indicatorCount = await page - .locator('affine-drag-indicator') + const selectedCount = await page + .locator('.affine-embed-synced-doc-container.selected') .count(); - expect(indicatorCount).toBe(1); + expect(selectedCount).toBe(1); }); }); });