diff --git a/src/core/providers/canvas/canvas.model.ts b/src/core/providers/canvas/canvas.model.ts index 645736dc..e0e2e503 100644 --- a/src/core/providers/canvas/canvas.model.ts +++ b/src/core/providers/canvas/canvas.model.ts @@ -101,6 +101,7 @@ export interface CanvasContextModel { setActivePage: (pageId: string) => void; deletePage: (pageIndex: number) => void; editPageTitle: (pageIndex: number, newName: string) => void; + swapPages: (id1: string, id2: string) => void; activePageIndex: number; isThumbnailContextMenuVisible: boolean; setIsThumbnailContextMenuVisible: React.Dispatch< diff --git a/src/core/providers/canvas/canvas.provider.tsx b/src/core/providers/canvas/canvas.provider.tsx index fef9d809..dea96574 100644 --- a/src/core/providers/canvas/canvas.provider.tsx +++ b/src/core/providers/canvas/canvas.provider.tsx @@ -123,6 +123,20 @@ export const CanvasProvider: React.FC = props => { ); }; + const swapPages = (id1: string, id2: string) => { + setDocument(lastDocument => + produce(lastDocument, draft => { + const index1 = draft.pages.findIndex(page => page.id === id1); + const index2 = draft.pages.findIndex(page => page.id === id2); + if (index1 !== -1 && index2 !== -1) { + const temp = draft.pages[index1]; + draft.pages[index1] = draft.pages[index2]; + draft.pages[index2] = temp; + } + }) + ); + }; + const pasteShapes = (shapes: ShapeModel[]) => { const newShapes: ShapeModel[] = shapes.map(shape => { shape.id = uuidv4(); @@ -307,6 +321,7 @@ export const CanvasProvider: React.FC = props => { setActivePage, deletePage, editPageTitle, + swapPages, activePageIndex: document.activePageIndex, isThumbnailContextMenuVisible, setIsThumbnailContextMenuVisible, diff --git a/src/pods/canvas/use-monitor-shape.hook.ts b/src/pods/canvas/use-monitor-shape.hook.ts index d9229b88..1f0b6db2 100644 --- a/src/pods/canvas/use-monitor-shape.hook.ts +++ b/src/pods/canvas/use-monitor-shape.hook.ts @@ -24,38 +24,40 @@ export const useMonitorShape = ( if (!destination) return; invariant(destination); - const type = source.data.type as ShapeType; + if (source.data.type !== 'thumbPage') { + const type = source.data.type as ShapeType; - const screenPosition = - extractScreenCoordinatesFromPragmaticLocation(location); + const screenPosition = + extractScreenCoordinatesFromPragmaticLocation(location); - let positionX = 0; - let positionY = 0; - if (screenPosition) { - invariant(dropRef.current); - const { x: divRelativeX, y: divRelativeY } = - portScreenPositionToDivCoordinates( - dropRef.current as HTMLDivElement, - screenPosition - ); + let positionX = 0; + let positionY = 0; + if (screenPosition) { + invariant(dropRef.current); + const { x: divRelativeX, y: divRelativeY } = + portScreenPositionToDivCoordinates( + dropRef.current as HTMLDivElement, + screenPosition + ); - invariant(stageRef.current); - const stage = stageRef.current; - const { scrollLeft, scrollTop } = getScrollFromDiv( - dropRef as unknown as React.MutableRefObject - ); - const konvaCoord = convertFromDivElementCoordsToKonvaCoords(stage, { - screenPosition, - relativeDivPosition: { x: divRelativeX, y: divRelativeY }, - scroll: { x: scrollLeft, y: scrollTop }, - }); + invariant(stageRef.current); + const stage = stageRef.current; + const { scrollLeft, scrollTop } = getScrollFromDiv( + dropRef as unknown as React.MutableRefObject + ); + const konvaCoord = convertFromDivElementCoordsToKonvaCoords(stage, { + screenPosition, + relativeDivPosition: { x: divRelativeX, y: divRelativeY }, + scroll: { x: scrollLeft, y: scrollTop }, + }); - positionX = - konvaCoord.x - - calculateShapeOffsetToXDropCoordinate(konvaCoord.x, type); - positionY = konvaCoord.y; + positionX = + konvaCoord.x - + calculateShapeOffsetToXDropCoordinate(konvaCoord.x, type); + positionY = konvaCoord.y; + } + addNewShape(type, positionX, positionY); } - addNewShape(type, positionX, positionY); }, }); }, []); diff --git a/src/pods/canvas/use-multiple-selection-shape.hook.tsx b/src/pods/canvas/use-multiple-selection-shape.hook.tsx index 698c265c..006a415b 100644 --- a/src/pods/canvas/use-multiple-selection-shape.hook.tsx +++ b/src/pods/canvas/use-multiple-selection-shape.hook.tsx @@ -91,7 +91,6 @@ export const useMultipleSelectionShapeHook = ( e: KonvaEventObject | KonvaEventObject ) => { const transformerRect = transformerRef.current?.getClientRect(); - console.log(transformerRect); const mousePosition = e.target?.getStage()?.getPointerPosition() ?? { x: 0, y: 0, diff --git a/src/pods/thumb-pages/components/context-menu/context-menu.component.tsx b/src/pods/thumb-pages/components/context-menu/context-menu.component.tsx index 8b5a474e..62d8e73c 100644 --- a/src/pods/thumb-pages/components/context-menu/context-menu.component.tsx +++ b/src/pods/thumb-pages/components/context-menu/context-menu.component.tsx @@ -42,7 +42,6 @@ export const ThumbPageContextMenu: React.FunctionComponent< duplicatePage(pageIndex); break; case ContextButtonType.Rename: - console.log('Rename'); setPageTitleBeingEdited(pageIndex); break; case ContextButtonType.Delete: diff --git a/src/pods/thumb-pages/components/drag-drop-thumb.hook.ts b/src/pods/thumb-pages/components/drag-drop-thumb.hook.ts new file mode 100644 index 00000000..530b8149 --- /dev/null +++ b/src/pods/thumb-pages/components/drag-drop-thumb.hook.ts @@ -0,0 +1,58 @@ +import { useCanvasContext } from '@/core/providers'; +import { + draggable, + dropTargetForElements, +} from '@atlaskit/pragmatic-drag-and-drop/element/adapter'; +import { useEffect, useState } from 'react'; +import invariant from 'tiny-invariant'; + +export const useDragDropThumb = ( + divRef: React.RefObject, + pageIndex: number +) => { + const { fullDocument } = useCanvasContext(); + const page = fullDocument.pages[pageIndex]; + const [dragging, setDragging] = useState(false); + const [isDraggedOver, setIsDraggedOver] = useState(false); + + // Drag + useEffect(() => { + const el = divRef.current; + invariant(el); + return draggable({ + element: el, + getInitialData: () => ({ + pageId: page.id, //fullDocument.pages[pageIndex].id, + type: 'thumbPage', + }), + onDragStart: () => { + setDragging(true); + }, + onDrop: () => setDragging(false), + }); + }, [divRef.current, pageIndex, fullDocument.pages]); + + // Drop + useEffect(() => { + const el = divRef.current; + invariant(el); + + return dropTargetForElements({ + element: el, + getData: () => ({ + pageId: page.id, //fullDocument.pages[pageIndex].id, + type: 'thumbPage', + }), + onDragEnter: () => setIsDraggedOver(true), + onDragLeave: () => setIsDraggedOver(false), + onDrop: () => { + setIsDraggedOver(false); + }, + }); + }, [divRef.current, pageIndex, fullDocument.pages]); + + return { + dragging, + isDraggedOver, + }; +}; diff --git a/src/pods/thumb-pages/components/thumb-page.tsx b/src/pods/thumb-pages/components/thumb-page.tsx index 8ec6e209..507cc037 100644 --- a/src/pods/thumb-pages/components/thumb-page.tsx +++ b/src/pods/thumb-pages/components/thumb-page.tsx @@ -9,7 +9,9 @@ import { ThumbPageContextMenu } from './context-menu'; import { useContextMenu } from '../use-context-menu-thumb.hook'; import { CaretDown } from '@/common/components/icons'; import classes from './thumb-page.module.css'; + import React from 'react'; +import { useDragDropThumb } from './drag-drop-thumb.hook'; interface Props { pageIndex: number; @@ -19,9 +21,9 @@ interface Props { } export const ThumbPage: React.FunctionComponent = props => { + const { fullDocument, activePageIndex } = useCanvasContext(); const { pageIndex, onSetActivePage, setPageTitleBeingEdited, isVisible } = props; - const { fullDocument, activePageIndex } = useCanvasContext(); const page = fullDocument.pages[pageIndex]; const shapes = page.shapes; const fakeShapeRefs = useRef({}); @@ -35,6 +37,8 @@ export const ThumbPage: React.FunctionComponent = props => { const divRef = useRef(null); const [key, setKey] = React.useState(0); + const { dragging, isDraggedOver } = useDragDropThumb(divRef, pageIndex); + const handleResizeAndForceRedraw = () => { const newCanvaSize = { width: divRef.current?.clientWidth || 1, @@ -85,6 +89,10 @@ export const ThumbPage: React.FunctionComponent = props => { className={classes.container} onClick={() => onSetActivePage(page.id)} onContextMenu={handleShowContextMenu} + style={{ + opacity: dragging ? 0.4 : 1, + background: isDraggedOver ? 'lightblue' : 'white', + }} key={key} >
diff --git a/src/pods/thumb-pages/monitor-drop-thumb.hook.ts b/src/pods/thumb-pages/monitor-drop-thumb.hook.ts new file mode 100644 index 00000000..c2d8e151 --- /dev/null +++ b/src/pods/thumb-pages/monitor-drop-thumb.hook.ts @@ -0,0 +1,25 @@ +import { useEffect } from 'react'; +import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter'; +import { useCanvasContext } from '@/core/providers'; + +export const useMonitorDropThumb = () => { + const { fullDocument, swapPages } = useCanvasContext(); + + // Monitor + useEffect(() => { + return monitorForElements({ + onDrop({ source, location }) { + const destination = location.current.dropTargets[0]; + if (!destination || source.data.pageId === destination.data.pageId) { + return; + } + if (destination.data.type === 'thumbPage') { + swapPages( + String(source.data.pageId), + String(destination.data.pageId) + ); + } + }, + }); + }, [fullDocument.pages]); +}; diff --git a/src/pods/thumb-pages/thumb-pages.pod.tsx b/src/pods/thumb-pages/thumb-pages.pod.tsx index cf5a596f..30de47c4 100644 --- a/src/pods/thumb-pages/thumb-pages.pod.tsx +++ b/src/pods/thumb-pages/thumb-pages.pod.tsx @@ -3,6 +3,7 @@ import classes from './thumb-pages.module.css'; import { useCanvasContext } from '@/core/providers'; import { PageTitleInlineEdit, ThumbPage } from './components'; import { PlusIcon } from '@/common/components/icons'; +import { useMonitorDropThumb } from './monitor-drop-thumb.hook'; interface Props { isVisible: boolean; @@ -24,6 +25,8 @@ export const ThumbPagesPod: React.FC = props => { setActivePage(pageId); }; + useMonitorDropThumb(); + return (
{fullDocument.pages.map((page, index) => (