mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-27 19:02:23 +08:00
feat(core): add new bs dnd adapter (#9717)
This commit is contained in:
@@ -5,3 +5,4 @@ declare type _GLOBAL_ = typeof SurfaceEffects;
|
|||||||
export * from './consts';
|
export * from './consts';
|
||||||
export * from './drag-handle';
|
export * from './drag-handle';
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
|
export type { DragBlockPayload } from './watchers/drag-event-watcher';
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ export {
|
|||||||
openFileOrFiles,
|
openFileOrFiles,
|
||||||
printToPdf,
|
printToPdf,
|
||||||
} from '@blocksuite/affine-shared/utils';
|
} from '@blocksuite/affine-shared/utils';
|
||||||
|
export type { DragBlockPayload } from '@blocksuite/affine-widget-drag-handle';
|
||||||
|
|
||||||
export const BlocksUtils = {
|
export const BlocksUtils = {
|
||||||
splitElements,
|
splitElements,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { getAdaptedEventArgs } from './common';
|
|||||||
import { DNDContext } from './context';
|
import { DNDContext } from './context';
|
||||||
import type { DNDData, fromExternalData } from './types';
|
import type { DNDData, fromExternalData } from './types';
|
||||||
|
|
||||||
type MonitorGetFeedback<D extends DNDData = DNDData> = Parameters<
|
export type MonitorGetFeedback<D extends DNDData = DNDData> = Parameters<
|
||||||
NonNullable<Parameters<typeof monitorForElements>[0]['canMonitor']>
|
NonNullable<Parameters<typeof monitorForElements>[0]['canMonitor']>
|
||||||
>[0] & {
|
>[0] & {
|
||||||
source: {
|
source: {
|
||||||
@@ -22,7 +22,7 @@ type MonitorGet<T, D extends DNDData = DNDData> =
|
|||||||
| T
|
| T
|
||||||
| ((data: MonitorGetFeedback<D>) => T);
|
| ((data: MonitorGetFeedback<D>) => T);
|
||||||
|
|
||||||
type MonitorDragEvent<D extends DNDData = DNDData> = {
|
export type MonitorDragEvent<D extends DNDData = DNDData> = {
|
||||||
/**
|
/**
|
||||||
* Location history for the drag operation
|
* Location history for the drag operation
|
||||||
*/
|
*/
|
||||||
@@ -119,3 +119,5 @@ export const useDndMonitor = <D extends DNDData = DNDData>(
|
|||||||
return monitorForExternal(monitorOptions);
|
return monitorForExternal(monitorOptions);
|
||||||
}, [monitorOptions, options.fromExternalData]);
|
}, [monitorOptions, options.fromExternalData]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export { monitorForElements };
|
||||||
|
|||||||
@@ -180,7 +180,11 @@ export const ExplorerTreeNode = ({
|
|||||||
>
|
>
|
||||||
<div className={styles.contentContainer} data-open={!collapsed}>
|
<div className={styles.contentContainer} data-open={!collapsed}>
|
||||||
{to ? (
|
{to ? (
|
||||||
<LinkComponent to={to} className={styles.linkItemRoot}>
|
<LinkComponent
|
||||||
|
to={to}
|
||||||
|
className={styles.linkItemRoot}
|
||||||
|
draggable={false}
|
||||||
|
>
|
||||||
{content}
|
{content}
|
||||||
</LinkComponent>
|
</LinkComponent>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
import {
|
import {
|
||||||
type ExternalGetDataFeedbackArgs,
|
type ExternalGetDataFeedbackArgs,
|
||||||
type fromExternalData,
|
type fromExternalData,
|
||||||
|
monitorForElements,
|
||||||
|
type MonitorGetFeedback,
|
||||||
type toExternalData,
|
type toExternalData,
|
||||||
} from '@affine/component';
|
} from '@affine/component';
|
||||||
import { createPageModeSpecs } from '@affine/core/components/blocksuite/block-suite-editor/specs/page';
|
import { createPageModeSpecs } from '@affine/core/components/blocksuite/block-suite-editor/specs/page';
|
||||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||||
import { BlockStdScope } from '@blocksuite/affine/block-std';
|
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 { type SliceSnapshot } from '@blocksuite/affine/store';
|
||||||
import { Service } from '@toeverything/infra';
|
import { Service } from '@toeverything/infra';
|
||||||
|
|
||||||
@@ -19,6 +24,10 @@ type EntityResolver = (data: string) => Entity | null;
|
|||||||
|
|
||||||
type ExternalDragPayload = ExternalGetDataFeedbackArgs['source'];
|
type ExternalDragPayload = ExternalGetDataFeedbackArgs['source'];
|
||||||
|
|
||||||
|
type MixedDNDData = AffineDNDData & {
|
||||||
|
draggable: DragBlockPayload;
|
||||||
|
};
|
||||||
|
|
||||||
export class DndService extends Service {
|
export class DndService extends Service {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly docsService: DocsService,
|
private readonly docsService: DocsService,
|
||||||
@@ -53,6 +62,90 @@ export class DndService extends Service {
|
|||||||
return null;
|
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: ((
|
private readonly resolvers: ((
|
||||||
@@ -161,6 +254,9 @@ export class DndService extends Service {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Blocksuite DND is now using pragmatic-dnd as well
|
||||||
|
*/
|
||||||
private readonly resolveBlocksuiteExternalData = (
|
private readonly resolveBlocksuiteExternalData = (
|
||||||
source: ExternalDragPayload
|
source: ExternalDragPayload
|
||||||
): AffineDNDData['draggable'] | null => {
|
): AffineDNDData['draggable'] | null => {
|
||||||
|
|||||||
@@ -72,7 +72,8 @@ export interface BaseExplorerTreeNodeProps {
|
|||||||
childrenOperations?: NodeOperation[];
|
childrenOperations?: NodeOperation[];
|
||||||
childrenPlaceholder?: React.ReactNode;
|
childrenPlaceholder?: React.ReactNode;
|
||||||
linkComponent?: React.ComponentType<
|
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;
|
[key: `data-${string}`]: any;
|
||||||
}
|
}
|
||||||
@@ -433,7 +434,12 @@ export const ExplorerTreeNode = ({
|
|||||||
ref={dropTargetRef}
|
ref={dropTargetRef}
|
||||||
>
|
>
|
||||||
{to ? (
|
{to ? (
|
||||||
<LinkComponent to={to} className={styles.linkItemRoot} ref={dragRef}>
|
<LinkComponent
|
||||||
|
to={to}
|
||||||
|
className={styles.linkItemRoot}
|
||||||
|
ref={dragRef}
|
||||||
|
draggable={false}
|
||||||
|
>
|
||||||
{content}
|
{content}
|
||||||
</LinkComponent>
|
</LinkComponent>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -85,9 +85,10 @@ export const SplitView = ({
|
|||||||
|
|
||||||
useDndMonitor<AffineDNDData>(() => {
|
useDndMonitor<AffineDNDData>(() => {
|
||||||
return {
|
return {
|
||||||
// todo(@pengx17): external data for monitor is not supported yet
|
|
||||||
// allowExternal: true,
|
|
||||||
canMonitor(data) {
|
canMonitor(data) {
|
||||||
|
if (!BUILD_CONFIG.isElectron) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// allow dropping doc && tab view to split view panel
|
// allow dropping doc && tab view to split view panel
|
||||||
const from = data.source.data.from;
|
const from = data.source.data.from;
|
||||||
const entity = data.source.data.entity;
|
const entity = data.source.data.entity;
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ test('drag a page link in editor to favourites', async ({ page }) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.skip('drag a page card block to another page', async ({ page }) => {
|
test('drag a page card block to another page', async ({ page }) => {
|
||||||
await clickNewPageButton(page);
|
await clickNewPageButton(page);
|
||||||
await page.waitForTimeout(500);
|
await page.waitForTimeout(500);
|
||||||
await page.keyboard.press('Enter');
|
await page.keyboard.press('Enter');
|
||||||
@@ -293,7 +293,7 @@ test.skip('drag a page card block to another page', async ({ page }) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.skip('drag a favourite page into blocksuite', async ({ page }) => {
|
test('drag a favourite page into blocksuite', async ({ page }) => {
|
||||||
await clickNewPageButton(page, 'hi from page');
|
await clickNewPageButton(page, 'hi from page');
|
||||||
await page.getByTestId('pin-button').click();
|
await page.getByTestId('pin-button').click();
|
||||||
const pageId = getCurrentDocIdFromUrl(page);
|
const pageId = getCurrentDocIdFromUrl(page);
|
||||||
|
|||||||
Reference in New Issue
Block a user