diff --git a/ui2/src/components/Commander/Commander.tsx b/ui2/src/components/Commander/Commander.tsx
index f6545b6ab..cc2f9524e 100644
--- a/ui2/src/components/Commander/Commander.tsx
+++ b/ui2/src/components/Commander/Commander.tsx
@@ -3,7 +3,7 @@ import {Group} from "@mantine/core"
import {useSelector, useDispatch} from "react-redux"
import {useNavigate} from "react-router-dom"
-import FolderNodeActions from "@/components/FolderNodeActions"
+import FolderNodeActions from "@/components/Commander/FolderNodeActions"
import Node from "@/components/Node"
import {
selectPanelNodes,
@@ -111,7 +111,7 @@ export default function Commander({mode}: Args) {
}
const nodes = data.map((n: NodeType) => (
-
+
))
if (nodes.length > 0) {
diff --git a/ui2/src/components/Commander/DeleteButton.tsx b/ui2/src/components/Commander/DeleteButton.tsx
new file mode 100644
index 000000000..a8e9a7214
--- /dev/null
+++ b/ui2/src/components/Commander/DeleteButton.tsx
@@ -0,0 +1,12 @@
+import {Button} from "@mantine/core"
+import {IconTrash} from "@tabler/icons-react"
+
+export default function DeleteButton() {
+ const onClick = () => {}
+
+ return (
+ } onClick={onClick} variant={"default"}>
+ Delete
+
+ )
+}
diff --git a/ui2/src/components/Commander/FolderNodeActions/FolderNodeActions.tsx b/ui2/src/components/Commander/FolderNodeActions/FolderNodeActions.tsx
new file mode 100644
index 000000000..ee2dbabd7
--- /dev/null
+++ b/ui2/src/components/Commander/FolderNodeActions/FolderNodeActions.tsx
@@ -0,0 +1,33 @@
+import {Group} from "@mantine/core"
+import {useSelector} from "react-redux"
+import {selectSelectedNodeIds} from "@/slices/dualPanel"
+
+import type {RootState} from "@/app/types"
+import type {PanelMode} from "@/types"
+import ToggleSecondaryPanel from "@/components/DualPanel/ToggleSecondaryPanel"
+import DeleteButton from "@/components/Commander/DeleteButton"
+import NewFolderButton from "@/components/Commander/NewFolderButton"
+import UploadButton from "@/components/Commander/UploadButton"
+
+type Args = {
+ mode: PanelMode
+}
+
+export default function FolderNodeActions({mode}: Args) {
+ const selectedIds = useSelector((state: RootState) =>
+ selectSelectedNodeIds(state, mode)
+ ) as Array
+
+ return (
+
+
+
+
+ {selectedIds.length > 0 && }
+
+
+
+
+
+ )
+}
diff --git a/ui2/src/components/FolderNodeActions/index.tsx b/ui2/src/components/Commander/FolderNodeActions/index.tsx
similarity index 100%
rename from ui2/src/components/FolderNodeActions/index.tsx
rename to ui2/src/components/Commander/FolderNodeActions/index.tsx
diff --git a/ui2/src/components/Commander/NewFolderButton.tsx b/ui2/src/components/Commander/NewFolderButton.tsx
new file mode 100644
index 000000000..e0f1b6c94
--- /dev/null
+++ b/ui2/src/components/Commander/NewFolderButton.tsx
@@ -0,0 +1,35 @@
+import {ActionIcon} from "@mantine/core"
+import {IconFolderPlus} from "@tabler/icons-react"
+import {useSelector, useDispatch} from "react-redux"
+import {selectCurrentFolderID, folderAdded} from "@/slices/dualPanel"
+import create_new_folder from "@/components/modals/NewFolder"
+
+import type {RootState} from "@/app/types"
+import type {NodeType, PanelMode} from "@/types"
+
+type Args = {
+ mode: PanelMode
+}
+
+export default function NewFolderButton({mode}: Args) {
+ const dispatch = useDispatch()
+ const currentFolderId = useSelector((state: RootState) =>
+ selectCurrentFolderID(state, mode)
+ )
+
+ const onNewFolder = () => {
+ if (!currentFolderId) {
+ console.error("Error: no current folder found")
+ return
+ }
+ create_new_folder(currentFolderId).then((new_folder: NodeType) => {
+ dispatch(folderAdded({node: new_folder, mode: mode}))
+ })
+ }
+
+ return (
+
+
+
+ )
+}
diff --git a/ui2/src/components/Commander/UploadButton.tsx b/ui2/src/components/Commander/UploadButton.tsx
new file mode 100644
index 000000000..1fb9bc345
--- /dev/null
+++ b/ui2/src/components/Commander/UploadButton.tsx
@@ -0,0 +1,16 @@
+import {ActionIcon, FileButton} from "@mantine/core"
+import {IconUpload} from "@tabler/icons-react"
+
+export default function UploadButton() {
+ const onUpload = () => {}
+
+ return (
+
+ {props => (
+
+
+
+ )}
+
+ )
+}
diff --git a/ui2/src/components/FolderNodeActions/FolderNodeActions.tsx b/ui2/src/components/FolderNodeActions/FolderNodeActions.tsx
deleted file mode 100644
index 75837ffa9..000000000
--- a/ui2/src/components/FolderNodeActions/FolderNodeActions.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import {Group, ActionIcon, FileButton} from "@mantine/core"
-import {IconFolderPlus, IconUpload} from "@tabler/icons-react"
-import {useSelector, useDispatch} from "react-redux"
-import {selectCurrentFolderID, folderAdded} from "@/slices/dualPanel"
-import create_new_folder from "@/components/modals/NewFolder"
-
-import type {RootState} from "@/app/types"
-import type {NodeType, PanelMode} from "@/types"
-import ToggleSecondaryPanel from "../DualPanel/ToggleSecondaryPanel"
-
-type Args = {
- mode: PanelMode
-}
-
-export default function FolderNodeActions({mode}: Args) {
- const dispatch = useDispatch()
- const currentFolderId = useSelector((state: RootState) =>
- selectCurrentFolderID(state, mode)
- )
-
- const onNewFolder = () => {
- if (!currentFolderId) {
- console.error("Error: no current folder found")
- return
- }
- create_new_folder(currentFolderId).then((new_folder: NodeType) => {
- dispatch(folderAdded({node: new_folder, mode: mode}))
- })
- }
-
- const onUpload = () => {}
-
- return (
-
-
-
- {props => (
-
-
-
- )}
-
-
-
-
-
-
-
-
-
-
- )
-}
diff --git a/ui2/src/components/Node/Document/Document.tsx b/ui2/src/components/Node/Document/Document.tsx
index 6b53b1347..5f49a8717 100644
--- a/ui2/src/components/Node/Document/Document.tsx
+++ b/ui2/src/components/Node/Document/Document.tsx
@@ -1,17 +1,39 @@
+import {useDispatch, useSelector} from "react-redux"
import {Checkbox} from "@mantine/core"
-import type {NodeType} from "@/types"
+import {
+ selectSelectedNodeIds,
+ selectionAddNode,
+ selectionRemoveNode
+} from "@/slices/dualPanel"
+
+import type {NodeType, PanelMode} from "@/types"
import classes from "./Document.module.css"
+import {RootState} from "@/app/types"
type Args = {
node: NodeType
+ mode: PanelMode
onClick: (node: NodeType) => void
}
-export default function Document({node, onClick}: Args) {
+export default function Document({node, mode, onClick}: Args) {
+ const selectedIds = useSelector((state: RootState) =>
+ selectSelectedNodeIds(state, mode)
+ ) as Array
+ const dispatch = useDispatch()
+
+ const onCheck = (e: React.ChangeEvent) => {
+ if (e.currentTarget.checked) {
+ dispatch(selectionAddNode({selectionId: node.id, mode}))
+ } else {
+ dispatch(selectionRemoveNode({selectionId: node.id, mode}))
+ }
+ }
+
return (
-
+
onClick(node)}>
{node.title}
diff --git a/ui2/src/components/Node/Folder/Folder.tsx b/ui2/src/components/Node/Folder/Folder.tsx
index 89d7b63fa..e431b36b7 100644
--- a/ui2/src/components/Node/Folder/Folder.tsx
+++ b/ui2/src/components/Node/Folder/Folder.tsx
@@ -1,16 +1,39 @@
+import {useDispatch, useSelector} from "react-redux"
import {Checkbox} from "@mantine/core"
-import type {NodeType} from "@/types"
+
+import {
+ selectSelectedNodeIds,
+ selectionAddNode,
+ selectionRemoveNode
+} from "@/slices/dualPanel"
+
+import type {NodeType, PanelMode} from "@/types"
import classes from "./Folder.module.css"
+import {RootState} from "@/app/types"
type Args = {
node: NodeType
+ mode: PanelMode
onClick: (node: NodeType) => void
}
-export default function Folder({node, onClick}: Args) {
+export default function Folder({node, mode, onClick}: Args) {
+ const selectedIds = useSelector((state: RootState) =>
+ selectSelectedNodeIds(state, mode)
+ ) as Array
+ const dispatch = useDispatch()
+
+ const onCheck = (e: React.ChangeEvent) => {
+ if (e.currentTarget.checked) {
+ dispatch(selectionAddNode({selectionId: node.id, mode}))
+ } else {
+ dispatch(selectionRemoveNode({selectionId: node.id, mode}))
+ }
+ }
+
return (
-
+
onClick(node)}>
{node.title}
diff --git a/ui2/src/components/Node/Node.tsx b/ui2/src/components/Node/Node.tsx
index 75a205f49..5fb5de817 100644
--- a/ui2/src/components/Node/Node.tsx
+++ b/ui2/src/components/Node/Node.tsx
@@ -1,16 +1,17 @@
-import type {NodeType} from "@/types"
+import type {NodeType, PanelMode} from "@/types"
import Document from "./Document/Document"
import Folder from "./Folder/Folder"
type Args = {
node: NodeType
+ mode: PanelMode
onClick: (node: NodeType) => void
}
-export default function Node({node, onClick}: Args) {
+export default function Node({node, mode, onClick}: Args) {
if (node.ctype == "folder") {
- return
+ return
}
- return
+ return
}
diff --git a/ui2/src/slices/dualPanel.ts b/ui2/src/slices/dualPanel.ts
index 8760c2e13..ceabfadc1 100644
--- a/ui2/src/slices/dualPanel.ts
+++ b/ui2/src/slices/dualPanel.ts
@@ -38,11 +38,22 @@ type FolderAddedArgs = {
mode: PanelMode
}
+type SelectionNodePayload = {
+ selectionId: string
+ mode: PanelMode
+}
+
+type SelectionNodesPayload = {
+ selectionIds: Array
+ mode: PanelMode
+}
+
interface Commander {
currentNode: CurrentNodeType | null
pagination: PaginationType | null | undefined
lastPageSize: number
nodes: SliceState>
+ selectedIds: Array
}
interface Viewer {}
@@ -67,7 +78,8 @@ function commanderInitialState(node: CurrentNodeType | null): Commander {
status: "idle",
error: null,
data: null
- }
+ },
+ selectedIds: []
}
}
@@ -174,6 +186,68 @@ const dualPanelSlice = createSlice({
},
closeSecondaryPanel(state) {
state.secondaryPanel = null
+ },
+ selectionAddNode: (state, action: PayloadAction) => {
+ if (action.payload.mode == "main") {
+ if (state.mainPanel.commander) {
+ state.mainPanel.commander.selectedIds.push(action.payload.selectionId)
+ }
+ } else {
+ if (state.secondaryPanel?.commander) {
+ state.secondaryPanel.commander.selectedIds.push(
+ action.payload.selectionId
+ )
+ }
+ }
+ },
+ selectionAddNodes: (
+ state,
+ action: PayloadAction
+ ) => {
+ if (action.payload.mode == "main") {
+ // main panel / commander
+ if (state.mainPanel.commander) {
+ state.mainPanel.commander.selectedIds = action.payload.selectionIds
+ }
+ } else {
+ // secondary panel / commander
+ if (state.secondaryPanel?.commander) {
+ state.secondaryPanel.commander.selectedIds =
+ action.payload.selectionIds
+ }
+ }
+ },
+ selectionRemoveNode: (
+ state,
+ action: PayloadAction
+ ) => {
+ if (action.payload.mode == "main") {
+ if (state.mainPanel.commander) {
+ const newSelectedIds = state.mainPanel.commander.selectedIds.filter(
+ i => i != action.payload.selectionId
+ )
+ state.mainPanel.commander.selectedIds = newSelectedIds
+ }
+ } else {
+ if (state.secondaryPanel?.commander) {
+ const newSelectedIds =
+ state.secondaryPanel.commander.selectedIds.filter(
+ i => i != action.payload.selectionId
+ )
+ state.secondaryPanel.commander.selectedIds = newSelectedIds
+ }
+ }
+ },
+ clearNodesSelection: (state, action: PayloadAction) => {
+ if (action.payload == "main") {
+ if (state.mainPanel.commander) {
+ state.mainPanel.commander.selectedIds = []
+ }
+ } else {
+ if (state.secondaryPanel?.commander) {
+ state.secondaryPanel.commander.selectedIds = []
+ }
+ }
}
},
extraReducers(builder) {
@@ -249,7 +323,11 @@ export const {
setCurrentNode,
folderAdded,
openSecondaryPanel,
- closeSecondaryPanel
+ closeSecondaryPanel,
+ selectionAddNode,
+ selectionAddNodes,
+ selectionRemoveNode,
+ clearNodesSelection
} = dualPanelSlice.actions
export default dualPanelSlice.reducer
@@ -299,15 +377,25 @@ export const selectPanelNodes = (
const selectMainPanelNodes = (
state: RootState
): SliceState> => {
- const nodeIds = state.dualPanel.mainPanel.commander!.nodes.data!.map(
- (n: NodeWithSpinner) => n.id
- )
- const output = {
- status: "succeeded",
- error: null,
- data: state.dualPanel.nodes.filter((n: NodeType) => nodeIds.includes(n.id))
- } as SliceState>
- return output
+ if (state.dualPanel.mainPanel!.commander!.nodes.data) {
+ const nodeIds = state.dualPanel.mainPanel.commander!.nodes.data!.map(
+ (n: NodeWithSpinner) => n.id
+ )
+ const output = {
+ status: "succeeded",
+ error: null,
+ data: state.dualPanel.nodes.filter((n: NodeType) =>
+ nodeIds.includes(n.id)
+ )
+ } as SliceState>
+ return output
+ }
+
+ return {
+ status: state.dualPanel.mainPanel!.commander!.nodes.status,
+ error: state.dualPanel.mainPanel!.commander!.nodes.error,
+ data: null
+ }
}
const selectSecondaryPanelNodes = (
@@ -415,3 +503,11 @@ export const selectCommanderPageNumber = (
return state.dualPanel.secondaryPanel?.commander?.pagination?.pageNumber
}
+
+export const selectSelectedNodeIds = (state: RootState, mode: PanelMode) => {
+ if (mode == "main") {
+ return state.dualPanel.mainPanel.commander?.selectedIds
+ }
+
+ return state.dualPanel.secondaryPanel?.commander?.selectedIds
+}