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) => {