Skip to content

Commit 77afdf7

Browse files
fix(site): Show folders in the template version editor (coder#6145)
1 parent 32fbd10 commit 77afdf7

File tree

12 files changed

+398
-208
lines changed

12 files changed

+398
-208
lines changed

.github/workflows/ci.yaml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -583,9 +583,6 @@ jobs:
583583
- run: yarn playwright:install
584584
working-directory: site
585585

586-
- run: yarn playwright:install-deps
587-
working-directory: site
588-
589586
- run: yarn playwright:test
590587
env:
591588
DEBUG: pw:api

site/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@
1515
"format:write:only": "prettier --write",
1616
"lint": "jest --selectProjects lint",
1717
"lint:fix": "FIX=true yarn lint",
18-
"playwright:install": "playwright install",
19-
"playwright:install-deps": "playwright install-deps",
18+
"playwright:install": "playwright install --with-deps chromium",
2019
"playwright:test": "playwright test --config=e2e/playwright.config.ts",
2120
"storybook": "start-storybook -p 6006",
2221
"storybook:build": "build-storybook",

site/src/components/TemplateVersionEditor/FileTree.tsx renamed to site/src/components/TemplateVersionEditor/FileTreeView.tsx

Lines changed: 59 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -5,114 +5,90 @@ 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"
9-
import { TemplateVersionFiles } from "util/templateVersion"
8+
import { FC, useState } from "react"
9+
import { FileTree } from "util/filetree"
1010
import { DockerIcon } from "components/Icons/DockerIcon"
1111

12-
export interface File {
12+
const sortFileTree = (fileTree: FileTree) => (a: string, b: string) => {
13+
const contentA = fileTree[a]
14+
const contentB = fileTree[b]
15+
if (typeof contentA === "object") {
16+
return -1
17+
}
18+
if (typeof contentB === "object") {
19+
return 1
20+
}
21+
return a.localeCompare(b)
22+
}
23+
24+
type ContextMenu = {
1325
path: string
14-
content?: string
15-
children: Record<string, File>
26+
clientX: number
27+
clientY: number
1628
}
1729

18-
export const FileTree: FC<{
19-
onSelect: (file: File) => void
20-
onDelete: (file: File) => void
21-
onRename: (file: File) => void
22-
files: TemplateVersionFiles
23-
activeFile?: File
24-
}> = ({ activeFile, files, onDelete, onRename, onSelect }) => {
30+
export const FileTreeView: FC<{
31+
onSelect: (path: string) => void
32+
onDelete: (path: string) => void
33+
onRename: (path: string) => void
34+
fileTree: FileTree
35+
activePath?: string
36+
}> = ({ fileTree, activePath, onDelete, onRename, onSelect }) => {
2537
const styles = useStyles()
26-
const fileTree = useMemo<Record<string, File>>(() => {
27-
const paths = Object.keys(files)
28-
const roots: Record<string, File> = {}
29-
paths.forEach((path) => {
30-
const pathParts = path.split("/")
31-
const firstPart = pathParts.shift()
32-
if (!firstPart) {
33-
// Not possible!
34-
return
35-
}
36-
let activeFile = roots[firstPart]
37-
if (!activeFile) {
38-
activeFile = {
39-
path: firstPart,
40-
children: {},
41-
}
42-
roots[firstPart] = activeFile
43-
}
44-
while (pathParts.length > 0) {
45-
const pathPart = pathParts.shift()
46-
if (!pathPart) {
47-
continue
48-
}
49-
if (!activeFile.children[pathPart]) {
50-
activeFile.children[pathPart] = {
51-
path: activeFile.path + "/" + pathPart,
52-
children: {},
53-
}
54-
}
55-
activeFile = activeFile.children[pathPart]
56-
}
57-
activeFile.content = files[path]
58-
activeFile.path = path
59-
})
60-
return roots
61-
}, [files])
62-
const [contextMenu, setContextMenu] = useState<
63-
| {
64-
file: File
65-
clientX: number
66-
clientY: number
67-
}
68-
| undefined
69-
>()
70-
71-
const buildTreeItems = (name: string, file: File): JSX.Element => {
38+
const [contextMenu, setContextMenu] = useState<ContextMenu | undefined>()
39+
40+
const buildTreeItems = (
41+
filename: string,
42+
content?: FileTree | string,
43+
parentPath?: string,
44+
): JSX.Element => {
45+
const currentPath = parentPath ? `${parentPath}/${filename}` : filename
7246
let icon: JSX.Element | null = null
73-
if (file.path.endsWith(".tf")) {
47+
if (filename.endsWith(".tf")) {
7448
icon = <FileTypeTerraform />
7549
}
76-
if (file.path.endsWith(".md")) {
50+
if (filename.endsWith(".md")) {
7751
icon = <FileTypeMarkdown />
7852
}
79-
if (file.path.endsWith("Dockerfile")) {
53+
if (filename.endsWith("Dockerfile")) {
8054
icon = <FileTypeDockerfile />
8155
}
8256

8357
return (
8458
<TreeItem
85-
nodeId={file.path}
86-
key={file.path}
87-
label={name}
59+
nodeId={currentPath}
60+
key={currentPath}
61+
label={filename}
8862
className={`${styles.fileTreeItem} ${
89-
file.path === activeFile?.path ? "active" : ""
63+
currentPath === activePath ? "active" : ""
9064
}`}
9165
onClick={() => {
92-
if (file.content) {
93-
onSelect(file)
94-
}
66+
onSelect(currentPath)
9567
}}
9668
onContextMenu={(event) => {
9769
event.preventDefault()
98-
if (!file.content) {
99-
return
100-
}
10170
setContextMenu(
10271
contextMenu
10372
? undefined
10473
: {
105-
file: file,
74+
path: currentPath,
10675
clientY: event.clientY,
10776
clientX: event.clientX,
10877
},
10978
)
11079
}}
11180
icon={icon}
11281
>
113-
{Object.entries(file.children || {}).map(([name, file]) => {
114-
return buildTreeItems(name, file)
115-
})}
82+
{typeof content === "object" ? (
83+
Object.keys(content)
84+
.sort(sortFileTree(content))
85+
.map((filename) => {
86+
const child = content[filename]
87+
return buildTreeItems(filename, child, currentPath)
88+
})
89+
) : (
90+
<></>
91+
)}
11692
</TreeItem>
11793
)
11894
}
@@ -124,9 +100,12 @@ export const FileTree: FC<{
124100
aria-label="Files"
125101
className={styles.fileTree}
126102
>
127-
{Object.entries(fileTree).map(([name, file]) => {
128-
return buildTreeItems(name, file)
129-
})}
103+
{Object.keys(fileTree)
104+
.sort(sortFileTree(fileTree))
105+
.map((filename) => {
106+
const child = fileTree[filename]
107+
return buildTreeItems(filename, child)
108+
})}
130109

131110
<Menu
132111
onClose={() => setContextMenu(undefined)}
@@ -154,7 +133,7 @@ export const FileTree: FC<{
154133
if (!contextMenu) {
155134
return
156135
}
157-
onRename(contextMenu.file)
136+
onRename(contextMenu.path)
158137
setContextMenu(undefined)
159138
}}
160139
>
@@ -165,7 +144,7 @@ export const FileTree: FC<{
165144
if (!contextMenu) {
166145
return
167146
}
168-
onDelete(contextMenu.file)
147+
onDelete(contextMenu.path)
169148
setContextMenu(undefined)
170149
}}
171150
>

site/src/components/TemplateVersionEditor/TemplateVersionEditor.stories.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Story } from "@storybook/react"
22
import {
33
MockTemplate,
44
MockTemplateVersion,
5-
MockTemplateVersionFiles,
5+
MockTemplateVersionFileTree,
66
MockWorkspaceBuildLogs,
77
MockWorkspaceResource,
88
MockWorkspaceResource2,
@@ -29,15 +29,15 @@ export const Example = Template.bind({})
2929
Example.args = {
3030
template: MockTemplate,
3131
templateVersion: MockTemplateVersion,
32-
initialFiles: MockTemplateVersionFiles,
32+
defaultFileTree: MockTemplateVersionFileTree,
3333
}
3434

3535
export const Logs = Template.bind({})
3636

3737
Logs.args = {
3838
template: MockTemplate,
3939
templateVersion: MockTemplateVersion,
40-
initialFiles: MockTemplateVersionFiles,
40+
defaultFileTree: MockTemplateVersionFileTree,
4141
buildLogs: MockWorkspaceBuildLogs,
4242
}
4343

@@ -46,7 +46,7 @@ export const Resources = Template.bind({})
4646
Resources.args = {
4747
template: MockTemplate,
4848
templateVersion: MockTemplateVersion,
49-
initialFiles: MockTemplateVersionFiles,
49+
defaultFileTree: MockTemplateVersionFileTree,
5050
buildLogs: MockWorkspaceBuildLogs,
5151
resources: [
5252
MockWorkspaceResource,

0 commit comments

Comments
 (0)