Skip to content

Commit

Permalink
WIP: dragndrop nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
ciur committed Sep 20, 2024
1 parent f63fcb3 commit 919fbce
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 25 deletions.
4 changes: 2 additions & 2 deletions ui2/src/features/document/components/Thumbnail/Thumbnail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
selectSelectedPages
} from "@/features/document/documentVersSlice"
import {
dragPagesEnded,
dragEnded,
dragPagesStarted,
selectCurrentDocVerID,
selectCurrentNodeID,
Expand Down Expand Up @@ -174,7 +174,7 @@ export default function Thumbnail({page}: Args) {
position: position
})
)
dispatch(dragPagesEnded())
dispatch(dragEnded())
} else {
// here we deal with pages transfer between documents
trPagesDialogOpen()
Expand Down
24 changes: 23 additions & 1 deletion ui2/src/features/nodes/components/Commander.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {Box, Group, Stack} from "@mantine/core"
import {useDisclosure} from "@mantine/hooks"
import {useContext, useState} from "react"
import {createRoot} from "react-dom/client"

import {useAppDispatch, useAppSelector} from "@/app/hooks"
import {useNavigate} from "react-router-dom"
Expand Down Expand Up @@ -30,6 +31,7 @@ import {
import type {NType, NodeType, PanelMode} from "@/types"
import classes from "./Commander.module.scss"

import DraggingIcon from "./DraggingIcon"
import {DropFilesModal} from "./DropFiles"
import ExtractPagesModal from "./ExtractPagesModal"
import FolderNodeActions from "./FolderNodeActions"
Expand Down Expand Up @@ -172,8 +174,28 @@ export default function Commander() {
refetch()
}

const onNodeDrag = (nodeID: string, event: React.DragEvent) => {}

const onNodeDragStart = (nodeID: string, event: React.DragEvent) => {
const image = <DraggingIcon nodeID={nodeID} />
let ghost = document.createElement("div")
ghost.style.transform = "translate(-10000px, -10000px)"
ghost.style.position = "absolute"
document.body.appendChild(ghost)
event.dataTransfer.setDragImage(ghost, 0, -10)

let root = createRoot(ghost)
root.render(image)
}

const nodes = data.items.map((n: NodeType) => (
<Node onClick={onClick} key={n.id} node={n} />
<Node
onClick={onClick}
key={n.id}
node={n}
onDrag={onNodeDrag}
onDragStart={onNodeDragStart}
/>
))

let commanderContent: JSX.Element
Expand Down
35 changes: 35 additions & 0 deletions ui2/src/features/nodes/components/DraggingIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {store} from "@/app/store"
import theme from "@/themes"
import {CType} from "@/types"
import {MantineProvider, Stack, Text} from "@mantine/core"
import {IconFile, IconFolder} from "@tabler/icons-react"

interface Args {
nodeID: string
}

export default function DraggingIcon({nodeID}: Args) {
const state = store.getState()
const draggedNodeIDs = state.ui.dragndrop?.nodeIDs
const draggedNodes = Object.values(state.nodes.entities).filter(
i => draggedNodeIDs?.includes(i.id) || i.id == nodeID
)
const count = dedup(draggedNodeIDs || []).length
// unique list of used ctypes
const usedCTypes: Array<CType> = dedup(draggedNodes.map(n => n.ctype))
const folderOnly = usedCTypes.includes("folder") && usedCTypes.length == 1
const icon: JSX.Element = folderOnly ? <IconFolder /> : <IconFile />

return (
<MantineProvider theme={theme}>
<Stack>
{icon}
<Text>Move {count} items</Text>
</Stack>
</MantineProvider>
)
}

function dedup<T>(arr: Array<T>): Array<T> {
return [...new Set(arr)]
}
30 changes: 25 additions & 5 deletions ui2/src/features/nodes/components/Node/Document/Document.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {useContext} from "react"
import {useAppDispatch, useAppSelector} from "@/app/hooks"
import {Checkbox} from "@mantine/core"
import {Checkbox, Stack} from "@mantine/core"
import {useContext} from "react"

import {
commanderSelectionNodeAdded,
commanderSelectionNodeRemoved,
dragNodesStarted,
selectSelectedNodeIds
} from "@/features/ui/uiSlice"

Expand All @@ -18,9 +19,11 @@ import {useGetDocumentThumbnailQuery} from "@/features/nodes/apiSlice"
type Args = {
node: NodeType
onClick: (node: NodeType) => void
onDragStart: (nodeID: string, event: React.DragEvent) => void
onDrag: (nodeID: string, event: React.DragEvent) => void
}

export default function Document({node, onClick}: Args) {
export default function Document({node, onClick, onDrag, onDragStart}: Args) {
const mode: PanelMode = useContext(PanelContext)
const selectedIds = useAppSelector(s =>
selectSelectedNodeIds(s, mode)
Expand All @@ -37,14 +40,31 @@ export default function Document({node, onClick}: Args) {
}
}

const onDragStartLocal = (e: React.DragEvent) => {
dispatch(dragNodesStarted([node.id, ...selectedIds]))
onDragStart(node.id, e)
}

const onDragEnd = () => {}

const onDragLocal = (e: React.DragEvent) => {
onDrag(node.id, e)
}

return (
<div className={classes.document}>
<Stack
className={classes.document}
draggable
onDragStart={onDragStartLocal}
onDrag={onDragLocal}
onDragEnd={onDragEnd}
>
<Checkbox onChange={onCheck} checked={selectedIds.includes(node.id)} />
<a onClick={() => onClick(node)}>
<img src={data} />
<Tags names={tagNames} />
<div className={classes.title}>{node.title}</div>
</a>
</div>
</Stack>
)
}
32 changes: 26 additions & 6 deletions ui2/src/features/nodes/components/Node/Folder/Folder.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
import {useContext} from "react"
import {useAppDispatch, useAppSelector} from "@/app/hooks"
import {Checkbox} from "@mantine/core"
import {Checkbox, Stack} from "@mantine/core"
import {useContext} from "react"

import {
commanderSelectionNodeAdded,
commanderSelectionNodeRemoved,
dragNodesStarted,
selectSelectedNodeIds
} from "@/features/ui/uiSlice"

import Tags from "@/features/nodes/components/Node/Tags"
import classes from "./Folder.module.scss"
import type {NodeType, PanelMode} from "@/types"
import classes from "./Folder.module.scss"

import PanelContext from "@/contexts/PanelContext"

type Args = {
node: NodeType
onClick: (node: NodeType) => void
onDragStart: (nodeID: string, event: React.DragEvent) => void
onDrag: (nodeID: string, event: React.DragEvent) => void
}

export default function Folder({node, onClick}: Args) {
export default function Folder({node, onClick, onDrag, onDragStart}: Args) {
const mode: PanelMode = useContext(PanelContext)
const selectedIds = useAppSelector(s =>
selectSelectedNodeIds(s, mode)
Expand All @@ -35,14 +38,31 @@ export default function Folder({node, onClick}: Args) {
}
}

const onDragStartLocal = (e: React.DragEvent) => {
dispatch(dragNodesStarted([node.id, ...selectedIds]))
onDragStart(node.id, e)
}

const onDragEnd = () => {}

const onDragLocal = (e: React.DragEvent) => {
onDrag(node.id, e)
}

return (
<div className={classes.folder}>
<Stack
className={classes.folder}
draggable
onDragStart={onDragStartLocal}
onDrag={onDragLocal}
onDragEnd={onDragEnd}
>
<Checkbox onChange={onCheck} checked={selectedIds.includes(node.id)} />
<a onClick={() => onClick(node)}>
<div className={classes.folderIcon}></div>
<Tags names={tagNames} />
<div className={classes.title}>{node.title}</div>
</a>
</div>
</Stack>
)
}
22 changes: 19 additions & 3 deletions ui2/src/features/nodes/components/Node/Node.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,28 @@ import Folder from "./Folder/Folder"
type Args = {
node: NodeType
onClick: (node: NodeType) => void
onDragStart: (nodeID: string, event: React.DragEvent) => void
onDrag: (nodeID: string, event: React.DragEvent) => void
}

export default function Node({node, onClick}: Args) {
export default function Node({node, onClick, onDrag, onDragStart}: Args) {
if (node.ctype == "folder") {
return <Folder onClick={onClick} node={node} />
return (
<Folder
onClick={onClick}
node={node}
onDrag={onDrag}
onDragStart={onDragStart}
/>
)
}

return <Document onClick={onClick} node={node} />
return (
<Document
onClick={onClick}
node={node}
onDrag={onDrag}
onDragStart={onDragStart}
/>
)
}
27 changes: 22 additions & 5 deletions ui2/src/features/ui/uiSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,10 @@ interface DragNDropState {
in another thumbnail panel or inside Commander
*/
pages: Array<ClientPage> | null
pagesDocID: string
pagesDocParentID: string
pagesDocID?: string
pagesDocParentID?: string
// IDs of the nodes being dragged
nodeIDs: string[] | null
}

type PanelComponent = "commander" | "viewer" | "searchResults"
Expand Down Expand Up @@ -529,11 +531,22 @@ const uiSlice = createSlice({
state.dragndrop = {
pages: pages,
pagesDocID: docID,
pagesDocParentID: docParentID
pagesDocParentID: docParentID,
nodeIDs: null
}
}
},
dragPagesEnded(state) {
dragNodesStarted(state, action: PayloadAction<string[]>) {
if (state.dragndrop) {
state.dragndrop.nodeIDs = action.payload
} else {
state.dragndrop = {
nodeIDs: action.payload,
pages: null
}
}
},
dragEnded(state) {
if (state.dragndrop) {
state.dragndrop = undefined
}
Expand Down Expand Up @@ -566,7 +579,8 @@ export const {
viewerSelectionCleared,
currentDocVerUpdated,
dragPagesStarted,
dragPagesEnded
dragNodesStarted,
dragEnded
} = uiSlice.actions
export default uiSlice.reducer

Expand Down Expand Up @@ -718,6 +732,9 @@ export const selectDraggedPagesDocID = (state: RootState) =>
export const selectDraggedPagesDocParentID = (state: RootState) =>
state.ui.dragndrop?.pagesDocParentID

export const selectDraggedNodeIDs = (state: RootState) =>
state.ui.dragndrop?.nodeIDs

/* Load initial collapse state value from cookie */
function initial_collapse_value(): boolean {
const collapsed = Cookies.get(NAVBAR_COLLAPSED_COOKIE) as BooleanString
Expand Down
6 changes: 3 additions & 3 deletions ui2/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {MantineProvider} from "@mantine/core"
import React from "react"
import ReactDOM from "react-dom/client"
import {Provider} from "react-redux"
import {RouterProvider} from "react-router-dom"
import {MantineProvider} from "@mantine/core"

import "@/index.css"
import {store} from "@/app/store"
import {fetchCurrentUser} from "@/slices/currentUser"
import {cookieLoaded} from "@/features/auth/slice"
import "@/index.css"
import {fetchCurrentUser} from "@/slices/currentUser"

import theme from "@/themes"
import router from "./router"
Expand Down

0 comments on commit 919fbce

Please sign in to comment.