chore(core): some dnd events (#9206)

fix AF-1999
This commit is contained in:
pengx17 2024-12-19 07:42:13 +00:00
parent 30588783ef
commit da0f3d0b56
No known key found for this signature in database
GPG Key ID: 23F23D9E8B3971ED
11 changed files with 104 additions and 24 deletions

View File

@ -4,7 +4,11 @@ import { disableNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/elem
import { pointerOutsideOfPreview } from '@atlaskit/pragmatic-drag-and-drop/element/pointer-outside-of-preview'; import { pointerOutsideOfPreview } from '@atlaskit/pragmatic-drag-and-drop/element/pointer-outside-of-preview';
import { preserveOffsetOnSource } from '@atlaskit/pragmatic-drag-and-drop/element/preserve-offset-on-source'; import { preserveOffsetOnSource } from '@atlaskit/pragmatic-drag-and-drop/element/preserve-offset-on-source';
import { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview'; import { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview';
import type { DropTargetRecord } from '@atlaskit/pragmatic-drag-and-drop/types'; import type {
BaseEventPayload,
DropTargetRecord,
ElementDragType,
} from '@atlaskit/pragmatic-drag-and-drop/types';
import { useContext, useEffect, useMemo, useRef, useState } from 'react'; import { useContext, useEffect, useMemo, useRef, useState } from 'react';
import ReactDOM, { flushSync } from 'react-dom'; import ReactDOM, { flushSync } from 'react-dom';
@ -20,6 +24,10 @@ import {
export interface DraggableOptions<D extends DNDData = DNDData> { export interface DraggableOptions<D extends DNDData = DNDData> {
data?: DraggableGet<D['draggable']>; data?: DraggableGet<D['draggable']>;
toExternalData?: toExternalData<D>; toExternalData?: toExternalData<D>;
onDragStart?: (data: BaseEventPayload<ElementDragType>) => void;
onDrag?: (data: BaseEventPayload<ElementDragType>) => void;
onDrop?: (data: BaseEventPayload<ElementDragType>) => void;
onDropTargetChange?: (data: BaseEventPayload<ElementDragType>) => void;
canDrag?: DraggableGet<boolean>; canDrag?: DraggableGet<boolean>;
disableDragPreview?: boolean; disableDragPreview?: boolean;
dragPreviewPosition?: DraggableDragPreviewPosition; dragPreviewPosition?: DraggableDragPreviewPosition;
@ -126,8 +134,9 @@ export const useDraggable = <D extends DNDData = DNDData>(
if (dragRef.current) { if (dragRef.current) {
dragRef.current.dataset['dragging'] = 'true'; dragRef.current.dataset['dragging'] = 'true';
} }
options.onDragStart?.(args);
}, },
onDrop: () => { onDrop: args => {
if (enableDragging.current) { if (enableDragging.current) {
setDragging(false); setDragging(false);
} }
@ -148,6 +157,7 @@ export const useDraggable = <D extends DNDData = DNDData>(
if (dragRef.current) { if (dragRef.current) {
delete dragRef.current.dataset['dragging']; delete dragRef.current.dataset['dragging'];
} }
options.onDrop?.(args);
}, },
onDrag: args => { onDrag: args => {
if (enableDraggingPosition.current) { if (enableDraggingPosition.current) {
@ -163,11 +173,13 @@ export const useDraggable = <D extends DNDData = DNDData>(
outWindow: prev.outWindow, outWindow: prev.outWindow,
})); }));
} }
options.onDrag?.(args);
}, },
onDropTargetChange(args) { onDropTargetChange(args) {
if (enableDropTarget.current) { if (enableDropTarget.current) {
setDropTarget(args.location.current.dropTargets); setDropTarget(args.location.current.dropTargets);
} }
options.onDropTargetChange?.(args);
}, },
onGenerateDragPreview({ nativeSetDragImage, source, location }) { onGenerateDragPreview({ nativeSetDragImage, source, location }) {
if (options.disableDragPreview) { if (options.disableDragPreview) {

View File

@ -22,6 +22,7 @@ import { JournalService } from '@affine/core/modules/journal';
import { ViewIcon, ViewTitle } from '@affine/core/modules/workbench'; import { ViewIcon, ViewTitle } from '@affine/core/modules/workbench';
import type { AffineDNDData } from '@affine/core/types/dnd'; import type { AffineDNDData } from '@affine/core/types/dnd';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import type { Doc } from '@blocksuite/affine/store'; import type { Doc } from '@blocksuite/affine/store';
import { useLiveData, useService, type Workspace } from '@toeverything/infra'; import { useLiveData, useService, type Workspace } from '@toeverything/infra';
import { forwardRef, useCallback, useEffect, useRef, useState } from 'react'; import { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
@ -187,6 +188,9 @@ export function DetailPageHeader(
id: page.id, id: page.id,
}, },
}, },
onDragStart: () => {
track.$.header.$.dragStart();
},
dragPreviewPosition: 'pointer-outside', dragPreviewPosition: 'pointer-outside',
}; };
}, [page.id]); }, [page.id]);

