Skip to content

Commit 4e476dc

Browse files
Fix add file button, drag and rename in fiddle frontend (#1656)
Fixes #1392 and some other bugs <!-- ELLIPSIS_HIDDEN --> ---- > [!IMPORTANT] > Fixes add file button, drag-and-drop, and renaming in `FileViewer.tsx`, ensuring correct state updates for files and directories. > > - **Behavior**: > - Fixes add file button and drag-and-drop functionality in `FileViewer.tsx`. > - Corrects renaming logic for files and directories, ensuring state updates with `setFiles` and `setEmptydirs`. > - Handles empty directories during move and rename operations. > - **Functions**: > - Updates `onMove` to handle file and directory renames, using `fileRenames` and `emptyDirRenames`. > - Implements `onCreate` to add new files and directories, updating state accordingly. > - Refines `onRename` to adjust paths for files and directories, ensuring correct state management. > - **Misc**: > - Removes unused imports and variables. > - Fixes variable naming for clarity (e.g., `data2` to `data`). > > <sup>This description was created by </sup>[<img alt="Ellipsis" src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=BoundaryML%2Fbaml&utm_source=github&utm_medium=referral)<sup> for 9218c18. It will automatically update as commits are pushed.</sup> <!-- ELLIPSIS_HIDDEN -->
1 parent f3c2532 commit 4e476dc

1 file changed

Lines changed: 114 additions & 33 deletions

File tree

  • typescript/fiddle-frontend/app/[project_id]/_components/Tree

typescript/fiddle-frontend/app/[project_id]/_components/Tree/FileViewer.tsx

Lines changed: 114 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
// 1: Uncontrolled Tree
22
import { useEffect, useRef, useState } from 'react'
33

4-
import { MoveHandler, RenameHandler, Tree, type TreeApi } from 'react-arborist'
4+
import { MoveHandler, NodeApi, RenameHandler, Tree, type TreeApi } from 'react-arborist'
55

66
import { EditorFile } from '@/app/actions'
7-
// import { updateFileAtom } from '@baml/playground-common/baml_wasm_web/EventListener'
87
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
98
import { FilePlus, FolderPlus } from 'lucide-react'
109
import useResizeObserver from 'use-resize-observer'
1110
import { PROJECT_ROOT, activeFileNameAtom, currentEditorFilesAtom, emptyDirsAtom } from '../../_atoms/atoms'
1211
import Node from './Node'
12+
import { filesAtom } from '@/shared/baml-project-panel/atoms'
1313

1414
export const data = [
1515
{
@@ -106,12 +106,12 @@ function createTree(filePaths: string[]): TreeNode[] {
106106
const FileViewer = () => {
107107
const { width, height = 200, ref } = useResizeObserver()
108108
const editorFiles = useAtomValue(currentEditorFilesAtom)
109-
// const updateFile = useSetAtom(updateFileAtom)
109+
const setFiles = useSetAtom(filesAtom)
110110
const treeRef = useRef<TreeApi<any> | null>(null)
111111
const activeFile = useAtomValue(activeFileNameAtom)
112112
const [emptyDirs, setEmptydirs] = useAtom(emptyDirsAtom)
113113

114-
const data2 = createTree(editorFiles.map((f) => f.path).concat(emptyDirs))
114+
const data = createTree(editorFiles.map((f) => f.path).concat(emptyDirs))
115115

116116
const [term, setTerm] = useState('')
117117

@@ -152,7 +152,7 @@ const FileViewer = () => {
152152
ref={treeRef}
153153
openByDefault={false}
154154
// initialOpenState={{ baml_src: true }}
155-
data={data2}
155+
data={data}
156156
indent={12}
157157
initialOpenState={{ baml_src: true }}
158158
rowHeight={28}
@@ -163,42 +163,123 @@ const FileViewer = () => {
163163
return
164164
}
165165

166-
const renames = dragIds
167-
.filter((id) => id.endsWith('.baml') || id.endsWith('.json'))
168-
.map((id) => ({
169-
from: id,
170-
to: `${parentId}/${id.split('/').pop() ?? ''}`,
171-
}))
172-
173-
// updateFile({
174-
// reason: 'move_files',
175-
// root_path: PROJECT_ROOT,
176-
// files: [],
177-
// renames,
178-
// })
166+
const emptyDirsLookup = new Set(emptyDirs)
167+
168+
const emptyDirRenames = new Map<string, string>()
169+
const fileRenames: { from: string; to: string }[] = []
170+
171+
dragNodes.forEach((node) => {
172+
const stack: { nodes: NodeApi<any>[]; parents: string[] }[] = [
173+
{
174+
nodes: [node],
175+
parents: [],
176+
},
177+
]
178+
179+
while (stack.length > 0) {
180+
const { nodes, parents } = stack.pop()!
181+
182+
for (const node of nodes) {
183+
let dest: string
184+
185+
if (parents.length > 0) {
186+
dest = `${parentId}/${parents.join('/')}/${node.id.split('/').pop() ?? ''}`
187+
} else {
188+
dest = `${parentId}/${node.id.split('/').pop() ?? ''}`
189+
}
190+
191+
if (node.isLeaf) {
192+
if (dest !== node.id) {
193+
fileRenames.push({ from: node.id, to: dest })
194+
}
195+
} else {
196+
if (emptyDirsLookup.has(node.id) || emptyDirsLookup.has(`${node.id}/`)) {
197+
emptyDirRenames.set(`${node.id}/`, `${dest}/`)
198+
}
199+
stack.push({
200+
nodes: node.children!,
201+
parents: parents.concat(node.id.split('/').pop() ?? ''),
202+
})
203+
}
204+
}
205+
}
206+
})
207+
208+
console.log('onMove', { fileRenames, emptyDirRenames })
209+
210+
setFiles((prev) => {
211+
const movedFiles = { ...prev }
212+
213+
fileRenames.forEach((rename) => {
214+
movedFiles[rename.to] = movedFiles[rename.from]
215+
delete movedFiles[rename.from]
216+
})
217+
218+
return movedFiles
219+
})
220+
221+
setEmptydirs((prev) => {
222+
// TODO: See onRename(), there's some issue with trailing slashes, fix this.
223+
const movedEmptyDirs = prev.filter((dir) => !emptyDirRenames.has(dir) && !emptyDirRenames.has(`${dir}/`))
224+
emptyDirRenames.values().forEach((dir) => movedEmptyDirs.push(dir))
225+
226+
return movedEmptyDirs
227+
})
179228
}}
180229
onCreate={({ parentId, parentNode, type }) => {
230+
console.log('onCreate', type, parentId, parentNode)
231+
181232
if (type === 'internal') {
182233
const newDir = `${parentId ?? 'baml_src'}/new/`
183234
setEmptydirs((prev) => [...prev, newDir])
184235

185236
return { id: newDir, name: 'new_folder' }
186237
}
187-
console.log('onCreate', parentId, parentNode)
188-
189-
const newFileName = 'new.baml'
190-
// updateFile({
191-
// reason: 'create_file',
192-
// root_path: PROJECT_ROOT,
193-
// files: [
194-
// {
195-
// name: `baml_src/${newFileName}`,
196-
// content: '',
197-
// },
198-
// ],
199-
// })
200-
201-
return { id: `baml_src/${newFileName}`, name: newFileName }
238+
239+
const newFileName = `${parentId ?? 'baml_src'}/new.baml`
240+
setFiles((prev) => ({ ...prev, [newFileName]: '' }))
241+
242+
return { id: newFileName, name: newFileName }
243+
}}
244+
onRename={({ node, id, name }) => {
245+
const parentName = node.parent?.id ?? 'baml_src'
246+
const newName = `${parentName}/${name}`
247+
248+
const emptyDirRenames = emptyDirs
249+
.filter((dir) => dir.startsWith(id))
250+
.map((dir) => ({ from: dir, to: dir.replace(id, newName) }))
251+
252+
const fileRenames = editorFiles
253+
.filter((f) => f.path.startsWith(id))
254+
.map((f) => ({ from: f.path, to: f.path.replace(id, newName) }))
255+
256+
console.log('onRename', { fileRenames, emptyDirRenames })
257+
258+
if (emptyDirRenames.length > 0) {
259+
setEmptydirs((prev) => {
260+
const dirs = prev.filter((dir) => !dir.startsWith(id))
261+
// TODO: Something's causing the last slash to be removed after
262+
// triggering this handler more than once. This fixes it but we
263+
// should find where it's removed.
264+
emptyDirRenames.forEach((rename) => dirs.push(`${rename.to}/`))
265+
266+
console.log({ prev, dirs })
267+
268+
return dirs
269+
})
270+
}
271+
272+
if (fileRenames.length > 0) {
273+
setFiles((prev) => {
274+
const newFiles = { ...prev }
275+
fileRenames.forEach((rename) => {
276+
newFiles[rename.to] = newFiles[rename.from]
277+
delete newFiles[rename.from]
278+
})
279+
280+
return newFiles
281+
})
282+
}
202283
}}
203284
height={height}
204285
searchTerm={term}

0 commit comments

Comments
 (0)