diff --git a/packages/frontend/component/src/ui/dnd/drop-target.ts b/packages/frontend/component/src/ui/dnd/drop-target.ts index 6b758ab114afc..908e810424d33 100644 --- a/packages/frontend/component/src/ui/dnd/drop-target.ts +++ b/packages/frontend/component/src/ui/dnd/drop-target.ts @@ -19,6 +19,7 @@ import { } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item'; import { useContext, useEffect, useMemo, useRef, useState } from 'react'; +import { shallowEqual } from '../../utils'; import { DNDContext } from './context'; import type { DNDData, fromExternalData } from './types'; @@ -152,6 +153,15 @@ function dropTargetGet( }) as any; } +const shallowUpdater = + (newState: T) => + (state: T | null): T => { + if (state && shallowEqual(state, newState)) { + return state; + } + return newState; + }; + export interface DropTargetOptions { data?: DropTargetGet; canDrop?: DropTargetGet; @@ -168,6 +178,8 @@ export interface DropTargetOptions { }; onDrop?: (data: DropTargetDropEvent) => void; onDrag?: (data: DropTargetDragEvent) => void; + onDragEnter?: (data: DropTargetDragEvent) => void; + onDragLeave?: (data: DropTargetDragEvent) => void; /** * external data adapter. * Will use the external data adapter from the context if not provided. @@ -232,6 +244,55 @@ export const useDropTarget = ( const dropTargetOptions = useMemo(() => { const wrappedCanDrop = dropTargetGet(options.canDrop, options); let _element: HTMLElement | null = null; + + const updateDragOver = ( + args: DropTargetDragEvent, + handler?: (data: DropTargetDragEvent) => void + ) => { + args = getAdaptedEventArgs(options, args); + 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); + } + }; + return { get element() { if (!_element) { @@ -332,47 +393,13 @@ export const useDropTarget = ( return withClosestEdge; }, onDrag: (args: DropTargetDragEvent) => { - 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); - } + updateDragOver(args, options.onDrag); + }, + onDragEnter: (args: DropTargetDragEvent) => { + updateDragOver(args, options.onDragEnter); + }, + onDragLeave: (args: DropTargetDragEvent) => { + options.onDragLeave?.(args); }, onDropTargetChange: (args: DropTargetDropEvent) => { args = getAdaptedEventArgs(options, args); diff --git a/packages/frontend/component/src/ui/dnd/monitor.tsx b/packages/frontend/component/src/ui/dnd/monitor.tsx new file mode 100644 index 0000000000000..163d5bd1a1f27 --- /dev/null +++ b/packages/frontend/component/src/ui/dnd/monitor.tsx @@ -0,0 +1,57 @@ +import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter'; +import type { + BaseEventPayload, + ElementDragType, +} from '@atlaskit/pragmatic-drag-and-drop/types'; +import { useEffect, useMemo } from 'react'; + +type MonitorGetFeedback = Parameters< + NonNullable[0]['canMonitor']> +>[0]; + +type MonitorGet = T | ((data: MonitorGetFeedback) => T); + +export interface MonitorOptions { + canMonitor?: MonitorGet; + onDragStart?: (data: BaseEventPayload) => void; + onDrag?: (data: BaseEventPayload) => void; + onDrop?: (data: BaseEventPayload) => void; + onDropTargetChange?: (data: BaseEventPayload) => void; +} + +function monitorGet( + get: T +): T extends undefined + ? undefined + : T extends MonitorGet + ? (args: MonitorGetFeedback) => I + : never { + if (get === undefined) { + return undefined as any; + } + return ((args: MonitorGetFeedback) => + typeof get === 'function' ? (get as any)(args) : get) as any; +} + +export const useMonitor = ( + getOptions: () => MonitorOptions = () => ({}), + deps: any[] = [] +) => { + const options = useMemo(() => { + const opts = getOptions(); + return { + ...opts, + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [...deps, getOptions]); + + useEffect(() => { + return monitorForElements({ + canMonitor: monitorGet(options.canMonitor), + onDragStart: monitorGet(options.onDragStart), + onDrag: monitorGet(options.onDrag), + onDrop: monitorGet(options.onDrop), + onDropTargetChange: monitorGet(options.onDropTargetChange), + }); + }, [options]); +}; diff --git a/packages/frontend/component/src/ui/dnd/types.ts b/packages/frontend/component/src/ui/dnd/types.ts index 9c6c26b938ede..ae588d0e3b212 100644 --- a/packages/frontend/component/src/ui/dnd/types.ts +++ b/packages/frontend/component/src/ui/dnd/types.ts @@ -1,6 +1,8 @@ import type { draggable } from '@atlaskit/pragmatic-drag-and-drop/element/adapter'; import type { dropTargetForExternal } from '@atlaskit/pragmatic-drag-and-drop/external/adapter'; +export type { Edge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge'; + export interface DNDData< Draggable extends Record = Record, DropTarget extends Record = Record, diff --git a/packages/frontend/component/src/utils/index.ts b/packages/frontend/component/src/utils/index.ts index 8aab883c19eb9..861f83c636b6d 100644 --- a/packages/frontend/component/src/utils/index.ts +++ b/packages/frontend/component/src/utils/index.ts @@ -1,4 +1,5 @@ export * from './observe-intersection'; export * from './observe-resize'; +export * from './shallow-equal'; export { startScopedViewTransition } from './view-transition'; export * from './with-unit'; diff --git a/packages/frontend/component/src/utils/shallow-equal.ts b/packages/frontend/component/src/utils/shallow-equal.ts new file mode 100644 index 0000000000000..b9d4a9d7a3f15 --- /dev/null +++ b/packages/frontend/component/src/utils/shallow-equal.ts @@ -0,0 +1,34 @@ +// credit: https://github.com/facebook/fbjs/blob/main/packages/fbjs/src/core/shallowEqual.js +export function shallowEqual(objA: any, objB: any) { + if (Object.is(objA, objB)) { + return true; + } + + if ( + typeof objA !== 'object' || + objA === null || + typeof objB !== 'object' || + objB === null + ) { + return false; + } + + const keysA = Object.keys(objA); + const keysB = Object.keys(objB); + + if (keysA.length !== keysB.length) { + return false; + } + + // Test for A's keys different from B. + for (const key of keysA) { + if ( + !Object.prototype.hasOwnProperty.call(objB, key) || + !Object.is(objA[key], objB[key]) + ) { + return false; + } + } + + return true; +} diff --git a/packages/frontend/core/src/components/page-list/page-group.tsx b/packages/frontend/core/src/components/page-list/page-group.tsx index 50cfa8476012e..6f075dc290b1f 100644 --- a/packages/frontend/core/src/components/page-list/page-group.tsx +++ b/packages/frontend/core/src/components/page-list/page-group.tsx @@ -1,3 +1,4 @@ +import { shallowEqual } from '@affine/component'; import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; import type { Tag } from '@affine/env/filter'; import { useI18n } from '@affine/i18n'; @@ -34,7 +35,6 @@ import type { TagListItemProps, TagMeta, } from './types'; -import { shallowEqual } from './utils'; export const ItemGroupHeader = memo(function ItemGroupHeader< T extends ListItem, diff --git a/packages/frontend/core/src/components/page-list/scoped-atoms.tsx b/packages/frontend/core/src/components/page-list/scoped-atoms.tsx index 226994695c4c5..dfb10282c5342 100644 --- a/packages/frontend/core/src/components/page-list/scoped-atoms.tsx +++ b/packages/frontend/core/src/components/page-list/scoped-atoms.tsx @@ -1,3 +1,4 @@ +import { shallowEqual } from '@affine/component'; import { DEFAULT_SORT_KEY } from '@affine/env/constant'; import { atom } from 'jotai'; import { selectAtom } from 'jotai/utils'; @@ -10,7 +11,6 @@ import type { MetaRecord, VirtualizedListProps, } from './types'; -import { shallowEqual } from './utils'; // for ease of use in the component tree // note: must use selectAtom to access this atom for efficiency diff --git a/packages/frontend/core/src/components/page-list/utils.tsx b/packages/frontend/core/src/components/page-list/utils.tsx index 1a63f2fe36441..7cdc2553dfd87 100644 --- a/packages/frontend/core/src/components/page-list/utils.tsx +++ b/packages/frontend/core/src/components/page-list/utils.tsx @@ -56,38 +56,3 @@ export const betweenDaysAgo = ( ): boolean => { return !withinDaysAgo(date, days0) && withinDaysAgo(date, days1); }; - -// credit: https://github.com/facebook/fbjs/blob/main/packages/fbjs/src/core/shallowEqual.js -export function shallowEqual(objA: any, objB: any) { - if (Object.is(objA, objB)) { - return true; - } - - if ( - typeof objA !== 'object' || - objA === null || - typeof objB !== 'object' || - objB === null - ) { - return false; - } - - const keysA = Object.keys(objA); - const keysB = Object.keys(objB); - - if (keysA.length !== keysB.length) { - return false; - } - - // Test for A's keys different from B. - for (const key of keysA) { - if ( - !Object.prototype.hasOwnProperty.call(objB, key) || - !Object.is(objA[key], objB[key]) - ) { - return false; - } - } - - return true; -} diff --git a/packages/frontend/core/src/modules/workbench/view/split-view/indicator.css.ts b/packages/frontend/core/src/modules/workbench/view/split-view/indicator.css.ts index 9a2755ea94492..efb9e6dde3239 100644 --- a/packages/frontend/core/src/modules/workbench/view/split-view/indicator.css.ts +++ b/packages/frontend/core/src/modules/workbench/view/split-view/indicator.css.ts @@ -23,6 +23,7 @@ export const menuTrigger = style({ height: 0, pointerEvents: 'none', }); + export const indicator = style({ width: 29, height: 15, @@ -48,7 +49,7 @@ export const indicatorInner = style({ transition: 'all 0.5s cubic-bezier(0.16, 1, 0.3, 1)', selectors: { - '[data-is-dragging="true"] &': { + '[data-is-dragging="true"] &, &:active': { width: 24, height: 2, }, diff --git a/packages/frontend/core/src/modules/workbench/view/split-view/indicator.tsx b/packages/frontend/core/src/modules/workbench/view/split-view/indicator.tsx index 29425e878fb30..64cc94a0696ff 100644 --- a/packages/frontend/core/src/modules/workbench/view/split-view/indicator.tsx +++ b/packages/frontend/core/src/modules/workbench/view/split-view/indicator.tsx @@ -4,41 +4,21 @@ import clsx from 'clsx'; import type { HTMLAttributes, MouseEventHandler } from 'react'; import { forwardRef, memo, useCallback, useMemo, useState } from 'react'; +import type { View } from '../../entities/view'; import * as styles from './indicator.css'; export interface SplitViewMenuProps extends HTMLAttributes { active?: boolean; open?: boolean; onOpenMenu?: () => void; - setPressed: (v: boolean) => void; } export const SplitViewMenuIndicator = memo( forwardRef( function SplitViewMenuIndicator( - { - className, - active, - open, - setPressed, - onOpenMenu, - ...attrs - }: SplitViewMenuProps, + { className, active, open, onOpenMenu, ...attrs }: SplitViewMenuProps, ref ) { - // dnd's `isDragging` changes after mouseDown and mouseMoved - const onMouseDown = useCallback(() => { - const t = setTimeout(() => setPressed(true), 100); - window.addEventListener( - 'mouseup', - () => { - clearTimeout(t); - setPressed(false); - }, - { once: true } - ); - }, [setPressed]); - const onClick: MouseEventHandler = useCallback(() => { !open && onOpenMenu?.(); }, [onOpenMenu, open]); @@ -50,7 +30,6 @@ export const SplitViewMenuIndicator = memo( data-testid="split-view-indicator" className={clsx(className, styles.indicator)} onClick={onClick} - onMouseDown={onMouseDown} {...attrs} >
@@ -61,64 +40,68 @@ export const SplitViewMenuIndicator = memo( ); interface SplitViewIndicatorProps extends HTMLAttributes { - isDragging?: boolean; + view: View; isActive?: boolean; + isDragging?: boolean; menuItems?: React.ReactNode; - // import type { SyntheticListenerMap } from '@dnd-kit/core/dist/hooks/utilities' is not allowed - listeners?: any; setPressed?: (pressed: boolean) => void; + dragHandleRef?: React.RefObject; } -export const SplitViewIndicator = ({ - isDragging, - isActive, - menuItems, - listeners, - setPressed, -}: SplitViewIndicatorProps) => { - const active = isActive || isDragging; - const [menuOpen, setMenuOpen] = useState(false); +export const SplitViewIndicator = memo( + forwardRef( + function SplitViewIndicator( + { isActive, menuItems, isDragging, dragHandleRef }, + ref + ) { + const [menuOpen, setMenuOpen] = useState(false); - // prevent menu from opening when dragging - const setOpenMenuManually = useCallback((open: boolean) => { - if (open) return; - setMenuOpen(open); - }, []); - const openMenu = useCallback(() => { - setMenuOpen(true); - }, []); + // prevent menu from opening when dragging + const setOpenMenuManually = useCallback((open: boolean) => { + if (open) return; + setMenuOpen(open); + }, []); - const menuRootOptions = useMemo( - () => - ({ - open: menuOpen, - onOpenChange: setOpenMenuManually, - }) satisfies MenuProps['rootOptions'], - [menuOpen, setOpenMenuManually] - ); - const menuContentOptions = useMemo( - () => - ({ - align: 'center', - }) satisfies MenuProps['contentOptions'], - [] - ); + const openMenu = useCallback(() => { + setMenuOpen(true); + }, []); - return ( -
- -
-
- -
- ); -}; + const menuRootOptions = useMemo( + () => + ({ + open: menuOpen, + onOpenChange: setOpenMenuManually, + }) satisfies MenuProps['rootOptions'], + [menuOpen, setOpenMenuManually] + ); + const menuContentOptions = useMemo( + () => + ({ + align: 'center', + }) satisfies MenuProps['contentOptions'], + [] + ); + + return ( +
+ +
+
+ +
+ ); + } + ) +); diff --git a/packages/frontend/core/src/modules/workbench/view/split-view/panel.tsx b/packages/frontend/core/src/modules/workbench/view/split-view/panel.tsx index 65f3aaeab5d79..bb0f36116ad77 100644 --- a/packages/frontend/core/src/modules/workbench/view/split-view/panel.tsx +++ b/packages/frontend/core/src/modules/workbench/view/split-view/panel.tsx @@ -1,4 +1,10 @@ -import { MenuItem } from '@affine/component'; +import { + MenuItem, + useDraggable, + useDropTarget, + type Edge, +} from '@affine/component'; +import type { AffineDNDData } from '@affine/core/types/dnd'; import { useI18n } from '@affine/i18n'; import { ExpandCloseIcon, @@ -6,24 +12,10 @@ import { MoveToRightDuotoneIcon, SoloViewIcon, } from '@blocksuite/icons/rc'; -import { useSortable } from '@dnd-kit/sortable'; import { useLiveData, useService } from '@toeverything/infra'; import { assignInlineVars } from '@vanilla-extract/dynamic'; -import type { - Dispatch, - HTMLAttributes, - PropsWithChildren, - RefObject, - SetStateAction, -} from 'react'; -import { - memo, - useCallback, - useLayoutEffect, - useMemo, - useRef, - useState, -} from 'react'; +import type { HTMLAttributes, PropsWithChildren } from 'react'; +import { memo, useCallback, useMemo } from 'react'; import type { View } from '../../entities/view'; import { WorkbenchService } from '../../services/workbench'; @@ -34,9 +26,8 @@ export interface SplitViewPanelProps extends PropsWithChildren> { view: View; resizeHandle?: React.ReactNode; - setSlots?: Dispatch< - SetStateAction>> - >; + isDragging?: boolean; + onMove?: (from: number, to: number) => void; } export const SplitViewPanelContainer = ({ @@ -50,36 +41,34 @@ export const SplitViewPanelContainer = ({ ); }; +export type SplitViewPanelDraggingState = + | { + type: 'idle'; + } + | { + type: 'is-over'; + viewId: string; + dragging: DOMRect; + closestEdge: Edge; + }; + export const SplitViewPanel = memo(function SplitViewPanel({ children, + resizeHandle, view, - setSlots, + onMove, + isDragging, }: SplitViewPanelProps) { - const [indicatorPressed, setIndicatorPressed] = useState(false); - const ref = useRef(null); const size = useLiveData(view.size$); const workbench = useService(WorkbenchService).workbench; const activeView = useLiveData(workbench.activeView$); const views = useLiveData(workbench.views$); const isLast = views[views.length - 1] === view; - - const { - attributes, - listeners, - transform, - transition, - isDragging: dndIsDragging, - setNodeRef, - } = useSortable({ id: view.id, attributes: { role: 'group' } }); - - const isDragging = dndIsDragging || indicatorPressed; const isActive = activeView === view; - useLayoutEffect(() => { - if (ref.current) { - setSlots?.(slots => ({ ...slots, [view.id]: ref })); - } - }, [setSlots, view.id]); + const [state, setState] = useState({ + type: 'idle', + }); const style = useMemo( () => ({ @@ -87,13 +76,29 @@ export const SplitViewPanel = memo(function SplitViewPanel({ }), [size] ); - const dragStyle = useMemo( - () => ({ - transform: `translate3d(${transform?.x ?? 0}px, 0, 0)`, - transition, - }), - [transform, transition] - ); + + const { dropTargetRef } = useDropTarget(() => { + return { + canDrop(data) { + return ( + data.source.data.from?.at === 'workbench:view' && + data.source.data.from?.viewId !== view.id + ); + }, + }; + }, [view.id]); + + const { dragRef, dragging } = useDraggable(() => { + return { + data: { + from: { + at: 'workbench:view', + viewId: view.id, + }, + }, + disableDragPreview: true, + }; + }, [view.id]); return ( 1} data-is-last={isLast} > -
-
- {views.length > 1 ? ( +
+
+ {children} +
+ {views.length > 1 && onMove ? ( } - setPressed={setIndicatorPressed} + isDragging={dragging} + dragHandleRef={dragRef} + menuItems={} /> ) : null}
- {children} + {resizeHandle} ); }); -const SplitViewMenu = ({ view }: { view: View }) => { +const SplitViewMenu = ({ + view, + onMove, +}: { + view: View; + onMove: (from: number, to: number) => void; +}) => { const t = useI18n(); const workbench = useService(WorkbenchService).workbench; const views = useLiveData(workbench.views$); @@ -136,11 +144,11 @@ const SplitViewMenu = ({ view }: { view: View }) => { [view, workbench] ); const handleMoveLeft = useCallback(() => { - workbench.moveView(viewIndex, viewIndex - 1); - }, [viewIndex, workbench]); + onMove(viewIndex, viewIndex - 1); + }, [onMove, viewIndex]); const handleMoveRight = useCallback(() => { - workbench.moveView(viewIndex, viewIndex + 1); - }, [viewIndex, workbench]); + onMove(viewIndex, viewIndex + 1); + }, [onMove, viewIndex]); const handleCloseOthers = useCallback(() => { workbench.closeOthers(view); }, [view, workbench]); diff --git a/packages/frontend/core/src/modules/workbench/view/split-view/split-view.css.ts b/packages/frontend/core/src/modules/workbench/view/split-view/split-view.css.ts index 344117079dde9..edaf38debc408 100644 --- a/packages/frontend/core/src/modules/workbench/view/split-view/split-view.css.ts +++ b/packages/frontend/core/src/modules/workbench/view/split-view/split-view.css.ts @@ -22,9 +22,6 @@ export const splitViewRoot = style({ [borderRadius]: '6px', }, }, - '&[data-orientation="vertical"]': { - flexDirection: 'column', - }, }, }); @@ -35,12 +32,6 @@ export const splitViewPanel = style({ borderRadius: 'inherit', selectors: { - '[data-orientation="vertical"] &': { - height: 0, - }, - '[data-orientation="horizontal"] &': { - width: 0, - }, '[data-client-border="false"] &:not([data-is-last="true"]):not([data-is-dragging="true"])': { borderRight: `0.5px solid ${cssVar('borderColor')}`, @@ -131,8 +122,5 @@ export const resizeHandle = style({ boxShadow: `0px 12px 21px 4px ${cssVar('brandColor')}`, opacity: 0.15, }, - - // vertical - // TODO }, }); diff --git a/packages/frontend/core/src/modules/workbench/view/split-view/split-view.tsx b/packages/frontend/core/src/modules/workbench/view/split-view/split-view.tsx index 14fcdcc88b020..d997c1443ee16 100644 --- a/packages/frontend/core/src/modules/workbench/view/split-view/split-view.tsx +++ b/packages/frontend/core/src/modules/workbench/view/split-view/split-view.tsx @@ -1,21 +1,9 @@ import { useAppSettingHelper } from '@affine/core/components/hooks/affine/use-app-setting-helper'; import type { DragEndEvent } from '@dnd-kit/core'; -import { - closestCenter, - DndContext, - PointerSensor, - useSensor, - useSensors, -} from '@dnd-kit/core'; -import { - horizontalListSortingStrategy, - SortableContext, -} from '@dnd-kit/sortable'; import { useService } from '@toeverything/infra'; import clsx from 'clsx'; -import type { HTMLAttributes, RefObject } from 'react'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { createPortal } from 'react-dom'; +import type { HTMLAttributes } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import type { View } from '../../entities/view'; import { WorkbenchService } from '../../services/workbench'; @@ -34,8 +22,6 @@ export interface SplitViewProps extends HTMLAttributes { onMove?: (from: number, to: number) => void; } -type SlotsMap = Record>; -// TODO(@catsjuice): vertical orientation support export const SplitView = ({ orientation = 'horizontal', className, @@ -45,7 +31,6 @@ export const SplitView = ({ ...attrs }: SplitViewProps) => { const rootRef = useRef(null); - const [slots, setSlots] = useState({}); const [resizingViewId, setResizingViewId] = useState(null); const { appSettings } = useAppSettingHelper(); const workbench = useService(WorkbenchService).workbench; @@ -54,21 +39,6 @@ export const SplitView = ({ // Add a workaround here to force remounting after dropping. const [visible, setVisibility] = useState(true); - const sensors = useSensors( - useSensor( - PointerSensor, - useMemo( - /* avoid re-rendering */ - () => ({ - activationConstraint: { - distance: 0, - }, - }), - [] - ) - ) - ); - const onResizing = useCallback( (index: number, { x, y }: { x: number; y: number }) => { const rootEl = rootRef.current; @@ -85,6 +55,14 @@ export const SplitView = ({ [orientation, workbench] ); + const handleOnMove = useCallback( + (from: number, to: number) => { + onMove?.(from, to); + setVisibility(false); + }, + [onMove] + ); + const resizeHandleRenderer = useCallback( (view: View, index: number) => index < views.length - 1 ? ( @@ -106,7 +84,6 @@ export const SplitView = ({ const fromIndex = views.findIndex(v => v.id === active.id); const toIndex = views.findIndex(v => v.id === over?.id); onMove?.(fromIndex, toIndex); - setVisibility(false); } }, [onMove, views] @@ -133,31 +110,18 @@ export const SplitView = ({ data-client-border={appSettings.clientBorder} {...attrs} > - - - {views.map((view, index) => - visible ? ( - - {resizeHandleRenderer(view, index)} - - ) : null - )} - - - - {views.map((view, index) => { - const slot = slots[view.id]?.current; - if (!slot) return null; - return createPortal( - renderer(view, index), - slot, - `portalToSplitViewPanel_${view.id}` - ); - })} + {views.map((view, index) => + visible ? ( + + {renderer(view, index)} + + ) : null + )}
); }; diff --git a/packages/frontend/core/src/types/dnd.ts b/packages/frontend/core/src/types/dnd.ts index 51f8abee51634..375972505d97e 100644 --- a/packages/frontend/core/src/types/dnd.ts +++ b/packages/frontend/core/src/types/dnd.ts @@ -79,6 +79,10 @@ export interface AffineDNDData extends DNDData { at: 'doc-detail:header'; docId: string; } + | { + at: 'workbench:view'; + viewId: string; + } | { at: 'blocksuite-editor'; } @@ -114,5 +118,9 @@ export interface AffineDNDData extends DNDData { | { at: 'app-header:tabs'; } + | { + at: 'workbench:view'; + viewId: string; + } | Record; }