Skip to content

Commit f388d83

Browse files
committed
Fix open folder
1 parent 818868d commit f388d83

File tree

3 files changed

+88
-139
lines changed

3 files changed

+88
-139
lines changed

site/src/components/TemplateVersionEditor/FileTree.tsx

Lines changed: 52 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -5,137 +5,91 @@ import TreeView from "@material-ui/lab/TreeView"
55
import TreeItem from "@material-ui/lab/TreeItem"
66
import Menu from "@material-ui/core/Menu"
77
import MenuItem from "@material-ui/core/MenuItem"
8-
import { FC, useMemo, useState } from "react"
8+
import { FC, useState } from "react"
99
import { TemplateVersionFileTree } from "util/templateVersion"
1010
import { DockerIcon } from "components/Icons/DockerIcon"
1111

12-
export interface File {
13-
path: string
14-
content?: string
15-
children: Record<string, File>
16-
}
17-
18-
const mapFileTreeToFiles = (
19-
fileTree: TemplateVersionFileTree,
20-
parent?: string,
21-
): Record<string, File> => {
22-
const files: Record<string, File> = {}
23-
24-
Object.keys(fileTree).forEach((filename) => {
25-
const currentPath = parent ? `${parent}/${filename}` : filename
26-
const content = fileTree[filename]
27-
if (typeof content === "string") {
28-
files[currentPath] = {
29-
path: currentPath,
30-
content,
31-
children: {},
32-
}
33-
} else {
34-
files[currentPath] = {
35-
path: currentPath,
36-
children: mapFileTreeToFiles(content, currentPath),
37-
}
38-
}
39-
})
40-
41-
return files
42-
}
43-
44-
const sortFileChildren = (file: File) => (a: string, b: string) => {
45-
const child = file.children[a]
46-
const childB = file.children[b]
47-
if (child.content === undefined) {
48-
return -1
49-
}
50-
if (childB.content === undefined) {
51-
return 1
52-
}
53-
return a.localeCompare(b)
54-
}
55-
56-
const sortRootFiles =
57-
(rootFiles: Record<string, File>) => (a: string, b: string) => {
58-
const child = rootFiles[a]
59-
const childB = rootFiles[b]
60-
if (child.content === undefined) {
12+
const sortFileTree =
13+
(fileTree: TemplateVersionFileTree) => (a: string, b: string) => {
14+
const contentA = fileTree[a]
15+
const contentB = fileTree[b]
16+
if (typeof contentA === "object") {
6117
return -1
6218
}
63-
if (childB.content === undefined) {
19+
if (typeof contentB === "object") {
6420
return 1
6521
}
6622
return a.localeCompare(b)
6723
}
6824

25+
type ContextMenu = {
26+
path: string
27+
clientX: number
28+
clientY: number
29+
}
30+
6931
export const FileTree: FC<{
70-
onSelect: (file: File) => void
71-
onDelete: (file: File) => void
72-
onRename: (file: File) => void
73-
files: TemplateVersionFileTree
74-
activeFile?: File
75-
}> = ({ activeFile, files, onDelete, onRename, onSelect }) => {
32+
onSelect: (path: string) => void
33+
onDelete: (path: string) => void
34+
onRename: (path: string) => void
35+
fileTree: TemplateVersionFileTree
36+
activePath?: string
37+
}> = ({ fileTree, activePath, onDelete, onRename, onSelect }) => {
7638
const styles = useStyles()
77-
const fileTree = useMemo<Record<string, File>>(
78-
() => mapFileTreeToFiles(files),
79-
[files],
80-
)
81-
const [contextMenu, setContextMenu] = useState<
82-
| {
83-
file: File
84-
clientX: number
85-
clientY: number
86-
}
87-
| undefined
88-
>()
89-
90-
const buildTreeItems = (name: string, file: File): JSX.Element => {
39+
const [contextMenu, setContextMenu] = useState<ContextMenu | undefined>()
40+
41+
const buildTreeItems = (
42+
filename: string,
43+
content?: TemplateVersionFileTree | string,
44+
parentPath?: string,
45+
): JSX.Element => {
46+
const currentPath = parentPath ? `${parentPath}/${filename}` : filename
9147
let icon: JSX.Element | null = null
92-
if (file.path.endsWith(".tf")) {
48+
if (filename.endsWith(".tf")) {
9349
icon = <FileTypeTerraform />
9450
}
95-
if (file.path.endsWith(".md")) {
51+
if (filename.endsWith(".md")) {
9652
icon = <FileTypeMarkdown />
9753
}
98-
if (file.path.endsWith("Dockerfile")) {
54+
if (filename.endsWith("Dockerfile")) {
9955
icon = <FileTypeDockerfile />
10056
}
10157

10258
return (
10359
<TreeItem
104-
nodeId={file.path}
105-
key={file.path}
106-
label={name}
60+
nodeId={currentPath}
61+
key={currentPath}
62+
label={filename}
10763
className={`${styles.fileTreeItem} ${
108-
file.path === activeFile?.path ? "active" : ""
64+
currentPath === activePath ? "active" : ""
10965
}`}
11066
onClick={() => {
111-
// Content can be an empty string
112-
if (file.content !== undefined) {
113-
onSelect(file)
114-
}
67+
onSelect(currentPath)
11568
}}
11669
onContextMenu={(event) => {
11770
event.preventDefault()
118-
if (!file.content) {
119-
return
120-
}
12171
setContextMenu(
12272
contextMenu
12373
? undefined
12474
: {
125-
file: file,
75+
path: currentPath,
12676
clientY: event.clientY,
12777
clientX: event.clientX,
12878
},
12979
)
13080
}}
13181
icon={icon}
13282
>
133-
{Object.keys(file.children)
134-
.sort(sortFileChildren(file))
135-
.map((path) => {
136-
const child = file.children[path]
137-
return buildTreeItems(path, child)
138-
})}
83+
{typeof content === "object" ? (
84+
Object.keys(content)
85+
.sort(sortFileTree(content))
86+
.map((filename) => {
87+
const child = content[filename]
88+
return buildTreeItems(filename, child, currentPath)
89+
})
90+
) : (
91+
<></>
92+
)}
13993
</TreeItem>
14094
)
14195
}
@@ -148,10 +102,10 @@ export const FileTree: FC<{
148102
className={styles.fileTree}
149103
>
150104
{Object.keys(fileTree)
151-
.sort(sortRootFiles(fileTree))
152-
.map((path) => {
153-
const child = fileTree[path]
154-
return buildTreeItems(path, child)
105+
.sort(sortFileTree(fileTree))
106+
.map((filename) => {
107+
const child = fileTree[filename]
108+
return buildTreeItems(filename, child)
155109
})}
156110

157111
<Menu
@@ -180,7 +134,7 @@ export const FileTree: FC<{
180134
if (!contextMenu) {
181135
return
182136
}
183-
onRename(contextMenu.file)
137+
onRename(contextMenu.path)
184138
setContextMenu(undefined)
185139
}}
186140
>
@@ -191,7 +145,7 @@ export const FileTree: FC<{
191145
if (!contextMenu) {
192146
return
193147
}
194-
onDelete(contextMenu.file)
148+
onDelete(contextMenu.path)
195149
setContextMenu(undefined)
196150
}}
197151
>

site/src/components/TemplateVersionEditor/TemplateVersionEditor.tsx

Lines changed: 30 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { navHeight } from "theme/constants"
2020
import {
2121
existsFile,
2222
getFileContent,
23+
isFolder,
2324
removeFile,
2425
setFile,
2526
TemplateVersionFileTree,
@@ -37,12 +38,6 @@ import {
3738
TemplateVersionStatusBadge,
3839
} from "./TemplateVersionStatusBadge"
3940

40-
interface File {
41-
path: string
42-
content?: string
43-
children: Record<string, File>
44-
}
45-
4641
export interface TemplateVersionEditorProps {
4742
template: Template
4843
templateVersion: TemplateVersion
@@ -59,16 +54,12 @@ const topbarHeight = navHeight
5954

6055
const findInitialFile = (
6156
fileTree: TemplateVersionFileTree,
62-
): File | undefined => {
63-
let initialFile: File | undefined
57+
): string | undefined => {
58+
let initialFile: string | undefined
6459

6560
traverse(fileTree, (content, filename, path) => {
6661
if (filename.endsWith(".tf")) {
67-
initialFile = {
68-
path,
69-
content: content as string,
70-
children: {},
71-
}
62+
initialFile = path
7263
}
7364
})
7465

@@ -93,9 +84,9 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
9384
})
9485
const [fileTree, setFileTree] = useState(initialFiles)
9586
const [createFileOpen, setCreateFileOpen] = useState(false)
96-
const [deleteFileOpen, setDeleteFileOpen] = useState<File>()
97-
const [renameFileOpen, setRenameFileOpen] = useState<File>()
98-
const [activeFile, setActiveFile] = useState<File | undefined>(() =>
87+
const [deleteFileOpen, setDeleteFileOpen] = useState<string>()
88+
const [renameFileOpen, setRenameFileOpen] = useState<string>()
89+
const [activePath, setActivePath] = useState<string | undefined>(() =>
9990
findInitialFile(fileTree),
10091
)
10192

@@ -149,6 +140,8 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
149140
const hasIcon = template.icon && template.icon !== ""
150141
const templateVersionSucceeded = templateVersion.job.status === "succeeded"
151142
const showBuildLogs = Boolean(buildLogs)
143+
const editorValue = getFileContent(activePath ?? "", fileTree) as string
144+
152145
useEffect(() => {
153146
window.dispatchEvent(new Event("resize"))
154147
}, [showBuildLogs])
@@ -243,11 +236,7 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
243236
checkExists={(path) => existsFile(path, fileTree)}
244237
onConfirm={(path) => {
245238
setFileTree((fileTree) => setFile(path, "", fileTree))
246-
setActiveFile({
247-
path,
248-
content: "",
249-
children: {},
250-
})
239+
setActivePath(path)
251240
setCreateFileOpen(false)
252241
setDirty(true)
253242
}}
@@ -257,25 +246,23 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
257246
if (!deleteFileOpen) {
258247
throw new Error("delete file must be set")
259248
}
260-
setFileTree((fileTree) =>
261-
removeFile(deleteFileOpen.path, fileTree),
262-
)
249+
setFileTree((fileTree) => removeFile(deleteFileOpen, fileTree))
263250
setDeleteFileOpen(undefined)
264-
if (activeFile?.path === deleteFileOpen.path) {
265-
setActiveFile(undefined)
251+
if (activePath === deleteFileOpen) {
252+
setActivePath(undefined)
266253
}
267254
setDirty(true)
268255
}}
269256
open={Boolean(deleteFileOpen)}
270257
onClose={() => setDeleteFileOpen(undefined)}
271-
filename={deleteFileOpen?.path || ""}
258+
filename={deleteFileOpen || ""}
272259
/>
273260
<RenameFileDialog
274261
open={Boolean(renameFileOpen)}
275262
onClose={() => {
276263
setRenameFileOpen(undefined)
277264
}}
278-
filename={renameFileOpen?.path || ""}
265+
filename={renameFileOpen || ""}
279266
checkExists={(path) => existsFile(path, fileTree)}
280267
onConfirm={(newPath) => {
281268
if (!renameFileOpen) {
@@ -284,40 +271,43 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
284271
setFileTree((fileTree) => {
285272
fileTree = setFile(
286273
newPath,
287-
getFileContent(renameFileOpen.path, fileTree),
274+
getFileContent(renameFileOpen, fileTree) as string,
288275
fileTree,
289276
)
290-
fileTree = removeFile(renameFileOpen.path, fileTree)
277+
fileTree = removeFile(renameFileOpen, fileTree)
291278
return fileTree
292279
})
293-
renameFileOpen.path = newPath
294-
setActiveFile(renameFileOpen)
280+
setActivePath(newPath)
295281
setRenameFileOpen(undefined)
296282
setDirty(true)
297283
}}
298284
/>
299285
</div>
300286
<FileTree
301-
files={fileTree}
287+
fileTree={fileTree}
302288
onDelete={(file) => setDeleteFileOpen(file)}
303-
onSelect={(file) => setActiveFile(file)}
289+
onSelect={(filePath) => {
290+
if (!isFolder(filePath, fileTree)) {
291+
setActivePath(filePath)
292+
}
293+
}}
304294
onRename={(file) => setRenameFileOpen(file)}
305-
activeFile={activeFile}
295+
activePath={activePath}
306296
/>
307297
</div>
308298

309299
<div className={styles.editorPane}>
310300
<div className={styles.editor} data-chromatic="ignore">
311-
{activeFile ? (
301+
{activePath ? (
312302
<MonacoEditor
313-
value={activeFile?.content}
314-
path={activeFile?.path}
303+
value={editorValue}
304+
path={activePath}
315305
onChange={(value) => {
316-
if (!activeFile) {
306+
if (!activePath) {
317307
return
318308
}
319309
setFileTree((fileTree) =>
320-
setFile(activeFile.path, value, fileTree),
310+
setFile(activePath, value, fileTree),
321311
)
322312
setDirty(true)
323313
}}

site/src/util/templateVersion.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,12 @@ export const getFileContent = (
100100
path: string,
101101
fileTree: TemplateVersionFileTree,
102102
) => {
103-
return get(fileTree, path.split("/"))
103+
return get(fileTree, path.split("/")) as string | TemplateVersionFileTree
104+
}
105+
106+
export const isFolder = (path: string, fileTree: TemplateVersionFileTree) => {
107+
const content = getFileContent(path, fileTree)
108+
return typeof content === "object"
104109
}
105110

106111
export const traverse = (

0 commit comments

Comments
 (0)