feat(core): add new bs dnd adapter (#9717)

This commit is contained in:
pengx17
2025-01-16 13:54:50 +00:00
parent 99717196c5
commit 7dc470e7ea
8 changed files with 121 additions and 10 deletions

View File

@@ -1,12 +1,17 @@
import {
type ExternalGetDataFeedbackArgs,
type fromExternalData,
monitorForElements,
type MonitorGetFeedback,
type toExternalData,
} from '@affine/component';
import { createPageModeSpecs } from '@affine/core/components/blocksuite/block-suite-editor/specs/page';
import type { AffineDNDData } from '@affine/core/types/dnd';
import { BlockStdScope } from '@blocksuite/affine/block-std';
import { DndApiExtensionIdentifier } from '@blocksuite/affine/blocks';
import {
DndApiExtensionIdentifier,
type DragBlockPayload,
} from '@blocksuite/affine/blocks';
import { type SliceSnapshot } from '@blocksuite/affine/store';
import { Service } from '@toeverything/infra';
@@ -19,6 +24,10 @@ type EntityResolver = (data: string) => Entity | null;
type ExternalDragPayload = ExternalGetDataFeedbackArgs['source'];
type MixedDNDData = AffineDNDData & {
draggable: DragBlockPayload;
};
export class DndService extends Service {
constructor(
private readonly docsService: DocsService,
@@ -53,6 +62,90 @@ export class DndService extends Service {
return null;
});
});
this.setupBlocksuiteAdapter();
}
private setupBlocksuiteAdapter() {
/**
* Migrate from affine to blocksuite
* For now, we only support doc
*/
const affineToBlocksuite = (args: MonitorGetFeedback<MixedDNDData>) => {
const data = args.source.data;
if (data.entity && !data.bsEntity) {
if (data.entity.type !== 'doc') {
return;
}
const dndAPI = this.getBlocksuiteDndAPI();
if (!dndAPI) {
return;
}
const snapshotSlice = dndAPI.fromEntity({
docId: data.entity.id,
flavour: 'affine:embed-linked-doc',
});
if (!snapshotSlice) {
return;
}
data.bsEntity = {
type: 'blocks',
modelIds: [],
snapshot: snapshotSlice,
};
}
};
/**
* Migrate from blocksuite to affine
*/
const blocksuiteToAffine = (args: MonitorGetFeedback<MixedDNDData>) => {
const data = args.source.data;
if (!data.entity && data.bsEntity) {
if (data.bsEntity.type !== 'blocks' || !data.bsEntity.snapshot) {
return;
}
const dndAPI = this.getBlocksuiteDndAPI();
if (!dndAPI) {
return;
}
const entity = this.resolveBlockSnapshot(data.bsEntity.snapshot);
if (!entity) {
return;
}
data.entity = entity;
}
};
function adaptDragEvent(args: MonitorGetFeedback<MixedDNDData>) {
affineToBlocksuite(args);
blocksuiteToAffine(args);
}
function canMonitor(args: MonitorGetFeedback<MixedDNDData>) {
return (
args.source.data.entity?.type === 'doc' ||
(args.source.data.bsEntity?.type === 'blocks' &&
!!args.source.data.bsEntity.snapshot)
);
}
this.disposables.push(
monitorForElements({
canMonitor: (args: MonitorGetFeedback<MixedDNDData>) => {
if (canMonitor(args)) {
// HACK ahead:
// canMonitor shall be used a pure function, which means
// we may need to adapt the drag event to make sure the data is applied onDragStart.
// However, canMonitor in blocksuite is also called BEFORE onDragStart,
// so we need to adapt it here in onMonitor
adaptDragEvent(args);
return true;
}
return false;
},
})
);
}
private readonly resolvers: ((
@@ -161,6 +254,9 @@ export class DndService extends Service {
return null;
};
/**
* @deprecated Blocksuite DND is now using pragmatic-dnd as well
*/
private readonly resolveBlocksuiteExternalData = (
source: ExternalDragPayload
): AffineDNDData['draggable'] | null => {

View File

@@ -72,7 +72,8 @@ export interface BaseExplorerTreeNodeProps {
childrenOperations?: NodeOperation[];
childrenPlaceholder?: React.ReactNode;
linkComponent?: React.ComponentType<
React.PropsWithChildren<{ to: To; className?: string }> & RefAttributes<any>
React.PropsWithChildren<{ to: To; className?: string }> &
RefAttributes<any> & { draggable?: boolean }
>;
[key: `data-${string}`]: any;
}
@@ -433,7 +434,12 @@ export const ExplorerTreeNode = ({
ref={dropTargetRef}
>
{to ? (
<LinkComponent to={to} className={styles.linkItemRoot} ref={dragRef}>
<LinkComponent
to={to}
className={styles.linkItemRoot}
ref={dragRef}
draggable={false}
>
{content}
</LinkComponent>
) : (

View File

@@ -85,9 +85,10 @@ export const SplitView = ({
useDndMonitor<AffineDNDData>(() => {
return {
// todo(@pengx17): external data for monitor is not supported yet
// allowExternal: true,
canMonitor(data) {
if (!BUILD_CONFIG.isElectron) {
return false;
}
// allow dropping doc && tab view to split view panel
const from = data.source.data.from;
const entity = data.source.data.entity;