Skip to content

Commit 0f5a18b

Browse files
committed
Add folder creation and moving functionality with modals
1 parent c0bd6ab commit 0f5a18b

File tree

4 files changed

+182
-33
lines changed

4 files changed

+182
-33
lines changed

web/oss/src/components/pages/prompts/PromptsPage.tsx

Lines changed: 111 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {useMemo, useState} from "react"
22

3-
import {Button, Dropdown, Input, Space, Table, Typography} from "antd"
3+
import {Button, Dropdown, Input, Space, Table, Typography, message} from "antd"
44
import {ColumnsType} from "antd/es/table"
55
import useSWR from "swr"
66

@@ -17,14 +17,15 @@ import {
1717

1818
import {useBreadcrumbsEffect} from "@/oss/lib/hooks/useBreadcrumbs"
1919
import {useProjectData} from "@/oss/state/project"
20-
import {queryFolders} from "@/oss/services/folders"
20+
import {createFolder, editFolder, queryFolders} from "@/oss/services/folders"
2121
import PromptsBreadcrumb from "./components/PromptsBreadcrumb"
2222
import {buildFolderTree, FolderTreeNode} from "./assets/utils"
2323
import {MoreOutlined} from "@ant-design/icons"
2424
import {formatDay} from "@/oss/lib/helpers/dateTimeHelper"
2525
import {DataNode} from "antd/es/tree"
2626
import MoveFolderModal from "./modals/MoveFolderModal"
2727
import DeleteFolderModal from "./modals/DeleteFolderModal"
28+
import NewFolderModal from "./modals/NewFolderModal"
2829

2930
const {Title} = Typography
3031

@@ -40,13 +41,18 @@ const PromptsPage = () => {
4041
const [deleteModalOpen, setDeleteModalOpen] = useState(false)
4142
const [renameValue, setRenameValue] = useState("")
4243
const [moveSelection, setMoveSelection] = useState<string | null>(null)
44+
const [moveFolderId, setMoveFolderId] = useState<string | null>(null)
4345
const [newFolderName, setNewFolderName] = useState("")
46+
const [isCreatingFolder, setIsCreatingFolder] = useState(false)
47+
const [isMovingFolder, setIsMovingFolder] = useState(false)
4448

4549
useBreadcrumbsEffect({breadcrumbs: {prompts: {label: "prompts"}}}, [])
4650

47-
const {data: foldersData, isLoading} = useSWR(projectId ? ["folders", projectId] : null, () =>
48-
queryFolders({folder: {}}),
49-
)
51+
const {
52+
data: foldersData,
53+
isLoading,
54+
mutate,
55+
} = useSWR(projectId ? ["folders", projectId] : null, () => queryFolders({folder: {}}))
5056

5157
const {roots, foldersById} = useMemo(() => {
5258
const folders = foldersData?.folders ?? []
@@ -64,17 +70,20 @@ const PromptsPage = () => {
6470
key: node.id!,
6571
title: node.name,
6672
children: buildNodes(node.children || []),
67-
disableCheckbox: node.id === currentFolderId,
68-
selectable: node.id !== currentFolderId,
6973
}))
7074

7175
return buildNodes(roots)
72-
}, [currentFolderId, roots])
76+
}, [roots])
7377

74-
const moveDestinationName = useMemo(
75-
() => (moveSelection ? (foldersById[moveSelection]?.name ?? moveSelection) : null),
76-
[moveSelection, foldersById],
77-
)
78+
const moveDestinationName = useMemo(() => {
79+
if (!moveSelection) return null
80+
return foldersById[moveSelection]?.name ?? moveSelection
81+
}, [moveSelection, foldersById])
82+
83+
const moveFolderName = useMemo(() => {
84+
if (!moveFolderId) return null
85+
return foldersById[moveFolderId]?.name ?? moveFolderId
86+
}, [foldersById, moveFolderId])
7887

