From 155a8a0bcc0a1ab2387561f232467f4d239cfdc8 Mon Sep 17 00:00:00 2001 From: Komediruzecki Date: Thu, 12 Aug 2021 15:36:07 +0200 Subject: [PATCH] Add dnd inside folder view and across sidenav and folder view Add callbacks for dnd functions Update lower level components to use dnd functions Add initial dnd refactor Replace dragged resource with resource saving in transfer data Change APIs across codebase (dragStart, dragEnd, dropOn) Add text/plain data transfer to folder and doc dragg save events --- .../Rows/ContentManagerDocRow.tsx | 9 ++ .../Rows/ContentManagerFolderRow.tsx | 10 ++ .../ContentManager/Rows/ContentManagerRow.tsx | 59 +++++++++- .../molecules/ContentManager/index.tsx | 68 ++++++++++- src/cloud/interfaces/resources.ts | 30 ++++- .../{useCloudSidebarDnd.ts => useCloudDnd.ts} | 107 +++++++++++++----- .../lib/hooks/sidebar/useCloudSidebarTree.tsx | 100 +++++++++------- src/cloud/lib/hooks/useCloudApi.ts | 14 ++- src/cloud/lib/utils/patterns.ts | 80 ++++++++++++- .../Sidebar/molecules/SidebarTree.tsx | 30 ++--- 10 files changed, 404 insertions(+), 103 deletions(-) rename src/cloud/lib/hooks/sidebar/{useCloudSidebarDnd.ts => useCloudDnd.ts} (52%) diff --git a/src/cloud/components/molecules/ContentManager/Rows/ContentManagerDocRow.tsx b/src/cloud/components/molecules/ContentManager/Rows/ContentManagerDocRow.tsx index 8685559f6a..c7287bfa96 100644 --- a/src/cloud/components/molecules/ContentManager/Rows/ContentManagerDocRow.tsx +++ b/src/cloud/components/molecules/ContentManager/Rows/ContentManagerDocRow.tsx @@ -33,6 +33,9 @@ interface ContentManagerDocRowProps { currentUserIsCoreMember: boolean onSelect: (val: boolean) => void setUpdating: React.Dispatch> + onDragStart: (event: any, doc: SerializedDocWithBookmark) => void + onDragEnd: (event: any) => void + onDrop: (event: any, doc: SerializedDocWithBookmark) => void } const ContentManagerDocRow = ({ @@ -43,6 +46,9 @@ const ContentManagerDocRow = ({ showPath, currentUserIsCoreMember, onSelect, + onDragStart, + onDragEnd, + onDrop, }: ContentManagerDocRowProps) => { const { permissions = [] } = usePage() const { push } = useRouter() @@ -165,6 +171,9 @@ const ContentManagerDocRow = ({ labelOnclick={() => push(href)} defaultIcon={mdiFileDocumentOutline} emoji={doc.emoji} + onDragStart={(event: any) => onDragStart(event, doc)} + onDragEnd={(event: any) => onDragEnd(event)} + onDrop={(event: any) => onDrop(event, doc)} > void currentUserIsCoreMember: boolean + onDragStart: (event: any, folder: SerializedFolderWithBookmark) => void + onDragEnd: (event: any) => void + onDrop: (event: any, folder: SerializedFolderWithBookmark) => void } const ContentmanagerFolderRow = ({ @@ -24,6 +28,9 @@ const ContentmanagerFolderRow = ({ checked, currentUserIsCoreMember, onSelect, + onDragStart, + onDragEnd, + onDrop, }: ContentManagerFolderRowProps) => { const { t } = useTranslation() const { docsMap, foldersMap } = useNav() @@ -51,6 +58,9 @@ const ContentmanagerFolderRow = ({ emoji={folder.emoji} labelHref={href} labelOnclick={() => push(href)} + onDragStart={(event: any) => onDragStart(event, folder)} + onDragEnd={(event: any) => onDragEnd(event)} + onDrop={(event: any) => onDrop(event, folder)} > {childrenFolders} {t(lngKeys.GeneralFolders).toLocaleLowerCase()}{' '} diff --git a/src/cloud/components/molecules/ContentManager/Rows/ContentManagerRow.tsx b/src/cloud/components/molecules/ContentManager/Rows/ContentManagerRow.tsx index fa93a3ed0f..a3391d2ce1 100644 --- a/src/cloud/components/molecules/ContentManager/Rows/ContentManagerRow.tsx +++ b/src/cloud/components/molecules/ContentManager/Rows/ContentManagerRow.tsx @@ -1,9 +1,10 @@ -import React, { useCallback } from 'react' +import React, { useCallback, useRef, useState } from 'react' import cc from 'classcat' import Checkbox from '../../../atoms/Checkbox' import styled from '../../../../../shared/lib/styled' import { AppComponent } from '../../../../../shared/lib/types' import EmojiIcon from '../../../atoms/EmojiIcon' +import { onDragLeaveCb } from '../../../../../shared/lib/dnd' interface ContentManagerRowProps { type?: 'header' | 'row' @@ -15,6 +16,9 @@ interface ContentManagerRowProps { emoji?: string defaultIcon?: string showCheckbox: boolean + onDragStart?: (event: any) => void + onDragEnd?: (event: any) => void + onDrop?: (event: any) => void } const ContentManagerRow: AppComponent = ({ @@ -29,6 +33,9 @@ const ContentManagerRow: AppComponent = ({ defaultIcon, showCheckbox, onSelect, + onDragStart, + onDragEnd, + onDrop, }) => { const LabelTag = labelHref != null || labelOnclick != null ? 'a' : 'div' @@ -45,9 +52,46 @@ const ContentManagerRow: AppComponent = ({ [labelOnclick] ) + const [draggedOver, setDraggedOver] = useState(false) + const dragRef = useRef(null) + return ( { + event.stopPropagation() + if (onDrop != null) { + onDrop(event) + } + setDraggedOver(false) + }} + onDragStart={(event: any) => { + event.stopPropagation() + if (onDragStart != null) { + onDragStart(event) + } + }} + onDragOver={(event: any) => { + event.preventDefault() + event.stopPropagation() + setDraggedOver(true) + }} + onDragLeave={(event: any) => { + onDragLeaveCb(event, dragRef, () => { + setDraggedOver(false) + }) + }} + onDragEnd={(event: any) => { + if (onDragEnd != null) { + onDragEnd(event) + } + }} > {showCheckbox && ( = ({ onChange={onSelect} /> )} - +
theme.colors.background.quaternary}; + } ` diff --git a/src/cloud/components/molecules/ContentManager/index.tsx b/src/cloud/components/molecules/ContentManager/index.tsx index 7bdbcfe799..b75e861f6b 100644 --- a/src/cloud/components/molecules/ContentManager/index.tsx +++ b/src/cloud/components/molecules/ContentManager/index.tsx @@ -1,7 +1,7 @@ -import React, { useState, useMemo, useCallback, useEffect, useRef } from 'react' +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { - SerializedDocWithBookmark, DocStatus, + SerializedDocWithBookmark, } from '../../../interfaces/db/doc' import { SerializedFolderWithBookmark } from '../../../interfaces/db/folder' import { useSet } from 'react-use' @@ -9,7 +9,13 @@ import { sortByAttributeAsc, sortByAttributeDesc, } from '../../../lib/utils/array' -import { getDocTitle, getDocId, getFolderId } from '../../../lib/utils/patterns' +import { + docToDataTransferItem, + folderToDataTransferItem, + getDocId, + getDocTitle, + getFolderId, +} from '../../../lib/utils/patterns' import { SerializedWorkspace } from '../../../interfaces/db/workspace' import { StyledContentManagerList } from './styled' import Checkbox from '../../atoms/Checkbox' @@ -32,6 +38,8 @@ import ContentManagerCell from './ContentManagerCell' import Flexbox from '../../../../shared/components/atoms/Flexbox' import ContentManagerStatusFilter from './ContentManagerStatusFilter' import VerticalScroller from '../../../../shared/components/atoms/VerticalScroller' +import { useCloudDnd } from '../../../lib/hooks/sidebar/useCloudDnd' +import { DraggedTo } from '../../../../shared/lib/dnd' export type ContentManagerParent = | { type: 'folder'; item: SerializedFolderWithBookmark } @@ -209,6 +217,54 @@ const ContentManager = ({ [setPreferences] ) + const { + dropInDocOrFolder, + saveFolderTransferData, + saveDocTransferData, + clearDragTransferData, + } = useCloudDnd() + + const onDragStartFolder = useCallback( + (event: any, folder: SerializedFolderWithBookmark) => { + saveFolderTransferData(event, folder) + }, + [saveFolderTransferData] + ) + + const onDropFolder = useCallback( + (event, folder: SerializedFolderWithBookmark) => + dropInDocOrFolder( + event, + { type: 'folder', resource: folderToDataTransferItem(folder) }, + DraggedTo.insideFolder + ), + [dropInDocOrFolder] + ) + + const onDragStartDoc = useCallback( + (event: any, doc: SerializedDocWithBookmark) => { + saveDocTransferData(event, doc) + }, + [saveDocTransferData] + ) + + const onDropDoc = useCallback( + (event: any, doc: SerializedDocWithBookmark) => + dropInDocOrFolder( + event, + { type: 'doc', resource: docToDataTransferItem(doc) }, + DraggedTo.beforeItem + ), + [dropInDocOrFolder] + ) + + const onDragEnd = useCallback( + (event: any) => { + clearDragTransferData(event) + }, + [clearDragTransferData] + ) + return ( @@ -283,6 +339,9 @@ const ContentManager = ({ checked={hasFolder(folder.id)} onSelect={() => toggleFolder(folder.id)} currentUserIsCoreMember={currentUserIsCoreMember} + onDrop={onDropFolder} + onDragEnd={onDragEnd} + onDragStart={onDragStartFolder} /> ))} @@ -331,6 +390,9 @@ const ContentManager = ({ onSelect={() => toggleDoc(doc.id)} showPath={page != null} currentUserIsCoreMember={currentUserIsCoreMember} + onDrop={onDropDoc} + onDragEnd={onDragEnd} + onDragStart={onDragStartDoc} /> ))} {orderedDocs.length === 0 && } diff --git a/src/cloud/interfaces/resources.ts b/src/cloud/interfaces/resources.ts index 26ae396373..87dc887e0a 100644 --- a/src/cloud/interfaces/resources.ts +++ b/src/cloud/interfaces/resources.ts @@ -1,14 +1,36 @@ -import { SerializedFolderWithBookmark } from './db/folder' -import { SerializedDocWithBookmark } from './db/doc' +export const FOLDER_DRAG_TRANSFER_DATA_JSON = + 'application/boostnote.folder+json' +export const DOC_DRAG_TRANSFER_DATA_JSON = 'application/boostnote.doc+json' +export const CATEGORY_DRAG_TRANSFER_DATA_JSON = + 'application/boostnote.category+json' + +export interface DocDataTransferItem { + workspaceId: string + teamId: string + id: string + emoji?: string + url: string + title: string +} + +export interface FolderDataTransferItem { + workspaceId: string + teamId: string + id: string + emoji?: string + url: string + name: string + description?: string +} export type SerializedFolderNav = { type: 'folder' - result: SerializedFolderWithBookmark + resource: FolderDataTransferItem } export type SerializedDocNav = { type: 'doc' - result: SerializedDocWithBookmark + resource: DocDataTransferItem } export type SerializedPendingNav = { diff --git a/src/cloud/lib/hooks/sidebar/useCloudSidebarDnd.ts b/src/cloud/lib/hooks/sidebar/useCloudDnd.ts similarity index 52% rename from src/cloud/lib/hooks/sidebar/useCloudSidebarDnd.ts rename to src/cloud/lib/hooks/sidebar/useCloudDnd.ts index a502a2f777..2c6068ba0c 100644 --- a/src/cloud/lib/hooks/sidebar/useCloudSidebarDnd.ts +++ b/src/cloud/lib/hooks/sidebar/useCloudDnd.ts @@ -1,20 +1,30 @@ -import { useCallback, useRef } from 'react' +import { useCallback } from 'react' import { UpdateDocRequestBody } from '../../../api/teams/docs' import { UpdateFolderRequestBody } from '../../../api/teams/folders' import { moveResource } from '../../../api/teams/resources' -import { SerializedDoc } from '../../../interfaces/db/doc' -import { SerializedFolder } from '../../../interfaces/db/folder' -import { NavResource } from '../../../interfaces/resources' +import { + CATEGORY_DRAG_TRANSFER_DATA_JSON, + DOC_DRAG_TRANSFER_DATA_JSON, + DocDataTransferItem, + FOLDER_DRAG_TRANSFER_DATA_JSON, + FolderDataTransferItem, + NavResource, +} from '../../../interfaces/resources' import { useNav } from '../../stores/nav' import { usePage } from '../../stores/pageStore' -import { getResourceId } from '../../utils/patterns' +import { + docToDataTransferItem, + folderToDataTransferItem, + getDraggedResource, + getResourceId, +} from '../../utils/patterns' import { SidebarDragState } from '../../../../shared/lib/dnd' import { useToast } from '../../../../shared/lib/stores/toast' import { getMapFromEntityArray } from '../../../../shared/lib/utils/array' +import { SerializedFolderWithBookmark } from '../../../interfaces/db/folder' +import { SerializedDocWithBookmark } from '../../../interfaces/db/doc' -export function useCloudSidebarDnd() { - const draggedCategory = useRef() - const draggedResource = useRef() +export function useCloudDnd() { const { updateFoldersMap, updateDocsMap, @@ -26,21 +36,23 @@ export function useCloudSidebarDnd() { const dropInWorkspace = useCallback( async ( + event: any, workspaceId: string, updateFolder: ( - folder: SerializedFolder, + folder: FolderDataTransferItem, body: UpdateFolderRequestBody ) => Promise, updateDoc: ( - doc: SerializedDoc, + doc: DocDataTransferItem, body: UpdateDocRequestBody ) => Promise ) => { - if (draggedResource.current == null) { + const draggedResource = getDraggedResource(event) + if (draggedResource === null) { return } - if (draggedResource.current.result.workspaceId === workspaceId) { + if (draggedResource.resource.workspaceId === workspaceId) { pushMessage({ title: 'Oops', description: 'Resource is already present in this space', @@ -48,24 +60,20 @@ export function useCloudSidebarDnd() { return } - if (draggedResource.current.type === 'folder') { - const folder = draggedResource.current.result - updateFolder(folder, { + if (draggedResource.type === 'folder') { + const folder = draggedResource.resource + await updateFolder(folder, { workspaceId: workspaceId, description: folder.description, folderName: folder.name, emoji: folder.emoji, - }).then(() => { - draggedResource.current = undefined }) - } else if (draggedResource.current.type === 'doc') { - const doc = draggedResource.current.result - updateDoc(doc, { + } else if (draggedResource.type === 'doc') { + const doc = draggedResource.resource + await updateDoc(doc, { workspaceId: workspaceId, title: doc.title, emoji: doc.emoji, - }).then(() => { - draggedResource.current = undefined }) } }, @@ -74,25 +82,27 @@ export function useCloudSidebarDnd() { const dropInDocOrFolder = useCallback( async ( + event: any, targetedResource: NavResource, targetedPosition: SidebarDragState ) => { - if (draggedResource.current == null || targetedPosition == null) { + const draggedResource = getDraggedResource(event) + if (draggedResource === null || targetedPosition == null) { return } if ( - draggedResource.current.type === targetedResource.type && - draggedResource.current.result.id === targetedResource.result.id + draggedResource.type === targetedResource.type && + draggedResource.resource.id === targetedResource.resource.id ) { return } try { - const originalResourceId = getResourceId(draggedResource.current) + const originalResourceId = getResourceId(draggedResource) const pos = targetedPosition const { folders, docs, workspaces } = await moveResource( - { id: draggedResource.current.result.teamId }, + { id: draggedResource.resource.teamId }, originalResourceId, { targetedPosition: pos, @@ -117,8 +127,6 @@ export function useCloudSidebarDnd() { if (pageDoc != null && changedDocs.get(pageDoc.id) != null) { setCurrentPath(changedDocs.get(pageDoc.id)!.folderPathname) } - - draggedResource.current = undefined } catch (error) { pushApiErrorMessage(error) } @@ -134,10 +142,47 @@ export function useCloudSidebarDnd() { ] ) + const saveFolderTransferData = useCallback( + (event: any, folder: SerializedFolderWithBookmark) => { + const folderDataTransferItem = folderToDataTransferItem(folder) + event.dataTransfer.setData( + FOLDER_DRAG_TRANSFER_DATA_JSON, + JSON.stringify(folderDataTransferItem) + ) + event.dataTransfer.setData( + 'text/plain', + `${folderDataTransferItem.name} ${folderDataTransferItem.url}` + ) + }, + [] + ) + + const saveDocTransferData = useCallback( + (event: any, doc: SerializedDocWithBookmark) => { + const docDataTransferItem = docToDataTransferItem(doc) + event.dataTransfer.setData( + DOC_DRAG_TRANSFER_DATA_JSON, + JSON.stringify(docDataTransferItem) + ) + event.dataTransfer.setData( + 'text/plain', + `${docDataTransferItem.title} ${docDataTransferItem.url}` + ) + }, + [] + ) + + const clearDragTransferData = useCallback((event: any) => { + event.dataTransfer.setData(DOC_DRAG_TRANSFER_DATA_JSON, '') + event.dataTransfer.setData(FOLDER_DRAG_TRANSFER_DATA_JSON, '') + event.dataTransfer.setData(CATEGORY_DRAG_TRANSFER_DATA_JSON, '') + }, []) + return { - draggedCategory, - draggedResource, dropInWorkspace, dropInDocOrFolder, + saveFolderTransferData, + saveDocTransferData, + clearDragTransferData, } } diff --git a/src/cloud/lib/hooks/sidebar/useCloudSidebarTree.tsx b/src/cloud/lib/hooks/sidebar/useCloudSidebarTree.tsx index cbce1ae27e..24ee62b291 100644 --- a/src/cloud/lib/hooks/sidebar/useCloudSidebarTree.tsx +++ b/src/cloud/lib/hooks/sidebar/useCloudSidebarTree.tsx @@ -47,10 +47,16 @@ import { CollapsableType, useSidebarCollapse, } from '../../stores/sidebarCollapse' -import { getDocId, getDocTitle, getFolderId } from '../../utils/patterns' +import { + docToDataTransferItem, + folderToDataTransferItem, + getDocId, + getDocTitle, + getFolderId, +} from '../../utils/patterns' import { useCloudApi } from '../useCloudApi' import { useCloudResourceModals } from '../useCloudResourceModals' -import { useCloudSidebarDnd } from './useCloudSidebarDnd' +import { useCloudDnd } from './useCloudDnd' import { getDocStatusHref, getSmartFolderHref } from '../../href' import CreateSmartFolderModal from '../../../components/organisms/Modal/contents/SmartFolder/CreateSmartFolderModal' import UpdateSmartFolderModal from '../../../components/organisms/Modal/contents/SmartFolder/UpdateSmartFolderModal' @@ -65,6 +71,7 @@ import { SidebarNavControls, SidebarTreeChildRow, } from '../../../../shared/components/organisms/Sidebar/molecules/SidebarTree' +import { CATEGORY_DRAG_TRANSFER_DATA_JSON } from '../../../interfaces/resources' export function useCloudSidebarTree() { const { team, currentUserIsCoreMember } = usePage() @@ -94,11 +101,12 @@ export function useCloudSidebarTree() { } = useSidebarCollapse() const { - draggedCategory, - draggedResource, dropInDocOrFolder, dropInWorkspace, - } = useCloudSidebarDnd() + saveFolderTransferData, + saveDocTransferData, + clearDragTransferData, + } = useCloudDnd() const { sendingMap: treeSendingMap, @@ -235,7 +243,8 @@ export function useCloudSidebarTree() { const coreRestrictedFeatures: Partial = currentUserIsCoreMember ? { dropIn: true, - onDrop: () => dropInWorkspace(wp.id, updateFolder, updateDoc), + onDrop: (event: any) => + dropInWorkspace(event, wp.id, updateFolder, updateDoc), controls: [ { icon: mdiTextBoxPlus, @@ -310,13 +319,17 @@ export function useCloudSidebarTree() { const coreRestrictedFeatures: Partial = currentUserIsCoreMember ? { - onDrop: (position: SidebarDragState) => - dropInDocOrFolder({ type: 'folder', result: folder }, position), - onDragStart: () => { - draggedResource.current = { type: 'folder', result: folder } + onDrop: (event: any, position: SidebarDragState) => + dropInDocOrFolder( + event, + { type: 'folder', resource: folderToDataTransferItem(folder) }, + position + ), + onDragStart: (event: any) => { + saveFolderTransferData(event, folder) }, - onDragEnd: () => { - draggedResource.current = undefined + onDragEnd: (event: any) => { + clearDragTransferData(event) }, dropIn: true, dropAround: sortingOrder === 'drag' ? true : false, @@ -426,13 +439,17 @@ export function useCloudSidebarTree() { ? { dropAround: sortingOrder === 'drag' ? true : false, navigateTo: () => push(href), - onDrop: (position: SidebarDragState) => - dropInDocOrFolder({ type: 'doc', result: doc }, position), - onDragStart: () => { - draggedResource.current = { type: 'doc', result: doc } + onDrop: (event: any, position: SidebarDragState) => + dropInDocOrFolder( + event, + { type: 'doc', resource: docToDataTransferItem(doc) }, + position + ), + onDragStart: (event: any) => { + saveDocTransferData(event, doc) }, - onDragEnd: () => { - draggedResource.current = undefined + onDragEnd: (event: any) => { + clearDragTransferData(event) }, contextControls: [ { @@ -837,8 +854,6 @@ export function useCloudSidebarTree() { return tree as SidebarNavCategory[] }, [ - showSearchScreen, - translate, initialLoadDone, team, pathname, @@ -848,9 +863,12 @@ export function useCloudSidebarTree() { workspacesMap, smartFoldersMap, tagsMap, + translate, + currentUserIsCoreMember, + openWorkspaceCreateForm, + showSearchScreen, sideBarOpenedWorkspaceIdsSet, getFoldEvents, - push, dropInWorkspace, updateFolder, updateDoc, @@ -858,13 +876,15 @@ export function useCloudSidebarTree() { createFolder, openWorkspaceEditForm, deleteWorkspace, - sideBarOpenedFolderIdsSet, + push, treeSendingMap, + sideBarOpenedFolderIdsSet, dropInDocOrFolder, - draggedResource, + saveFolderTransferData, toggleFolderBookmark, openRenameFolderForm, deleteFolder, + saveDocTransferData, toggleDocBookmark, openRenameDocForm, deleteDoc, @@ -874,8 +894,6 @@ export function useCloudSidebarTree() { createWorkspace, sideBarOpenedLinksIdsSet, toggleItem, - currentUserIsCoreMember, - openWorkspaceCreateForm, ]) const treeWithOrderedCategories = useMemo(() => { @@ -907,16 +925,23 @@ export function useCloudSidebarTree() { orderedTree.forEach((category) => { category.drag = { - onDragStart: () => { - draggedCategory.current = category.label + onDragStart: (event: any) => { + event.dataTransfer.setData( + category.label, + CATEGORY_DRAG_TRANSFER_DATA_JSON + ) }, - onDragEnd: () => { - draggedCategory.current = undefined + onDragEnd: (event: any) => { + clearDragTransferData(event) }, - onDrop: () => { - if (draggedCategory.current == null) { + onDrop: (event: any) => { + const draggedCategory = event.dataTransfer.getData( + CATEGORY_DRAG_TRANSFER_DATA_JSON + ) + if (draggedCategory.length === 0) { return } + const orderedItems = orderedCategories.splice(0) const categoryIndex = orderedItems.includes(category.label) ? orderedItems.indexOf(category.label) @@ -947,12 +972,7 @@ export function useCloudSidebarTree() { }) return orderedTree - }, [ - tree, - preferences.sidebarOrderedCategories, - setPreferences, - draggedCategory, - ]) + }, [tree, preferences.sidebarOrderedCategories, setPreferences]) return { tree, @@ -1024,7 +1044,7 @@ type CloudTreeItem = { contextControls?: MenuItem[] dropIn?: boolean dropAround?: boolean - onDragStart?: () => void - onDrop?: (position?: SidebarDragState) => void - onDragEnd?: () => void + onDragStart?: (event: any) => void + onDrop?: (event: any, position?: SidebarDragState) => void + onDragEnd?: (event: any) => void } diff --git a/src/cloud/lib/hooks/useCloudApi.ts b/src/cloud/lib/hooks/useCloudApi.ts index c38de2893e..35dc6a1868 100644 --- a/src/cloud/lib/hooks/useCloudApi.ts +++ b/src/cloud/lib/hooks/useCloudApi.ts @@ -68,6 +68,10 @@ import { SerializedWorkspace } from '../../interfaces/db/workspace' import { deleteSmartFolder } from '../../api/teams/smart-folder' import { format as formatDate } from 'date-fns' +import { + DocDataTransferItem, + FolderDataTransferItem, +} from '../../interfaces/resources' export function useCloudApi() { const { pageDoc, pageFolder, setPartialPageData } = usePage() @@ -271,7 +275,10 @@ export function useCloudApi() { ) const updateFolderApi = useCallback( - async (target: SerializedFolder, body: UpdateFolderRequestBody) => { + async ( + target: SerializedFolder | FolderDataTransferItem, + body: UpdateFolderRequestBody + ) => { await send(target.id, 'update', { api: () => updateFolder({ id: target.teamId }, target.id, body), cb: ({ folders, docs, workspaces }: UpdateFolderResponseBody) => { @@ -312,7 +319,10 @@ export function useCloudApi() { ) const updateDocApi = useCallback( - async (target: SerializedDoc, body: UpdateDocRequestBody) => { + async ( + target: SerializedDoc | DocDataTransferItem, + body: UpdateDocRequestBody + ) => { await send(target.id, 'update', { api: () => updateDoc(target.teamId, target.id, body), cb: ({ folders, doc, workspaces }: UpdateDocResponseBody) => { diff --git a/src/cloud/lib/utils/patterns.ts b/src/cloud/lib/utils/patterns.ts index 0c26017944..897bb1c4fa 100644 --- a/src/cloud/lib/utils/patterns.ts +++ b/src/cloud/lib/utils/patterns.ts @@ -1,10 +1,22 @@ import { join } from 'path' import { SerializedTeam } from '../../interfaces/db/team' -import { SerializedFolder } from '../../interfaces/db/folder' +import { + SerializedFolder, + SerializedFolderWithBookmark, +} from '../../interfaces/db/folder' import { getHexFromUUID, getUUIDFromHex } from './string' import slugify from 'slugify' -import { SerializedDoc } from '../../interfaces/db/doc' -import { NavResource } from '../../interfaces/resources' +import { + SerializedDoc, + SerializedDocWithBookmark, +} from '../../interfaces/db/doc' +import { + DOC_DRAG_TRANSFER_DATA_JSON, + DocDataTransferItem, + FOLDER_DRAG_TRANSFER_DATA_JSON, + FolderDataTransferItem, + NavResource, +} from '../../interfaces/resources' import { isArray } from 'util' import { SerializedWorkspace } from '../../interfaces/db/workspace' import { SerializedOpenInvite } from '../../interfaces/db/openInvite' @@ -78,11 +90,69 @@ export function getDocIdFromString(id: string) { return [prefixDocs, getHexFromUUID(id)].join('') } +export function getDraggedResource(event: any): NavResource | null { + const docData = event.dataTransfer.getData(DOC_DRAG_TRANSFER_DATA_JSON) + if (docData.length === 0) { + const folderData = event.dataTransfer.getData( + FOLDER_DRAG_TRANSFER_DATA_JSON + ) + if (folderData.length === 0) { + return null + } + try { + return { + type: 'folder', + resource: JSON.parse(folderData) as FolderDataTransferItem, + } + } catch (err) { + console.warn('Invalid drag data encountered', err) + return null + } + } else { + try { + return { + type: 'doc', + resource: JSON.parse(docData) as DocDataTransferItem, + } + } catch (err) { + console.warn('Invalid drag data encountered', err) + return null + } + } +} + export function getResourceId(source: NavResource) { if (source.type === 'doc') { - return getDocId(source.result) + return getDocIdFromString(source.resource.id) } else { - return getFolderId(source.result) + return getFolderIdFromString(source.resource.id) + } +} + +export function folderToDataTransferItem( + folder: SerializedFolderWithBookmark +): FolderDataTransferItem { + return { + workspaceId: folder.workspaceId, + teamId: folder.teamId, + id: folder.id, + emoji: folder.emoji, + name: folder.name, + description: folder.description, + url: getFolderURL(folder), + } +} + +export function docToDataTransferItem( + doc: SerializedDocWithBookmark +): DocDataTransferItem { + return { + workspaceId: doc.workspaceId, + teamId: doc.teamId, + id: doc.id, + emoji: doc.emoji, + title: doc.title, + url: getDocURL(doc), } } diff --git a/src/shared/components/organisms/Sidebar/molecules/SidebarTree.tsx b/src/shared/components/organisms/Sidebar/molecules/SidebarTree.tsx index 37b4d56901..d4627d47cd 100644 --- a/src/shared/components/organisms/Sidebar/molecules/SidebarTree.tsx +++ b/src/shared/components/organisms/Sidebar/molecules/SidebarTree.tsx @@ -3,7 +3,7 @@ import styled from '../../../../lib/styled' import cc from 'classcat' import { FoldingProps } from '../../../atoms/FoldingWrapper' import { ControlButtonProps } from '../../../../lib/types' -import { MenuItem } from '../../../../lib/stores/contextMenu/types' +import { MenuItem } from '../../../../lib/stores/contextMenu' import SidebarTreeForm from '../atoms/SidebarTreeForm' import { DraggedTo, onDragLeaveCb, SidebarDragState } from '../../../../lib/dnd' import SidebarItem from '../atoms/SidebarTreeItem' @@ -34,9 +34,9 @@ export interface SidebarNavCategory { footer?: React.ReactNode lastCategory?: boolean drag?: { - onDragStart: () => void - onDragEnd: () => void - onDrop: () => void + onDragStart: (event: any) => void + onDragEnd: (event: any) => void + onDrop: (event: any) => void } } @@ -59,9 +59,9 @@ interface SidebarNavRow { contextControls?: MenuItem[] dropIn?: boolean dropAround?: boolean - onDragStart?: () => void - onDrop?: (position?: SidebarDragState) => void - onDragEnd?: () => void + onDragStart?: (event: any) => void + onDrop?: (event: any, position?: SidebarDragState) => void + onDragEnd?: (event: any) => void } export type SidebarNavControls = @@ -233,15 +233,15 @@ const SidebarCategory = ({ onDragStart={(event) => { event.stopPropagation() setDraggingCategory(true) - category.drag!.onDragStart() + category.drag!.onDragStart(event) }} onDragLeave={(event) => { onDragLeaveCb(event, dragRef, () => { setDraggedOver(false) }) }} - onDragEnd={() => { - category.drag!.onDragEnd() + onDragEnd={(event) => { + category.drag!.onDragEnd(event) }} onDragOver={(event) => { event.preventDefault() @@ -250,7 +250,7 @@ const SidebarCategory = ({ }} onDrop={(event) => { event.stopPropagation() - category.drag!.onDrop() + category.drag!.onDrop(event) setDraggedOver(false) }} > @@ -312,7 +312,7 @@ const SidebarNestedTreeRow = ({ (event: any) => { event.stopPropagation() if (row.onDragStart != null) { - row.onDragStart() + row.onDragStart(event) } setDraggingItem(true) }, @@ -323,7 +323,7 @@ const SidebarNestedTreeRow = ({ (event: React.DragEvent, position: SidebarDragState) => { event.preventDefault() if (row.onDrop != null) { - row.onDrop(position) + row.onDrop(event, position) } setDraggedOver(undefined) setDraggingItem(false) @@ -379,9 +379,9 @@ const SidebarNestedTreeRow = ({ setDraggedOver(undefined) }) }} - onDragEnd={() => { + onDragEnd={(event) => { if (row.onDragEnd != null) { - row.onDragEnd() + row.onDragEnd(event) } }} onDragOver={(event) => {