Skip to content

Commit 381596d

Browse files
committed
feat(core): add new bs dnd adapter
1 parent 3a90c85 commit 381596d

File tree

8 files changed

+111
-9
lines changed

8 files changed

+111
-9
lines changed

blocksuite/affine/shared/src/services/drag-handle-config.ts

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { type BlockStdScope, StdIdentifier } from '@blocksuite/block-std';
22
import { type Container, createIdentifier } from '@blocksuite/global/di';
33
import { Extension, Slice, type SliceSnapshot } from '@blocksuite/store';
44

5+
export type { DragBlockPayload } from '@blocksuite/affine-widget-drag-handle';
6+
57
export const DndApiExtensionIdentifier = createIdentifier<DNDAPIExtension>(
68
'AffineDndApiIdentifier'
79
);

blocksuite/affine/widget-drag-handle/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ declare type _GLOBAL_ = typeof SurfaceEffects;
55
export * from './consts';
66
export * from './drag-handle';
77
export * from './utils';
8+
export type { DragBlockPayload } from './watchers/drag-event-watcher';

packages/frontend/component/src/ui/dnd/monitor.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { getAdaptedEventArgs } from './common';
1010
import { DNDContext } from './context';
1111
import type { DNDData, fromExternalData } from './types';
1212

