mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-12-30 07:51:51 +03:00
parent
30588783ef
commit
da0f3d0b56
@ -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 { 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 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 ReactDOM, { flushSync } from 'react-dom';
|
||||
|
||||
@ -20,6 +24,10 @@ import {
|
||||
export interface DraggableOptions<D extends DNDData = DNDData> {
|
||||
data?: DraggableGet<D['draggable']>;
|
||||
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>;
|
||||
disableDragPreview?: boolean;
|
||||
dragPreviewPosition?: DraggableDragPreviewPosition;
|
||||
@ -126,8 +134,9 @@ export const useDraggable = <D extends DNDData = DNDData>(
|
||||
if (dragRef.current) {
|
||||
dragRef.current.dataset['dragging'] = 'true';
|
||||
}
|
||||
options.onDragStart?.(args);
|
||||
},
|
||||
onDrop: () => {
|
||||
onDrop: args => {
|
||||
if (enableDragging.current) {
|
||||
setDragging(false);
|
||||
}
|
||||
@ -148,6 +157,7 @@ export const useDraggable = <D extends DNDData = DNDData>(
|
||||
if (dragRef.current) {
|
||||
delete dragRef.current.dataset['dragging'];
|
||||
}
|
||||
options.onDrop?.(args);
|
||||
},
|
||||
onDrag: args => {
|
||||
if (enableDraggingPosition.current) {
|
||||
@ -163,11 +173,13 @@ export const useDraggable = <D extends DNDData = DNDData>(
|
||||
outWindow: prev.outWindow,
|
||||
}));
|
||||
}
|
||||
options.onDrag?.(args);
|
||||
},
|
||||
onDropTargetChange(args) {
|
||||
if (enableDropTarget.current) {
|
||||
setDropTarget(args.location.current.dropTargets);
|
||||
}
|
||||
options.onDropTargetChange?.(args);
|
||||
},
|
||||
onGenerateDragPreview({ nativeSetDragImage, source, location }) {
|
||||
if (options.disableDragPreview) {
|
||||
|
@ -22,6 +22,7 @@ import { JournalService } from '@affine/core/modules/journal';
|
||||
import { ViewIcon, ViewTitle } from '@affine/core/modules/workbench';
|
||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { track } from '@affine/track';
|
||||
import type { Doc } from '@blocksuite/affine/store';
|
||||
import { useLiveData, useService, type Workspace } from '@toeverything/infra';
|
||||
import { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
|
||||
@ -187,6 +188,9 @@ export function DetailPageHeader(
|
||||
id: page.id,
|
||||
},
|
||||
},
|
||||
onDragStart: () => {
|
||||
track.$.header.$.dragStart();
|
||||
},
|
||||
dragPreviewPosition: 'pointer-outside',
|
||||
};
|
||||
}, [page.id]);
|
||||
|
@ -219,6 +219,11 @@ const WorkbenchTab = ({
|
||||
'text/uri-list': urls.join('\n'),
|
||||
};
|
||||
},
|
||||
onDragStart: () => {
|
||||
track.$.appTabsHeader.$.dragStart({
|
||||
type: 'tab',
|
||||
});
|
||||
},
|
||||
};
|
||||
}, [dnd, workbench.basename, workbench.id, workbench.views]);
|
||||
|
||||
|
@ -38,7 +38,15 @@ export class DndService extends Service {
|
||||
if (source.types.includes(type)) {
|
||||
const stringData = source.getStringData(type);
|
||||
if (stringData) {
|
||||
return resolver(stringData);
|
||||
const entity = resolver(stringData);
|
||||
if (entity) {
|
||||
return {
|
||||
entity,
|
||||
from: {
|
||||
at: 'external',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@ -48,7 +56,7 @@ export class DndService extends Service {
|
||||
|
||||
private readonly resolvers: ((
|
||||
source: ExternalDragPayload
|
||||
) => Entity | null)[] = [];
|
||||
) => AffineDNDData['draggable'] | null)[] = [];
|
||||
|
||||
getBlocksuiteDndAPI(sourceDocId?: string) {
|
||||
const collection = this.workspaceService.workspace.docCollection;
|
||||
@ -74,29 +82,23 @@ export class DndService extends Service {
|
||||
if (!isDropEvent) {
|
||||
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
|
||||
for (const resolver of this.resolvers) {
|
||||
const candidate = resolver(args.source);
|
||||
if (candidate) {
|
||||
entity = candidate;
|
||||
resolved = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!entity) {
|
||||
if (!resolved) {
|
||||
return {}; // no resolver can handle this data
|
||||
}
|
||||
|
||||
return {
|
||||
from,
|
||||
entity,
|
||||
};
|
||||
return resolved;
|
||||
};
|
||||
|
||||
toExternalData: toExternalData<AffineDNDData> = (args, data) => {
|
||||
@ -160,7 +162,7 @@ export class DndService extends Service {
|
||||
|
||||
private readonly resolveBlocksuiteExternalData = (
|
||||
source: ExternalDragPayload
|
||||
): Entity | null => {
|
||||
): AffineDNDData['draggable'] | null => {
|
||||
const dndAPI = this.getBlocksuiteDndAPI();
|
||||
if (!dndAPI) {
|
||||
return null;
|
||||
@ -173,7 +175,16 @@ export class DndService extends Service {
|
||||
if (!snapshot) {
|
||||
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 => {
|
||||
|
@ -125,6 +125,9 @@ export const ExplorerCollectionNode = ({
|
||||
target: 'doc',
|
||||
control: 'drag',
|
||||
});
|
||||
track.$.navigationPanel.collections.drop({
|
||||
type: data.source.data.entity.type,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
onDrop?.(data);
|
||||
|
@ -138,6 +138,9 @@ export const ExplorerDocNode = ({
|
||||
track.$.navigationPanel.docs.linkDoc({
|
||||
control: 'drag',
|
||||
});
|
||||
track.$.navigationPanel.docs.drop({
|
||||
type: data.source.data.entity.type,
|
||||
});
|
||||
} else {
|
||||
toast(t['com.affine.rootAppSidebar.doc.link-doc-only']());
|
||||
}
|
||||
@ -170,6 +173,9 @@ export const ExplorerDocNode = ({
|
||||
track.$.navigationPanel.docs.linkDoc({
|
||||
control: 'drag',
|
||||
});
|
||||
track.$.navigationPanel.docs.drop({
|
||||
type: data.source.data.entity.type,
|
||||
});
|
||||
} else {
|
||||
toast(t['com.affine.rootAppSidebar.doc.link-doc-only']());
|
||||
}
|
||||
|
@ -241,6 +241,11 @@ const ExplorerFolderNodeFolder = ({
|
||||
|
||||
const handleDropOnFolder = useCallback(
|
||||
(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.source.data.entity?.type === 'folder') {
|
||||
if (
|
||||
@ -313,6 +318,11 @@ const ExplorerFolderNodeFolder = ({
|
||||
|
||||
const handleDropOnPlaceholder = useCallback(
|
||||
(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 (
|
||||
node.id === data.source.data.entity.id ||
|
||||
@ -353,6 +363,11 @@ const ExplorerFolderNodeFolder = ({
|
||||
if (!dropAtNode || !dropAtNode.id) {
|
||||
return;
|
||||
}
|
||||
if (data.source.data.entity?.type) {
|
||||
track.$.navigationPanel.folders.drop({
|
||||
type: data.source.data.entity.type,
|
||||
});
|
||||
}
|
||||
if (
|
||||
data.treeInstruction?.type === 'reorder-above' ||
|
||||
data.treeInstruction?.type === 'reorder-below'
|
||||
|
@ -100,6 +100,9 @@ export const ExplorerTagNode = ({
|
||||
track.$.navigationPanel.tags.tagDoc({
|
||||
control: 'drag',
|
||||
});
|
||||
track.$.navigationPanel.tags.drop({
|
||||
type: data.source.data.entity.type,
|
||||
});
|
||||
} else {
|
||||
toast(t['com.affine.rootAppSidebar.tag.doc-only']());
|
||||
}
|
||||
|
@ -73,6 +73,9 @@ export const ExplorerFavorites = () => {
|
||||
type: data.source.data.entity.type,
|
||||
on: true,
|
||||
});
|
||||
track.$.navigationPanel.favorites.drop({
|
||||
type: data.source.data.entity.type,
|
||||
});
|
||||
explorerSection.setCollapsed(false);
|
||||
}
|
||||
},
|
||||
@ -141,6 +144,9 @@ export const ExplorerFavorites = () => {
|
||||
type: data.source.data.entity.type,
|
||||
on: true,
|
||||
});
|
||||
track.$.navigationPanel.favorites.drop({
|
||||
type: data.source.data.entity.type,
|
||||
});
|
||||
} else {
|
||||
return; // not supported
|
||||
}
|
||||
|
@ -80,7 +80,10 @@ export interface AffineDNDData extends DNDData {
|
||||
docId: string;
|
||||
}
|
||||
| {
|
||||
at: 'external'; // for blocksuite or external apps
|
||||
at: 'blocksuite-editor';
|
||||
}
|
||||
| {
|
||||
at: 'external'; // for external apps
|
||||
};
|
||||
};
|
||||
dropTarget:
|
||||
|
@ -86,6 +86,8 @@ type OrganizeEvents =
|
||||
| FolderEvents
|
||||
| TagEvents
|
||||
| FavoriteEvents;
|
||||
|
||||
type DNDEvents = 'dragStart' | 'drag' | 'drop';
|
||||
// END SECTION
|
||||
|
||||
// SECTION: cloud events
|
||||
@ -127,7 +129,8 @@ type UserEvents =
|
||||
| ShareEvents
|
||||
| AuthEvents
|
||||
| AccountEvents
|
||||
| PaymentEvents;
|
||||
| PaymentEvents
|
||||
| DNDEvents;
|
||||
interface PageDivision {
|
||||
[page: string]: {
|
||||
[segment: string]: {
|
||||
@ -209,12 +212,18 @@ const PageEvents = {
|
||||
'openInNewTab',
|
||||
'openInSplitView',
|
||||
'toggleFavorite',
|
||||
'drop',
|
||||
],
|
||||
docs: ['createDoc', 'deleteDoc', 'linkDoc'],
|
||||
collections: ['createDoc', 'addDocToCollection', 'removeOrganizeItem'],
|
||||
folders: ['createDoc'],
|
||||
tags: ['createDoc', 'tagDoc'],
|
||||
favorites: ['createDoc'],
|
||||
docs: ['createDoc', 'deleteDoc', 'linkDoc', 'drop'],
|
||||
collections: [
|
||||
'createDoc',
|
||||
'addDocToCollection',
|
||||
'removeOrganizeItem',
|
||||
'drop',
|
||||
],
|
||||
folders: ['createDoc', 'drop'],
|
||||
tags: ['createDoc', 'tagDoc', 'drop'],
|
||||
favorites: ['createDoc', 'drop'],
|
||||
migrationData: ['openMigrationDataHelp'],
|
||||
bottomButtons: [
|
||||
'downloadApp',
|
||||
@ -248,9 +257,10 @@ const PageEvents = {
|
||||
aiAction: ['viewPlans'],
|
||||
},
|
||||
appTabsHeader: {
|
||||
$: ['tabAction'],
|
||||
$: ['tabAction', 'dragStart'],
|
||||
},
|
||||
header: {
|
||||
$: ['dragStart'],
|
||||
actions: [
|
||||
'createDoc',
|
||||
'createWorkspace',
|
||||
@ -423,6 +433,8 @@ export type EventArgs = {
|
||||
editProperty: { type: string };
|
||||
addProperty: { type: string; control: 'at menu' | 'property list' };
|
||||
linkDoc: { type: string; journal: boolean };
|
||||
drop: { type: string };
|
||||
dragStart: { type: string };
|
||||
};
|
||||
|
||||
// for type checking
|
||||
|
Loading…
Reference in New Issue
Block a user