mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 05:14:54 +00:00
refactor: remove legacy drag handle logic (#9246)
This commit is contained in:
171
blocksuite/affine/shared/src/utils/dnd/calc-drop-target.ts
Normal file
171
blocksuite/affine/shared/src/utils/dnd/calc-drop-target.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import type { BlockComponent } from '@blocksuite/block-std';
|
||||
import { type Point, Rect } from '@blocksuite/global/utils';
|
||||
import type { BlockModel } from '@blocksuite/store';
|
||||
|
||||
import {
|
||||
getClosestBlockComponentByElement,
|
||||
getRectByBlockComponent,
|
||||
} from '../dom/index.js';
|
||||
import { matchFlavours } from '../model/index.js';
|
||||
import { getDropRectByPoint } from './get-drop-rect-by-point.js';
|
||||
import { DropFlags, type DroppingType, type DropResult } from './types.js';
|
||||
|
||||
/**
|
||||
* Calculates the drop target.
|
||||
*/
|
||||
export function calcDropTarget(
|
||||
point: Point,
|
||||
model: BlockModel,
|
||||
element: Element,
|
||||
draggingElements: BlockComponent[] = [],
|
||||
scale: number = 1,
|
||||
flavour: string | null = null // for block-hub
|
||||
): DropResult | null {
|
||||
const schema = model.doc.getSchemaByFlavour(
|
||||
'affine:database' as BlockSuite.Flavour
|
||||
);
|
||||
const children = schema?.model.children ?? [];
|
||||
|
||||
let shouldAppendToDatabase = true;
|
||||
|
||||
if (children.length) {
|
||||
if (draggingElements.length) {
|
||||
shouldAppendToDatabase = draggingElements
|
||||
.map(el => el.model)
|
||||
.every(m => children.includes(m.flavour));
|
||||
} else if (flavour) {
|
||||
shouldAppendToDatabase = children.includes(flavour);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!shouldAppendToDatabase &&
|
||||
!matchFlavours(model, ['affine:database' as BlockSuite.Flavour])
|
||||
) {
|
||||
const databaseBlockComponent =
|
||||
element.closest<BlockComponent>('affine-database');
|
||||
if (databaseBlockComponent) {
|
||||
element = databaseBlockComponent;
|
||||
model = databaseBlockComponent.model;
|
||||
}
|
||||
}
|
||||
|
||||
let type: DroppingType = 'none';
|
||||
const height = 3 * scale;
|
||||
const dropResult = getDropRectByPoint(point, model, element);
|
||||
if (!dropResult) return null;
|
||||
const { rect: domRect, flag } = dropResult;
|
||||
|
||||
if (flag === DropFlags.EmptyDatabase) {
|
||||
// empty database
|
||||
const rect = Rect.fromDOMRect(domRect);
|
||||
rect.top -= height / 2;
|
||||
rect.height = height;
|
||||
type = 'database';
|
||||
|
||||
return {
|
||||
type,
|
||||
rect,
|
||||
modelState: {
|
||||
model,
|
||||
rect: domRect,
|
||||
element: element as BlockComponent,
|
||||
},
|
||||
};
|
||||
} else if (flag === DropFlags.Database) {
|
||||
// not empty database
|
||||
const distanceToTop = Math.abs(domRect.top - point.y);
|
||||
const distanceToBottom = Math.abs(domRect.bottom - point.y);
|
||||
const before = distanceToTop < distanceToBottom;
|
||||
type = before ? 'before' : 'after';
|
||||
|
||||
return {
|
||||
type,
|
||||
rect: Rect.fromLWTH(
|
||||
domRect.left,
|
||||
domRect.width,
|
||||
(before ? domRect.top - 1 : domRect.bottom) - height / 2,
|
||||
height
|
||||
),
|
||||
modelState: {
|
||||
model,
|
||||
rect: domRect,
|
||||
element: element as BlockComponent,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const distanceToTop = Math.abs(domRect.top - point.y);
|
||||
const distanceToBottom = Math.abs(domRect.bottom - point.y);
|
||||
const before = distanceToTop < distanceToBottom;
|
||||
|
||||
type = before ? 'before' : 'after';
|
||||
let offsetY = 4;
|
||||
|
||||
if (type === 'before') {
|
||||
// before
|
||||
let prev;
|
||||
let prevRect;
|
||||
|
||||
prev = element.previousElementSibling;
|
||||
if (prev) {
|
||||
if (
|
||||
draggingElements.length &&
|
||||
prev === draggingElements[draggingElements.length - 1]
|
||||
) {
|
||||
type = 'none';
|
||||
} else {
|
||||
prevRect = getRectByBlockComponent(prev);
|
||||
}
|
||||
} else {
|
||||
prev = element.parentElement?.previousElementSibling;
|
||||
if (prev) {
|
||||
prevRect = prev.getBoundingClientRect();
|
||||
}
|
||||
}
|
||||
|
||||
if (prevRect) {
|
||||
offsetY = (domRect.top - prevRect.bottom) / 2;
|
||||
}
|
||||
} else {
|
||||
// after
|
||||
let next;
|
||||
let nextRect;
|
||||
|
||||
next = element.nextElementSibling;
|
||||
if (next) {
|
||||
if (draggingElements.length && next === draggingElements[0]) {
|
||||
type = 'none';
|
||||
next = null;
|
||||
}
|
||||
} else {
|
||||
next = getClosestBlockComponentByElement(
|
||||
element.parentElement
|
||||
)?.nextElementSibling;
|
||||
}
|
||||
|
||||
if (next) {
|
||||
nextRect = getRectByBlockComponent(next);
|
||||
offsetY = (nextRect.top - domRect.bottom) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'none') return null;
|
||||
|
||||
let top = domRect.top;
|
||||
if (type === 'before') {
|
||||
top -= offsetY;
|
||||
} else {
|
||||
top += domRect.height + offsetY;
|
||||
}
|
||||
|
||||
return {
|
||||
type,
|
||||
rect: Rect.fromLWTH(domRect.left, domRect.width, top - height / 2, height),
|
||||
modelState: {
|
||||
model,
|
||||
rect: domRect,
|
||||
element: element as BlockComponent,
|
||||
},
|
||||
};
|
||||
}
|
||||
161
blocksuite/affine/shared/src/utils/dnd/get-drop-rect-by-point.ts
Normal file
161
blocksuite/affine/shared/src/utils/dnd/get-drop-rect-by-point.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { BLOCK_ID_ATTR } from '@blocksuite/block-std';
|
||||
import type { Point } from '@blocksuite/global/utils';
|
||||
import type { BlockModel } from '@blocksuite/store';
|
||||
|
||||
import { getRectByBlockComponent } from '../dom/index.js';
|
||||
import { matchFlavours } from '../model/index.js';
|
||||
import { DropFlags } from './types.js';
|
||||
|
||||
const ATTR_SELECTOR = `[${BLOCK_ID_ATTR}]`;
|
||||
|
||||
/**
|
||||
* Gets the drop rect by block and point.
|
||||
*/
|
||||
export function getDropRectByPoint(
|
||||
point: Point,
|
||||
model: BlockModel,
|
||||
element: Element
|
||||
): null | {
|
||||
rect: DOMRect;
|
||||
flag: DropFlags;
|
||||
} {
|
||||
const result = {
|
||||
rect: getRectByBlockComponent(element),
|
||||
flag: DropFlags.Normal,
|
||||
};
|
||||
|
||||
const isDatabase = matchFlavours(model, [
|
||||
'affine:database' as BlockSuite.Flavour,
|
||||
]);
|
||||
|
||||
if (isDatabase) {
|
||||
const table = getDatabaseBlockTableElement(element);
|
||||
if (!table) {
|
||||
return result;
|
||||
}
|
||||
|
||||
let bounds = table.getBoundingClientRect();
|
||||
if (model.isEmpty.value) {
|
||||
result.flag = DropFlags.EmptyDatabase;
|
||||
|
||||
if (point.y < bounds.top) return result;
|
||||
|
||||
const header = getDatabaseBlockColumnHeaderElement(element);
|
||||
if (!header) {
|
||||
return null;
|
||||
}
|
||||
|
||||
bounds = header.getBoundingClientRect();
|
||||
result.rect = new DOMRect(
|
||||
result.rect.left,
|
||||
bounds.bottom,
|
||||
result.rect.width,
|
||||
1
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
result.flag = DropFlags.Database;
|
||||
const rows = getDatabaseBlockRowsElement(element);
|
||||
if (!rows) {
|
||||
return null;
|
||||
}
|
||||
const rowsBounds = rows.getBoundingClientRect();
|
||||
|
||||
if (point.y < rowsBounds.top || point.y > rowsBounds.bottom) return result;
|
||||
|
||||
const elements = document.elementsFromPoint(point.x, point.y);
|
||||
const len = elements.length;
|
||||
let e;
|
||||
let i = 0;
|
||||
for (; i < len; i++) {
|
||||
e = elements[i];
|
||||
|
||||
if (e.classList.contains('affine-database-block-row-cell-content')) {
|
||||
const cellRect = getCellRect(e, bounds);
|
||||
if (!cellRect) {
|
||||
return null;
|
||||
}
|
||||
result.rect = cellRect;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (e.classList.contains('affine-database-block-row')) {
|
||||
e = e.querySelector(ATTR_SELECTOR);
|
||||
if (!e) {
|
||||
return null;
|
||||
}
|
||||
const cellRect = getCellRect(e, bounds);
|
||||
if (!cellRect) {
|
||||
return null;
|
||||
}
|
||||
result.rect = cellRect;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const parent = element.parentElement;
|
||||
if (parent?.classList.contains('affine-database-block-row-cell-content')) {
|
||||
result.flag = DropFlags.Database;
|
||||
const cellRect = getCellRect(parent);
|
||||
if (!cellRect) {
|
||||
return null;
|
||||
}
|
||||
result.rect = cellRect;
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the table of the database.
|
||||
*/
|
||||
function getDatabaseBlockTableElement(element: Element) {
|
||||
return element.querySelector('.affine-database-block-table');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the column header of the database.
|
||||
*/
|
||||
function getDatabaseBlockColumnHeaderElement(element: Element) {
|
||||
return element.querySelector('.affine-database-column-header');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the rows of the database.
|
||||
*/
|
||||
function getDatabaseBlockRowsElement(element: Element) {
|
||||
return element.querySelector('.affine-database-block-rows');
|
||||
}
|
||||
|
||||
function getCellRect(element: Element, bounds?: DOMRect) {
|
||||
if (!bounds) {
|
||||
const table = element.closest('.affine-database-block-table');
|
||||
if (!table) {
|
||||
return null;
|
||||
}
|
||||
bounds = table.getBoundingClientRect();
|
||||
}
|
||||
// affine-database-block-row-cell
|
||||
const col = element.parentElement;
|
||||
if (!col) {
|
||||
return null;
|
||||
}
|
||||
// affine-database-block-row
|
||||
const row = col.parentElement;
|
||||
if (!row) {
|
||||
return null;
|
||||
}
|
||||
const colRect = col.getBoundingClientRect();
|
||||
return new DOMRect(
|
||||
bounds.left,
|
||||
colRect.top,
|
||||
colRect.right - bounds.left,
|
||||
colRect.height
|
||||
);
|
||||
}
|
||||
@@ -1 +1,4 @@
|
||||
export * from './calc-drop-target.js';
|
||||
export * from './get-drop-rect-by-point.js';
|
||||
export * from './legacy.js';
|
||||
export type { DropResult } from './types.js';
|
||||
|
||||
29
blocksuite/affine/shared/src/utils/dnd/types.ts
Normal file
29
blocksuite/affine/shared/src/utils/dnd/types.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { BlockComponent } from '@blocksuite/block-std';
|
||||
import type { Rect } from '@blocksuite/global/utils';
|
||||
import type { BlockModel } from '@blocksuite/store';
|
||||
|
||||
export interface EditingState {
|
||||
element: BlockComponent;
|
||||
model: BlockModel;
|
||||
rect: DOMRect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a flag for the drop target.
|
||||
*/
|
||||
export enum DropFlags {
|
||||
Normal,
|
||||
Database,
|
||||
EmptyDatabase,
|
||||
}
|
||||
|
||||
/**
|
||||
* A dropping type.
|
||||
*/
|
||||
export type DroppingType = 'none' | 'before' | 'after' | 'database';
|
||||
|
||||
export type DropResult = {
|
||||
type: DroppingType;
|
||||
rect: Rect;
|
||||
modelState: EditingState;
|
||||
};
|
||||
Reference in New Issue
Block a user