Skip to content

Commit

Permalink
feat(core): drop doc onto split view (#9487)
Browse files Browse the repository at this point in the history
fix AF-2068, AF-2069, AF-1175, AF-2061, AF-2079, AF-2034, AF-2080, AF-1960, AF-2081

1. replace `dnd-kit` with `@atlaskit/pragmatic-drag-and-drop`
2. allow creating split views by drag & drop the following
   a. WorkbenchLinks (route links), like journals, trash, all docs
   b. doc refs
   c. tags/collection
3. style adjustments to split view
4. remove split view's feature flag and make it GA for electron

https://github.com/user-attachments/assets/6a3e4a25-faa2-4215-8eb0-983f44db6e8c
  • Loading branch information
pengx17 committed Jan 8, 2025
1 parent c0ed74d commit a4841bb
Show file tree
Hide file tree
Showing 53 changed files with 1,569 additions and 900 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ export const showTabContextMenu = async (
];
const menu = Menu.buildFromTemplate(template);
menu.popup();

let unsub: (() => void) | undefined;
const subscription = WebContentViewsManager.instance.tabAction$.subscribe(
action => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { assignInlineVars } from '@vanilla-extract/dynamic';
import clsx from 'clsx';
import { forwardRef, useCallback, useLayoutEffect, useRef } from 'react';
import { useTransition } from 'react-transition-state';
import { useTransitionState } from 'react-transition-state';

import { useDropTarget } from '../../ui/dnd';
import { Tooltip, type TooltipProps } from '../../ui/tooltip';
import * as styles from './resize-panel.css';

Expand All @@ -22,6 +23,7 @@ export interface ResizeHandleProps
tooltipShortcut?: TooltipProps['shortcut'];
tooltipOptions?: Partial<Omit<TooltipProps, 'content' | 'shortcut'>>;
tooltipShortcutClassName?: string;
dropTargetOptions?: Parameters<typeof useDropTarget>[0];
}

export interface ResizePanelProps
Expand All @@ -40,6 +42,7 @@ export interface ResizePanelProps
resizeHandleTooltipOptions?: Partial<
Omit<TooltipProps, 'content' | 'shortcut'>
>;
resizeHandleDropTargetOptions?: Parameters<typeof useDropTarget>[0];
enableAnimation?: boolean;
width: number;
unmountOnExit?: boolean;
Expand All @@ -56,6 +59,7 @@ const ResizeHandle = ({
resizeHandlePos,
resizeHandleOffset,
resizeHandleVerticalPadding,
dropTargetOptions,
open,
onOpen,
onResizing,
Expand Down Expand Up @@ -115,6 +119,10 @@ const ResizeHandle = ({
[maxWidth, resizeHandlePos, minWidth, onWidthChange, onResizing, onOpen]
);

const { dropTargetRef } = useDropTarget(dropTargetOptions, [
dropTargetOptions,
]);

return (
<Tooltip
content={tooltip}
Expand All @@ -125,7 +133,10 @@ const ResizeHandle = ({
<div
{...rest}
data-testid="resize-handle"
ref={ref}
ref={node => {
ref.current = node;
dropTargetRef.current = node;
}}
style={assignInlineVars({
[styles.resizeHandleOffsetVar]: `${resizeHandleOffset ?? 0}px`,
[styles.resizeHandleVerticalPadding]: `${
Expand Down Expand Up @@ -169,17 +180,18 @@ export const ResizePanel = forwardRef<HTMLDivElement, ResizePanelProps>(
resizeHandleTooltipShortcut,
resizeHandleTooltipShortcutClassName,
resizeHandleTooltipOptions,
resizeHandleDropTargetOptions,
...rest
},
ref
) {
const safeWidth = Math.min(maxWidth, Math.max(minWidth, width));
const [{ status }, toggle] = useTransition({
const [{ status }, toggle] = useTransitionState({
timeout: animationTimeout,
});
useLayoutEffect(() => {
toggle(open);
}, [open]);
}, [open, toggle]);
return (
<div
{...rest}
Expand Down Expand Up @@ -213,6 +225,7 @@ export const ResizePanel = forwardRef<HTMLDivElement, ResizePanelProps>(
onWidthChange={onWidthChange}
open={open}
resizing={resizing}
dropTargetOptions={resizeHandleDropTargetOptions}
/>
</div>
);
Expand Down
33 changes: 33 additions & 0 deletions packages/frontend/component/src/ui/dnd/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { DNDData, fromExternalData } from './types';

export const isExternalDrag = <D extends DNDData>(args: {
source: { data: D['draggable'] };
}) => {
return !args.source['data'];
};

export const getAdaptedEventArgs = <
D extends DNDData,
Args extends { source: { data: D['draggable'] } },
>(
args: Args,
fromExternalData?: fromExternalData<D>,
isDropEvent = false
): Args => {
const data =
isExternalDrag(args) && fromExternalData
? fromExternalData(
// @ts-expect-error hack for external data adapter (source has no data field)
args as ExternalGetDataFeedbackArgs,
isDropEvent
)
: args.source['data'];

return {
...args,
source: {
...args.source,
data,
},
};
};
1 change: 0 additions & 1 deletion packages/frontend/component/src/ui/dnd/draggable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ export const useDraggable = <D extends DNDData = DNDData>(

const context = useContext(DNDContext);

// eslint-disable-next-line react-hooks/exhaustive-deps
const options = useMemo(() => {
const opts = getOptions();

Expand Down
158 changes: 79 additions & 79 deletions packages/frontend/component/src/ui/dnd/drop-target.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {
} from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
import { useContext, useEffect, useMemo, useRef, useState } from 'react';

import { shallowUpdater } from '../../utils';
import { getAdaptedEventArgs, isExternalDrag } from './common';
import { DNDContext } from './context';
import type { DNDData, fromExternalData } from './types';

Expand All @@ -44,7 +46,7 @@ export type DropTargetTreeInstruction = Instruction;

export type ExternalDragPayload = ExternalDragType['payload'];

type DropTargetGetFeedback<D extends DNDData> = Parameters<
export type DropTargetGetFeedback<D extends DNDData> = Parameters<
NonNullable<Parameters<typeof dropTargetForElements>[0]['canDrop']>
>[0] & {
source: {
Expand All @@ -59,38 +61,6 @@ type DropTargetGet<T, D extends DNDData> =
| T
| ((data: DropTargetGetFeedback<D>) => T);

const isExternalDrag = <D extends DNDData>(
args: Pick<DropTargetGetFeedback<D>, 'source'>
) => {
return !args.source['data'];
};

const getAdaptedEventArgs = <
D extends DNDData,
Args extends Pick<DropTargetGetFeedback<D>, 'source'>,
>(
options: DropTargetOptions<D>,
args: Args,
isDropEvent = false
): Args => {
const data =
isExternalDrag(args) && options.fromExternalData
? options.fromExternalData(
// @ts-expect-error hack for external data adapter (source has no data field)
args as ExternalGetDataFeedbackArgs,
isDropEvent
)
: args.source['data'];

return {
...args,
source: {
...args.source,
data,
},
};
};

function dropTargetGet<T, D extends DNDData>(
get: T,
options: DropTargetOptions<D>
Expand All @@ -110,7 +80,7 @@ function dropTargetGet<T, D extends DNDData>(
) => {
if (typeof get === 'function') {
return (get as any)({
...getAdaptedEventArgs(options, args),
...getAdaptedEventArgs(args, options.fromExternalData),
get treeInstruction() {
return options.treeInstruction
? extractInstruction(
Expand Down Expand Up @@ -145,8 +115,8 @@ function dropTargetGet<T, D extends DNDData>(
});
} else {
return {
...getAdaptedEventArgs(args, options.fromExternalData),
...get,
...getAdaptedEventArgs(options, args),
};
}
}) as any;
Expand All @@ -168,6 +138,8 @@ export interface DropTargetOptions<D extends DNDData = DNDData> {
};
onDrop?: (data: DropTargetDropEvent<D>) => void;
onDrag?: (data: DropTargetDragEvent<D>) => void;
onDragEnter?: (data: DropTargetDragEvent<D>) => void;
onDragLeave?: (data: DropTargetDragEvent<D>) => void;
/**
* external data adapter.
* Will use the external data adapter from the context if not provided.
Expand Down Expand Up @@ -232,6 +204,55 @@ export const useDropTarget = <D extends DNDData = DNDData>(
const dropTargetOptions = useMemo(() => {
const wrappedCanDrop = dropTargetGet(options.canDrop, options);
let _element: HTMLElement | null = null;

const updateDragOver = (
args: DropTargetDragEvent<D>,
handler?: (data: DropTargetDragEvent<D>) => void
) => {
args = getAdaptedEventArgs(args, options.fromExternalData);
if (
args.location.current.dropTargets[0]?.element === dropTargetRef.current
) {
if (enableDraggedOverDraggable.current) {
setDraggedOverDraggable(shallowUpdater(args.source));
}
let instruction = null;
let closestEdge = null;
if (options.treeInstruction) {
instruction = extractInstruction(args.self.data);
setTreeInstruction(shallowUpdater(instruction));
if (dropTargetRef.current) {
dropTargetRef.current.dataset['treeInstruction'] =
instruction?.type;
}
}
if (options.closestEdge) {
closestEdge = extractClosestEdge(args.self.data);
setClosestEdge(shallowUpdater(closestEdge));
}
if (enableDropEffect.current) {
setDropEffect(shallowUpdater(args.self.dropEffect));
}
if (enableDraggedOverPosition.current) {
const rect = args.self.element.getBoundingClientRect();
const { clientX, clientY } = args.location.current.input;
setDraggedOverPosition(
shallowUpdater({
relativeX: clientX - rect.x,
relativeY: clientY - rect.y,
clientX: clientX,
clientY: clientY,
})
);
}
handler?.({
...args,
treeInstruction: instruction,
closestEdge,
} as DropTargetDropEvent<D>);
}
};

return {
get element() {
if (!_element) {
Expand Down Expand Up @@ -285,7 +306,7 @@ export const useDropTarget = <D extends DNDData = DNDData>(

// external data is only available in drop event thus
// this is the only case for getAdaptedEventArgs
const args = getAdaptedEventArgs(options, _args, true);
const args = getAdaptedEventArgs(_args, options.fromExternalData, true);
if (
isExternalDrag(_args) &&
options.fromExternalData &&
Expand All @@ -309,7 +330,7 @@ export const useDropTarget = <D extends DNDData = DNDData>(
}
},
getData: (args: DropTargetGetFeedback<D>) => {
args = getAdaptedEventArgs(options, args);
args = getAdaptedEventArgs(args, options.fromExternalData);
const originData = dropTargetGet(options.data ?? {}, options)(args);
const { input, element } = args;
const withInstruction = options.treeInstruction
Expand All @@ -332,50 +353,29 @@ export const useDropTarget = <D extends DNDData = DNDData>(
return withClosestEdge;
},
onDrag: (args: DropTargetDragEvent<D>) => {
args = getAdaptedEventArgs(options, args);
if (
args.location.current.dropTargets[0]?.element ===
dropTargetRef.current
) {
if (enableDraggedOverDraggable.current) {
setDraggedOverDraggable(args.source);
}
let instruction = null;
let closestEdge = null;
if (options.treeInstruction) {
instruction = extractInstruction(args.self.data);
setTreeInstruction(instruction);
if (dropTargetRef.current) {
dropTargetRef.current.dataset['treeInstruction'] =
instruction?.type;
}
}
if (options.closestEdge) {
closestEdge = extractClosestEdge(args.self.data);
setClosestEdge(closestEdge);
}
if (enableDropEffect.current) {
setDropEffect(args.self.dropEffect);
}
if (enableDraggedOverPosition.current) {
const rect = args.self.element.getBoundingClientRect();
const { clientX, clientY } = args.location.current.input;
setDraggedOverPosition({
relativeX: clientX - rect.x,
relativeY: clientY - rect.y,
clientX: clientX,
clientY: clientY,
});
}
options.onDrag?.({
...args,
treeInstruction: instruction,
closestEdge,
} as DropTargetDropEvent<D>);
}
updateDragOver(args, options.onDrag);
},
onDragEnter: (args: DropTargetDragEvent<D>) => {
updateDragOver(args, options.onDragEnter);
},
onDragLeave: (args: DropTargetDragEvent<D>) => {
args = getAdaptedEventArgs(args, options.fromExternalData);

const withClosestEdge = options.closestEdge
? attachClosestEdge(args.self.data, {
element: args.self.element,
input: args.location.current.input,
allowedEdges: options.closestEdge.allowedEdges,
})
: args.self.data;

options.onDragLeave?.({
...args,
self: { ...args.self, data: withClosestEdge },
});
},
onDropTargetChange: (args: DropTargetDropEvent<D>) => {
args = getAdaptedEventArgs(options, args);
args = getAdaptedEventArgs(args, options.fromExternalData);
if (
args.location.current.dropTargets[0]?.element ===
dropTargetRef.current
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/component/src/ui/dnd/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export * from './context';
export * from './draggable';
export * from './drop-indicator';
export * from './drop-target';
export * from './monitor';
export * from './types';
Loading

0 comments on commit a4841bb

Please sign in to comment.