Skip to content

Commit

Permalink
refactor(editor): move file drop manager to components (#9264)
Browse files Browse the repository at this point in the history
  • Loading branch information
Saul-Mirone committed Dec 24, 2024
1 parent 74a1682 commit b29cb59
Show file tree
Hide file tree
Showing 16 changed files with 321 additions and 261 deletions.
44 changes: 44 additions & 0 deletions blocksuite/affine/components/src/drag-indicator/drag-indicator.ts
Original file line number Diff line number Diff line change
@@ -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`<div class="affine-drag-indicator" style=${style}></div>`;
}

@property({ attribute: false })
accessor rect: Rect | null = null;
}

declare global {
interface HTMLElementTagNameMap {
'affine-drag-indicator': DragIndicator;
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import type { DragIndicator } from '@blocksuite/affine-components/drag-indicator';
import {
calcDropTarget,
type DropResult,
getClosestBlockComponentByPoint,
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';
Expand All @@ -20,54 +28,32 @@ export type onDropProps = {

export type FileDropOptions = {
flavour: string;
onDrop?: ({
files,
targetModel,
place,
point,
}: onDropProps) => Promise<boolean> | void;
onDrop?: (onDropProps: onDropProps) => boolean;
};

export class FileDropManager {
private static _dropResult: DropResult | null = null;

private readonly _blockService: BlockService;

private readonly _fileDropOptions: FileDropOptions;

private readonly _indicator!: DragIndicator;

private readonly _onDrop = (event: DragEvent) => {
this._indicator.rect = null;

const { onDrop } = this._fileDropOptions;
if (!onDrop) return;

const dataTransfer = event.dataTransfer;
if (!dataTransfer) return;
export class FileDropExtension extends LifeCycleWatcher {
static override readonly key = 'FileDropExtension';

const effectAllowed = dataTransfer.effectAllowed;
if (effectAllowed === 'none') return;
static dropResult: DropResult | null = null;

const droppedFiles = dataTransfer.files;
if (!droppedFiles || !droppedFiles.length) return;

event.preventDefault();
static get indicator() {
let indicator = document.querySelector<DragIndicator>(
'affine-drag-indicator'
);

const { targetModel, type: place } = this;
const { x, y } = event;
if (!indicator) {
indicator = document.createElement(
'affine-drag-indicator'
) as DragIndicator;
document.body.append(indicator);
}

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) => {
Expand All @@ -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;
}

Expand All @@ -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;

const dataTransfer = event.dataTransfer;
if (!dataTransfer) return;

const effectAllowed = dataTransfer.effectAllowed;
if (effectAllowed === 'none') return;

if (fileDropOptions.onDrop) {
this._blockService.disposables.addFromEvent(
this._blockService.std.host,
'drop',
this._onDrop
);
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<FileDropOptions>(
'FileDropConfigExtension'
);

export const FileDropConfigExtension = (
options: FileDropOptions
): ExtensionType => {
const identifier = FileDropConfigExtensionIdentifier(options.flavour);
return {
setup: di => {
di.addImpl(identifier, () => options);
},
};
};
53 changes: 9 additions & 44 deletions blocksuite/affine/components/src/drag-indicator/index.ts
Original file line number Diff line number Diff line change
@@ -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';

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`<div class="affine-drag-indicator" style=${style}></div>`;
}

@property({ attribute: false })
accessor rect: Rect | null = null;
}

declare global {
interface HTMLElementTagNameMap {
'affine-drag-indicator': DragIndicator;
}
}
import { DragIndicator } from './drag-indicator.js';
export {
FileDropConfigExtension,
FileDropExtension,
type FileDropOptions,
type onDropProps,
} from './file-drop-manager.js';

export { DragIndicator };

export function effects() {
customElements.define('affine-drag-indicator', DragIndicator);
Expand Down
1 change: 0 additions & 1 deletion blocksuite/blocks/src/_common/components/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Loading

0 comments on commit b29cb59

Please sign in to comment.