View File

@ -219,6 +219,11 @@ const WorkbenchTab = ({
'text/uri-list': urls.join('\n'), 'text/uri-list': urls.join('\n'),
}; };
}, },
onDragStart: () => {
track.$.appTabsHeader.$.dragStart({
type: 'tab',
});
},
}; };
}, [dnd, workbench.basename, workbench.id, workbench.views]); }, [dnd, workbench.basename, workbench.id, workbench.views]);

View File

@ -38,7 +38,15 @@ export class DndService extends Service {
if (source.types.includes(type)) { if (source.types.includes(type)) {
const stringData = source.getStringData(type); const stringData = source.getStringData(type);
if (stringData) { if (stringData) {
return resolver(stringData); const entity = resolver(stringData);
if (entity) {
return {
entity,
from: {
at: 'external',
},
};
}
} }
} }
return null; return null;
@ -48,7 +56,7 @@ export class DndService extends Service {
private readonly resolvers: (( private readonly resolvers: ((
source: ExternalDragPayload source: ExternalDragPayload
) => Entity | null)[] = []; ) => AffineDNDData['draggable'] | null)[] = [];
getBlocksuiteDndAPI(sourceDocId?: string) { getBlocksuiteDndAPI(sourceDocId?: string) {
const collection = this.workspaceService.workspace.docCollection; const collection = this.workspaceService.workspace.docCollection;
@ -74,29 +82,23 @@ export class DndService extends Service {
if (!isDropEvent) { if (!isDropEvent) {
return {}; return {};
} }
const from: AffineDNDData['draggable']['from'] = {
at: 'external',
};
let entity: Entity | null = null; let resolved: AffineDNDData['draggable'] | null = null;
// in the order of the resolvers instead of the order of the types // in the order of the resolvers instead of the order of the types
for (const resolver of this.resolvers) { for (const resolver of this.resolvers) {
const candidate = resolver(args.source); const candidate = resolver(args.source);
if (candidate) { if (candidate) {
entity = candidate; resolved = candidate;
break; break;
} }
} }
if (!entity) { if (!resolved) {
return {}; // no resolver can handle this data return {}; // no resolver can handle this data
} }
return { return resolved;
from,
entity,
};
}; };
toExternalData: toExternalData<AffineDNDData> = (args, data) => { toExternalData: toExternalData<AffineDNDData> = (args, data) => {
@ -160,7 +162,7 @@ export class DndService extends Service {
private readonly resolveBlocksuiteExternalData = ( private readonly resolveBlocksuiteExternalData = (
source: ExternalDragPayload source: ExternalDragPayload
): Entity | null => { ): AffineDNDData['draggable'] | null => {
const dndAPI = this.getBlocksuiteDndAPI(); const dndAPI = this.getBlocksuiteDndAPI();
if (!dndAPI) { if (!dndAPI) {
return null; return null;
@ -173,7 +175,16 @@ export class DndService extends Service {
if (!snapshot) { if (!snapshot) {
return null; return null;
} }
return this.resolveBlockSnapshot(snapshot); const entity = this.resolveBlockSnapshot(snapshot);
if (!entity) {
return null;
}
return {
entity,
from: {
at: 'blocksuite-editor',
},
};
}; };
private readonly resolveHTML: EntityResolver = html => { private readonly resolveHTML: EntityResolver = html => {

View File

@ -125,6 +125,9 @@ export const ExplorerCollectionNode = ({
target: 'doc', target: 'doc',
control: 'drag', control: 'drag',
}); });
track.$.navigationPanel.collections.drop({
type: data.source.data.entity.type,
});
} }
} else { } else {
onDrop?.(data); onDrop?.(data);

View File

@ -138,6 +138,9 @@ export const ExplorerDocNode = ({
track.$.navigationPanel.docs.linkDoc({ track.$.navigationPanel.docs.linkDoc({
control: 'drag', control: 'drag',
}); });
track.$.navigationPanel.docs.drop({
type: data.source.data.entity.type,
});
} else { } else {
toast(t['com.affine.rootAppSidebar.doc.link-doc-only']()); toast(t['com.affine.rootAppSidebar.doc.link-doc-only']());
} }
@ -170,6 +173,9 @@ export const ExplorerDocNode = ({
track.$.navigationPanel.docs.linkDoc({ track.$.navigationPanel.docs.linkDoc({
control: 'drag', control: 'drag',
}); });
track.$.navigationPanel.docs.drop({
type: data.source.data.entity.type,
});
} else { } else {
toast(t['com.affine.rootAppSidebar.doc.link-doc-only']()); toast(t['com.affine.rootAppSidebar.doc.link-doc-only']());
} }

View File

@ -241,6 +241,11 @@ const ExplorerFolderNodeFolder = ({
const handleDropOnFolder = useCallback( const handleDropOnFolder = useCallback(
(data: DropTargetDropEvent<AffineDNDData>) => { (data: DropTargetDropEvent<AffineDNDData>) => {
if (data.source.data.entity?.type) {
track.$.navigationPanel.folders.drop({
type: data.source.data.entity.type,
});
}
if (data.treeInstruction?.type === 'make-child') { if (data.treeInstruction?.type === 'make-child') {
if (data.source.data.entity?.type === 'folder') { if (data.source.data.entity?.type === 'folder') {
if ( if (
@ -313,6 +318,11 @@ const ExplorerFolderNodeFolder = ({
const handleDropOnPlaceholder = useCallback( const handleDropOnPlaceholder = useCallback(
(data: DropTargetDropEvent<AffineDNDData>) => { (data: DropTargetDropEvent<AffineDNDData>) => {
if (data.source.data.entity?.type) {
track.$.navigationPanel.folders.drop({
type: data.source.data.entity.type,
});
}
if (data.source.data.entity?.type === 'folder') { if (data.source.data.entity?.type === 'folder') {
if ( if (
node.id === data.source.data.entity.id || node.id === data.source.data.entity.id ||
@ -353,6 +363,11 @@ const ExplorerFolderNodeFolder = ({
if (!dropAtNode || !dropAtNode.id) { if (!dropAtNode || !dropAtNode.id) {
return; return;
} }
if (data.source.data.entity?.type) {
track.$.navigationPanel.folders.drop({
type: data.source.data.entity.type,
});
}
if ( if (
data.treeInstruction?.type === 'reorder-above' || data.treeInstruction?.type === 'reorder-above' ||
data.treeInstruction?.type === 'reorder-below' data.treeInstruction?.type === 'reorder-below'

View File

@ -100,6 +100,9 @@ export const ExplorerTagNode = ({
track.$.navigationPanel.tags.tagDoc({ track.$.navigationPanel.tags.tagDoc({
control: 'drag', control: 'drag',
}); });
track.$.navigationPanel.tags.drop({
type: data.source.data.entity.type,
});
} else { } else {
toast(t['com.affine.rootAppSidebar.tag.doc-only']()); toast(t['com.affine.rootAppSidebar.tag.doc-only']());
} }

View File

@ -73,6 +73,9 @@ export const ExplorerFavorites = () => {
type: data.source.data.entity.type, type: data.source.data.entity.type,
on: true, on: true,
}); });
track.$.navigationPanel.favorites.drop({
type: data.source.data.entity.type,
});
explorerSection.setCollapsed(false); explorerSection.setCollapsed(false);
} }
}, },
@ -141,6 +144,9 @@ export const ExplorerFavorites = () => {
type: data.source.data.entity.type, type: data.source.data.entity.type,
on: true, on: true,
}); });
track.$.navigationPanel.favorites.drop({
type: data.source.data.entity.type,
});
} else { } else {
return; // not supported return; // not supported
} }

View File

@ -80,7 +80,10 @@ export interface AffineDNDData extends DNDData {
docId: string; docId: string;
} }
| { | {
at: 'external'; // for blocksuite or external apps at: 'blocksuite-editor';
}
| {
at: 'external'; // for external apps
}; };
}; };
dropTarget: dropTarget:

View File

@ -86,6 +86,8 @@ type OrganizeEvents =
| FolderEvents | FolderEvents
| TagEvents | TagEvents
| FavoriteEvents; | FavoriteEvents;
type DNDEvents = 'dragStart' | 'drag' | 'drop';
// END SECTION // END SECTION
// SECTION: cloud events // SECTION: cloud events
@ -127,7 +129,8 @@ type UserEvents =
| ShareEvents | ShareEvents
| AuthEvents | AuthEvents
| AccountEvents | AccountEvents
| PaymentEvents; | PaymentEvents
| DNDEvents;
interface PageDivision { interface PageDivision {
[page: string]: { [page: string]: {
[segment: string]: { [segment: string]: {
@ -209,12 +212,18 @@ const PageEvents = {
'openInNewTab', 'openInNewTab',
'openInSplitView', 'openInSplitView',
'toggleFavorite', 'toggleFavorite',
'drop',
], ],
docs: ['createDoc', 'deleteDoc', 'linkDoc'], docs: ['createDoc', 'deleteDoc', 'linkDoc', 'drop'],
collections: ['createDoc', 'addDocToCollection', 'removeOrganizeItem'], collections: [
folders: ['createDoc'], 'createDoc',
tags: ['createDoc', 'tagDoc'], 'addDocToCollection',
favorites: ['createDoc'], 'removeOrganizeItem',
'drop',
],
folders: ['createDoc', 'drop'],
tags: ['createDoc', 'tagDoc', 'drop'],
favorites: ['createDoc', 'drop'],
migrationData: ['openMigrationDataHelp'], migrationData: ['openMigrationDataHelp'],
bottomButtons: [ bottomButtons: [
'downloadApp', 'downloadApp',
@ -248,9 +257,10 @@ const PageEvents = {
aiAction: ['viewPlans'], aiAction: ['viewPlans'],
}, },
appTabsHeader: { appTabsHeader: {
$: ['tabAction'], $: ['tabAction', 'dragStart'],
}, },
header: { header: {
$: ['dragStart'],
actions: [ actions: [
'createDoc', 'createDoc',
'createWorkspace', 'createWorkspace',
@ -423,6 +433,8 @@ export type EventArgs = {
editProperty: { type: string }; editProperty: { type: string };
addProperty: { type: string; control: 'at menu' | 'property list' }; addProperty: { type: string; control: 'at menu' | 'property list' };
linkDoc: { type: string; journal: boolean }; linkDoc: { type: string; journal: boolean };
drop: { type: string };
dragStart: { type: string };
}; };
// for type checking // for type checking