7988
// what we show in the table
8089
const visibleRows: FolderTreeNode[] = useMemo(() => {
@@ -92,6 +101,74 @@ const PromptsPage = () => {
92101
setCurrentFolderId(folderId)
93102
}
94103

104+
const openNewFolderModal = () => {
105+
setNewFolderName("Untitled folder")
106+
setNewFolderModalOpen(true)
107+
}
108+
109+
const handleCreateFolder = async () => {
110+
const name = newFolderName.trim() || "Untitled folder"
111+
112+
setIsCreatingFolder(true)
113+
try {
114+
await createFolder({
115+
folder: {
116+
name,
117+
parent_id: currentFolderId ?? null,
118+
},
119+
})
120+
121+
await mutate()
122+
setNewFolderModalOpen(false)
123+
message.success("Folder created")
124+
} catch (error) {
125+
message.error("Failed to create folder")
126+
} finally {
127+
setIsCreatingFolder(false)
128+
}
129+
}
130+
131+
const handleOpenMoveModal = (folderId: string | null) => {
132+
if (!folderId) return
133+
134+
setMoveFolderId(folderId)
135+
setMoveSelection(folderId)
136+
setMoveModalOpen(true)
137+
}
138+
139+
const handleCloseMoveModal = () => {
140+
setMoveModalOpen(false)
141+
setMoveFolderId(null)
142+
setMoveSelection(null)
143+
}
144+
145+
const handleMoveFolder = async () => {
146+
if (!moveFolderId || !moveSelection || moveSelection === moveFolderId) {
147+
message.warning("Select a destination folder")
148+
return
149+
}
150+
151+
setIsMovingFolder(true)
152+
try {
153+
await editFolder(moveFolderId, {
154+
folder: {
155+
id: moveFolderId,
156+
parent_id: moveSelection,
157+
},
158+
})
159+
160+
await mutate()
161+
setMoveModalOpen(false)
162+
setMoveFolderId(null)
163+
setMoveSelection(null)
164+
message.success("Folder moved")
165+
} catch (error) {
166+
message.error("Failed to move folder")
167+
} finally {
168+
setIsMovingFolder(false)
169+
}
170+
}
171+
95172
const columns: ColumnsType<FolderTreeNode> = [
96173
{
97174
title: "Name",
@@ -144,7 +221,7 @@ const PromptsPage = () => {
144221
icon: <FolderDashedIcon size={16} />,
145222
onClick: (e: any) => {
146223
e.domEvent.stopPropagation()
147-
setMoveModalOpen(true)
224+
handleOpenMoveModal(record.id as string)
148225
},
149226
},
150227
{
@@ -185,6 +262,8 @@ const PromptsPage = () => {
185262
foldersById={foldersById}
186263
currentFolderId={currentFolderId}
187264
onFolderChange={handleBreadcrumbFolderChange}
265+
onNewFolder={openNewFolderModal}
266+
onMoveFolder={handleOpenMoveModal}
188267
/>
189268

190269
<div className="flex flex-col gap-2">
@@ -225,7 +304,7 @@ const PromptsPage = () => {
225304
label: "New folder",
226305
onClick: (event) => {
227306
event.domEvent.stopPropagation()
228-
// onNewFolder?.()
307+
openNewFolderModal()
229308
},
230309
},
231310
{
@@ -279,20 +358,35 @@ const PromptsPage = () => {
279358
</div>
280359

281360
<MoveFolderModal
282-
foldername={currentFolder?.name}
361+
folderName={moveFolderName}
283362
moveDestinationName={moveDestinationName}
284-
moveModalOpen={moveModalOpen}
285-
setMoveModalOpen={setMoveModalOpen}
363+
open={moveModalOpen}
364+
onCancel={handleCloseMoveModal}
365+
onMove={handleMoveFolder}
286366
treeData={treeData}
287367
moveSelection={moveSelection}
288368
setMoveSelection={setMoveSelection}
369+
isMoving={isMovingFolder}
370+
disabledConfirm={!moveFolderId || !moveSelection || moveSelection === moveFolderId}
289371
/>
290372

291373
<DeleteFolderModal
292374
deleteModalOpen={deleteModalOpen}
293375
setDeleteModalOpen={setDeleteModalOpen}
294376
folderName={currentFolder?.name}
295377
/>
378+
379+
<NewFolderModal
380+
open={newFolderModalOpen}
381+
folderName={newFolderName}
382+
setFolderName={setNewFolderName}
383+
onCreate={handleCreateFolder}
384+
onCancel={() => {
385+
setNewFolderModalOpen(false)
386+
setNewFolderName("")
387+
}}
388+
confirmLoading={isCreatingFolder}
389+
/>
296390
</div>
297391
)
298392
}

web/oss/src/components/pages/prompts/components/PromptsBreadcrumb.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ interface PromptsBreadcrumbProps {
1616
foldersById: Record<string, FolderTreeNode>
1717
currentFolderId: string | null
1818
onFolderChange?: (folderId: string | null) => void
19+
onNewFolder?: () => void
20+
onMoveFolder?: (folderId: string | null) => void
1921
}
2022

2123
const useStyles = createUseStyles((theme: JSSTheme) => ({
@@ -46,6 +48,8 @@ const PromptsBreadcrumb = ({
4648
foldersById,
4749
currentFolderId,
4850
onFolderChange,
51+
onNewFolder,
52+
onMoveFolder,
4953
}: PromptsBreadcrumbProps) => {
5054
const classes = useStyles()
5155
const {project} = useProjectData()
@@ -87,7 +91,7 @@ const PromptsBreadcrumb = ({
8791
key: "move_folder",
8892
icon: <FolderDashedIcon size={16} />,
8993
label: "Move",
90-
onClick: () => {},
94+
onClick: () => onMoveFolder?.(currentFolderId),
9195
// disabled: disableFolderActions,
9296
},
9397
{
@@ -103,7 +107,7 @@ const PromptsBreadcrumb = ({
103107
key: "new_folder",
104108
icon: <FolderIcon size={16} />,
105109
label: "New folder",
106-
onClick: () => {},
110+
onClick: () => onNewFolder?.(),
107111
},
108112
{
109113
type: "divider",
@@ -155,7 +159,7 @@ const PromptsBreadcrumb = ({
155159
})
156160

157161
return base
158-
}, [project, folderChain, onFolderChange])
162+
}, [project, folderChain, onFolderChange, onNewFolder, onMoveFolder])
159163

160164
return <Breadcrumb items={items} className={classes.container} />
161165
}

web/oss/src/components/pages/prompts/modals/MoveFolderModal.tsx

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,40 @@
11
import {Modal, Tree} from "antd"
22
import {DataNode} from "antd/es/tree"
3-
import React, {SetStateAction} from "react"
3+
import React from "react"
44

55
interface MoveFolderModalProps {
6-
foldername?: string | null | undefined
7-
moveModalOpen: boolean
8-
setMoveModalOpen: (value: SetStateAction<boolean>) => void
6+
folderName?: string | null
7+
open: boolean
8+
onCancel: () => void
9+
onMove: () => void
910
moveDestinationName: string | null
1011
treeData: DataNode[]
1112
moveSelection: string | null
12-
setMoveSelection: (value: SetStateAction<string | null>) => void
13+
setMoveSelection: (value: string | null) => void
14+
isMoving?: boolean
15+
disabledConfirm?: boolean
1316
}
1417

1518
const MoveFolderModal = ({
16-
foldername,
17-
moveModalOpen,
18-
setMoveModalOpen,
19+
folderName,
20+
open,
21+
onCancel,
22+
onMove,
1923
moveDestinationName,
2024
treeData,
2125
moveSelection,
2226
setMoveSelection,
27+
isMoving,
28+
disabledConfirm,
2329
}: MoveFolderModalProps) => {
2430
return (
2531
<Modal
26-
title={`Move ${foldername || "folder"}`}
27-
open={moveModalOpen}
28-
onOk={() => setMoveModalOpen(false)}
29-
onCancel={() => setMoveModalOpen(false)}
30-
okText={moveDestinationName ? `Move to ${moveDestinationName}` : "Move"}
32+
title={`Move ${folderName || "folder"}`}
33+
open={open}
34+
onOk={onMove}
35+
onCancel={onCancel}
36+
okText={moveDestinationName ? `Move to ${moveDestinationName}` : "Move folder"}
37+
okButtonProps={{disabled: disabledConfirm, loading: isMoving}}
3138
destroyOnClose
3239
>
3340
<div className="flex flex-col gap-2">
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import {Input, Modal} from "antd"
2+
import React, {Dispatch, SetStateAction} from "react"
3+
4+
interface NewFolderModalProps {
5+
open: boolean
6+
folderName: string
7+
setFolderName: Dispatch<SetStateAction<string>>
8+
onCreate: () => Promise<void>
9+
onCancel: () => void
10+
confirmLoading?: boolean
11+
}
12+
13+
const NewFolderModal = ({
14+
open,
15+
folderName,
16+
setFolderName,
17+
onCreate,
18+
onCancel,
19+
confirmLoading,
20+
}: NewFolderModalProps) => {
21+
return (
22+
<Modal
23+
title="New folder"
24+
open={open}
25+
onCancel={onCancel}
26+
onOk={onCreate}
27+
okText="Create"
28+
okButtonProps={{loading: confirmLoading}}
29+
destroyOnClose
30+
>
31+
<div className="flex flex-col gap-3">
32+
<div className="text-gray-500">Choose a folder name.</div>
33+
<Input
34+
value={folderName}
35+
onChange={(event) => setFolderName(event.target.value)}
36+
placeholder="Untitled folder"
37+
autoFocus
38+
/>
39+
</div>
40+
</Modal>
41+
)
42+
}
43+
44+
export default NewFolderModal

0 commit comments

Comments
 (0)