13-
type MonitorGetFeedback<D extends DNDData = DNDData> = Parameters<
13+
export type MonitorGetFeedback<D extends DNDData = DNDData> = Parameters<
1414
NonNullable<Parameters<typeof monitorForElements>[0]['canMonitor']>
1515
>[0] & {
1616
source: {
@@ -22,7 +22,7 @@ type MonitorGet<T, D extends DNDData = DNDData> =
2222
| T
2323
| ((data: MonitorGetFeedback<D>) => T);
2424

25-
type MonitorDragEvent<D extends DNDData = DNDData> = {
25+
export type MonitorDragEvent<D extends DNDData = DNDData> = {
2626
/**
2727
* Location history for the drag operation
2828
*/
@@ -119,3 +119,5 @@ export const useDndMonitor = <D extends DNDData = DNDData>(
119119
return monitorForExternal(monitorOptions);
120120
}, [monitorOptions, options.fromExternalData]);
121121
};
122+
123+
export { monitorForElements };

packages/frontend/core/src/mobile/components/explorer/tree/node.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,11 @@ export const ExplorerTreeNode = ({
180180
>
181181
<div className={styles.contentContainer} data-open={!collapsed}>
182182
{to ? (
183-
<LinkComponent to={to} className={styles.linkItemRoot}>
183+
<LinkComponent
184+
to={to}
185+
className={styles.linkItemRoot}
186+
draggable={false}
187+
>
184188
{content}
185189
</LinkComponent>
186190
) : (

packages/frontend/core/src/modules/dnd/services/index.ts

+86-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
import {
22
type ExternalGetDataFeedbackArgs,
33
type fromExternalData,
4+
type MonitorDragEvent,
5+
monitorForElements,
6+
type MonitorGetFeedback,
47
type toExternalData,
58
} from '@affine/component';
69
import { createPageModeSpecs } from '@affine/core/components/blocksuite/block-suite-editor/specs/page';
710
import type { AffineDNDData } from '@affine/core/types/dnd';
811
import { BlockStdScope } from '@blocksuite/affine/block-std';
9-
import { DndApiExtensionIdentifier } from '@blocksuite/affine/blocks';
12+
import {
13+
DndApiExtensionIdentifier,
14+
type DragBlockPayload,
15+
} from '@blocksuite/affine/blocks';
1016
import { type SliceSnapshot } from '@blocksuite/affine/store';
1117
import { Service } from '@toeverything/infra';
1218

@@ -19,6 +25,10 @@ type EntityResolver = (data: string) => Entity | null;
1925

2026
type ExternalDragPayload = ExternalGetDataFeedbackArgs['source'];
2127

28+
type MixedDNDData = AffineDNDData & {
29+
draggable: DragBlockPayload;
30+
};
31+
2232
export class DndService extends Service {
2333
constructor(
2434
private readonly docsService: DocsService,
@@ -53,6 +63,78 @@ export class DndService extends Service {
5363
return null;
5464
});
5565
});
66+
67+
this.setupBlocksuiteAdapter();
68+
}
69+
70+
private setupBlocksuiteAdapter() {
71+
/**
72+
* Migrate from affine to blocksuite
73+
* For now, we only support doc
74+
*/
75+
const affineToBlocksuite = (args: MonitorDragEvent<MixedDNDData>) => {
76+
const data = args.source.data;
77+
if (data.entity && !data.bsEntity) {
78+
if (data.entity.type !== 'doc') {
79+
return;
80+
}
81+
const dndAPI = this.getBlocksuiteDndAPI();
82+
if (!dndAPI) {
83+
return;
84+
}
85+
const snapshotSlice = dndAPI.fromEntity({
86+
docId: data.entity.id,
87+
flavour: 'affine:embed-linked-doc',
88+
});
89+
if (!snapshotSlice) {
90+
return;
91+
}
92+
data.bsEntity = {
93+
type: 'blocks',
94+
modelIds: [],
95+
snapshot: snapshotSlice,
96+
};
97+
}
98+
};
99+
100+
/**
101+
* Migrate from blocksuite to affine
102+
*/
103+
const blocksuiteToAffine = (args: MonitorDragEvent<MixedDNDData>) => {
104+
const data = args.source.data;
105+
if (!data.entity && data.bsEntity) {
106+
if (data.bsEntity.type !== 'blocks' || !data.bsEntity.snapshot) {
107+
return;
108+
}
109+
const dndAPI = this.getBlocksuiteDndAPI();
110+
if (!dndAPI) {
111+
return;
112+
}
113+
const entity = this.resolveBlockSnapshot(data.bsEntity.snapshot);
114+
if (!entity) {
115+
return;
116+
}
117+
data.entity = entity;
118+
}
119+
};
120+
121+
this.disposables.push(
122+
monitorForElements({
123+
canMonitor: (args: MonitorGetFeedback<MixedDNDData>) => {
124+
return (
125+
args.source.data.entity?.type === 'doc' ||
126+
(args.source.data.bsEntity?.type === 'blocks' &&
127+
!!args.source.data.bsEntity.snapshot)
128+
);
129+
},
130+
// assume data is only applied on drag start
131+
onDragStart: args => {
132+
// affine <-> blocksuite
133+
affineToBlocksuite(args);
134+
blocksuiteToAffine(args);
135+
},
136+
})
137+
);
56138
}
57139

58140
private readonly resolvers: ((
@@ -161,6 +243,9 @@ export class DndService extends Service {
161243
return null;
162244
};
163245

246+
/**
247+
* @deprecated Blocksuite DND is now using pragmatic-dnd as well
248+
*/
164249
private readonly resolveBlocksuiteExternalData = (
165250
source: ExternalDragPayload
166251
): AffineDNDData['draggable'] | null => {

packages/frontend/core/src/modules/explorer/views/tree/node.tsx

+8-2
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ export interface BaseExplorerTreeNodeProps {
7272
childrenOperations?: NodeOperation[];
7373
childrenPlaceholder?: React.ReactNode;
7474
linkComponent?: React.ComponentType<
75-
React.PropsWithChildren<{ to: To; className?: string }> & RefAttributes<any>
75+
React.PropsWithChildren<{ to: To; className?: string }> &
76+
RefAttributes<any> & { draggable?: boolean }
7677
>;
7778
[key: `data-${string}`]: any;
7879
}
@@ -433,7 +434,12 @@ export const ExplorerTreeNode = ({
433434
ref={dropTargetRef}
434435
>
435436
{to ? (
436-
<LinkComponent to={to} className={styles.linkItemRoot} ref={dragRef}>
437+
<LinkComponent
438+
to={to}
439+
className={styles.linkItemRoot}
440+
ref={dragRef}
441+
draggable={false}
442+
>
437443
{content}
438444
</LinkComponent>
439445
) : (

packages/frontend/core/src/modules/workbench/view/split-view/split-view.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,10 @@ export const SplitView = ({
8585

8686
useDndMonitor<AffineDNDData>(() => {
8787
return {
88-
// todo(@pengx17): external data for monitor is not supported yet
89-
// allowExternal: true,
9088
canMonitor(data) {
89+
if (!BUILD_CONFIG.isElectron) {
90+
return false;
91+
}
9192
// allow dropping doc && tab view to split view panel
9293
const from = data.source.data.from;
9394
const entity = data.source.data.entity;

tests/affine-local/e2e/drag-page.spec.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ test('drag a page link in editor to favourites', async ({ page }) => {
243243
);
244244
});
245245

246-
test.skip('drag a page card block to another page', async ({ page }) => {
246+
test('drag a page card block to another page', async ({ page }) => {
247247
await clickNewPageButton(page);
248248
await page.waitForTimeout(500);
249249
await page.keyboard.press('Enter');
@@ -293,6 +293,7 @@ test.skip('drag a page card block to another page', async ({ page }) => {
293293
);
294294
});
295295

296+
// todo(@pengx17): figure out why this test is failing
296297
test.skip('drag a favourite page into blocksuite', async ({ page }) => {
297298
await clickNewPageButton(page, 'hi from page');
298299
await page.getByTestId('pin-button').click();

0 commit comments

Comments
 (0)