Skip to content

Commit 3f152cd

Browse files
committed
Add rename and delete
1 parent cbab7f2 commit 3f152cd

File tree

3 files changed

+235
-34
lines changed

3 files changed

+235
-34
lines changed

site/src/components/TemplateVersionEditor/FileDialog.tsx

Lines changed: 102 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -44,26 +44,111 @@ export const CreateFileDialog: FC<{
4444
description={
4545
<Stack spacing={1}>
4646
<Typography>
47-
Specify the path to a file to be created. This path can contain slashes too!
47+
Specify the path to a file to be created. This path can contain
48+
slashes too!
4849
</Typography>
49-
<TextField
50-
autoFocus
51-
onKeyDown={(event) => {
52-
if (event.key === "Enter") {
53-
handleConfirm()
54-
}
55-
}}
56-
helperText={error}
57-
name="file-path"
58-
autoComplete="off"
59-
id="file-path"
60-
placeholder="main.tf"
61-
value={pathValue}
62-
onChange={handleChange}
63-
label="File Path"
64-
/>
50+
<TextField
51+
autoFocus
52+
onKeyDown={(event) => {
53+
if (event.key === "Enter") {
54+
handleConfirm()
55+
}
56+
}}
57+
helperText={error}
58+
name="file-path"
59+
autoComplete="off"
60+
id="file-path"
61+
placeholder="main.tf"
62+
value={pathValue}
63+
onChange={handleChange}
64+
label="File Path"
65+
/>
6566
</Stack>
67+
}
68+
/>
69+
)
70+
}
71+
72+
export const DeleteFileDialog: FC<{
73+
onClose: () => void
74+
onConfirm: () => void
75+
open: boolean
76+
filename: string
77+
}> = ({ onClose, onConfirm, open, filename }) => {
78+
return (
79+
<ConfirmDialog
80+
type="delete"
81+
onClose={onClose}
82+
open={open}
83+
onConfirm={onConfirm}
84+
title="Delete File"
85+
description={`Are you sure you want to delete "${filename}"?`}
86+
/>
87+
)
88+
}
89+
90+
export const RenameFileDialog: FC<{
91+
onClose: () => void
92+
onConfirm: (filename: string) => void
93+
checkExists: (path: string) => boolean
94+
open: boolean
95+
filename: string
96+
}> = ({ checkExists, onClose, onConfirm, open, filename }) => {
97+
const [pathValue, setPathValue] = useState(filename)
98+
const [error, setError] = useState("")
99+
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
100+
setPathValue(event.target.value)
101+
}
102+
const handleConfirm = () => {
103+
if (pathValue === "") {
104+
setError("You must enter a path!")
105+
return
106+
}
107+
if (checkExists(pathValue)) {
108+
setError("File already exists")
109+
return
110+
}
111+
onConfirm(pathValue)
112+
setPathValue("")
113+
}
66114

115+
return (
116+
<ConfirmDialog
117+
open={open}
118+
onClose={() => {
119+
onClose()
120+
setPathValue("")
121+
}}
122+
onConfirm={handleConfirm}
123+
hideCancel={false}
124+
type="success"
125+
cancelText="Cancel"
126+
confirmText="Create"
127+
title="Rename File"
128+
description={
129+
<Stack spacing={1}>
130+
<Typography>
131+
Rename {`"${filename}"`} to something else. This path can contain
132+
slashes too!
133+
</Typography>
134+
<TextField
135+
autoFocus
136+
onKeyDown={(event) => {
137+
if (event.key === "Enter") {
138+
handleConfirm()
139+
}
140+
}}
141+
helperText={error}
142+
name="file-path"
143+
autoComplete="off"
144+
id="file-path"
145+
placeholder="main.tf"
146+
defaultValue={filename}
147+
value={pathValue}
148+
onChange={handleChange}
149+
label="File Path"
150+
/>
151+
</Stack>
67152
}
68153
/>
69154
)

site/src/components/TemplateVersionEditor/FileTree.tsx

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import ChevronRightIcon from "@material-ui/icons/ChevronRight"
33
import ExpandMoreIcon from "@material-ui/icons/ExpandMore"
44
import TreeView from "@material-ui/lab/TreeView"
55
import TreeItem from "@material-ui/lab/TreeItem"
6-
7-
import { FC, useMemo } from "react"
6+
import Menu from "@material-ui/core/Menu"
7+
import MenuItem from "@material-ui/core/MenuItem"
8+
import { FC, useMemo, useState } from "react"
89
import { TemplateVersionFiles } from "util/templateVersion"
910

1011
export interface File {
@@ -15,9 +16,11 @@ export interface File {
1516

1617
export const FileTree: FC<{
1718
onSelect: (file: File) => void
19+
onDelete: (file: File) => void
20+
onRename: (file: File) => void
1821
files: TemplateVersionFiles
1922
activeFile?: File
20-
}> = ({ activeFile, files, onSelect }) => {
23+
}> = ({ activeFile, files, onDelete, onRename, onSelect }) => {
2124
const styles = useStyles()
2225
const fileTree = useMemo<Record<string, File>>(() => {
2326
const paths = Object.keys(files)
@@ -55,6 +58,14 @@ export const FileTree: FC<{
5558
})
5659
return roots
5760
}, [files])
61+
const [contextMenu, setContextMenu] = useState<
62+
| {
63+
file: File
64+
clientX: number
65+
clientY: number
66+
}
67+
| undefined
68+
>()
5869

5970
const buildTreeItems = (name: string, file: File): JSX.Element => {
6071
let icon: JSX.Element | null = null
@@ -81,6 +92,21 @@ export const FileTree: FC<{
8192
onSelect(file)
8293
}
8394
}}
95+
onContextMenu={(event) => {
96+
event.preventDefault()
97+
if (!file.content) {
98+
return
99+
}
100+
setContextMenu(
101+
contextMenu
102+
? undefined
103+
: {
104+
file: file,
105+
clientY: event.clientY,
106+
clientX: event.clientX,
107+
},
108+
)
109+
}}
84110
icon={icon}
85111
>
86112
{Object.entries(file.children || {}).map(([name, file]) => {
@@ -100,6 +126,47 @@ export const FileTree: FC<{
100126
{Object.entries(fileTree).map(([name, file]) => {
101127
return buildTreeItems(name, file)
102128
})}
129+
130+
<Menu
131+
onClose={() => setContextMenu(undefined)}
132+
open={Boolean(contextMenu)}
133+
anchorReference="anchorPosition"
134+
anchorPosition={contextMenu ? {
135+
top: contextMenu.clientY,
136+
left: contextMenu.clientX,
137+
} : undefined}
138+
anchorOrigin={{
139+
vertical: 'top',
140+
horizontal: 'left',
141+
}}
142+
transformOrigin={{
143+
vertical: 'top',
144+
horizontal: 'left',
145+
}}
146+
>
147+
<MenuItem
148+
onClick={() => {
149+
if (!contextMenu) {
150+
return
151+
}
152+
onRename(contextMenu.file)
153+
setContextMenu(undefined)
154+
}}
155+
>
156+
Rename...
157+
</MenuItem>
158+
<MenuItem
159+
onClick={() => {
160+
if (!contextMenu) {
161+
return
162+
}
163+
onDelete(contextMenu.file)
164+
setContextMenu(undefined)
165+
}}
166+
>
167+
Delete Permanently
168+
</MenuItem>
169+
</Menu>
103170
</TreeView>
104171
)
105172
}

site/src/components/TemplateVersionEditor/TemplateVersionEditor.tsx

Lines changed: 63 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ import { WorkspaceBuildLogs } from "components/WorkspaceBuildLogs/WorkspaceBuild
1818
import { FC, useCallback, useEffect, useState } from "react"
1919
import { navHeight } from "theme/constants"
2020
import { TemplateVersionFiles } from "util/templateVersion"
21-
import { CreateFileDialog } from "./FileDialog"
21+
import {
22+
CreateFileDialog,
23+
DeleteFileDialog,
24+
RenameFileDialog,
25+
} from "./FileDialog"
2226
import { FileTree } from "./FileTree"
2327
import { MonacoEditor } from "./MonacoEditor"
2428

@@ -59,6 +63,8 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
5963
const [selectedTab, setSelectedTab] = useState(0)
6064
const [files, setFiles] = useState(initialFiles)
6165
const [createFileOpen, setCreateFileOpen] = useState(false)
66+
const [deleteFileOpen, setDeleteFileOpen] = useState<File>()
67+
const [renameFileOpen, setRenameFileOpen] = useState<File>()
6268
const [activeFile, setActiveFile] = useState<File | undefined>(() => {
6369
const fileKeys = Object.keys(initialFiles)
6470
for (let i = 0; i < fileKeys.length; i++) {
@@ -188,29 +194,72 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
188194
setCreateFileOpen(false)
189195
}}
190196
/>
197+
<DeleteFileDialog
198+
onConfirm={() => {
199+
if (!deleteFileOpen) {
200+
throw new Error("delete file must be set")
201+
}
202+
const deleted = { ...files }
203+
delete deleted[deleteFileOpen.path]
204+
setFiles(deleted)
205+
setDeleteFileOpen(undefined)
206+
if (activeFile?.path === deleteFileOpen.path) {
207+
setActiveFile(undefined)
208+
}
209+
}}
210+
open={Boolean(deleteFileOpen)}
211+
onClose={() => setDeleteFileOpen(undefined)}
212+
filename={deleteFileOpen?.path || ""}
213+
/>
214+
<RenameFileDialog
215+
open={Boolean(renameFileOpen)}
216+
onClose={() => {
217+
setRenameFileOpen(undefined)
218+
}}
219+
filename={renameFileOpen?.path || ""}
220+
checkExists={(path) => Boolean(files[path])}
221+
onConfirm={(newPath) => {
222+
if (!renameFileOpen) {
223+
return
224+
}
225+
const renamed = { ...files }
226+
renamed[newPath] = renamed[renameFileOpen.path]
227+
delete renamed[renameFileOpen.path]
228+
setFiles(renamed)
229+
renameFileOpen.path = newPath
230+
setActiveFile(renameFileOpen)
231+
setRenameFileOpen(undefined)
232+
}}
233+
/>
191234
</div>
192235
<FileTree
193236
files={files}
237+
onDelete={(file) => setDeleteFileOpen(file)}
194238
onSelect={(file) => setActiveFile(file)}
239+
onRename={(file) => setRenameFileOpen(file)}
195240
activeFile={activeFile}
196241
/>
197242
</div>
198243

199244
<div className={styles.editorPane}>
200245
<div className={styles.editor}>
201-
<MonacoEditor
202-
value={activeFile?.content}
203-
path={activeFile?.path}
204-
onChange={(value) => {
205-
if (!activeFile) {
206-
return
207-
}
208-
setFiles({
209-
...files,
210-
[activeFile.path]: value,
211-
})
212-
}}
213-
/>
246+
{activeFile ? (
247+
<MonacoEditor
248+
value={activeFile?.content}
249+
path={activeFile?.path}
250+
onChange={(value) => {
251+
if (!activeFile) {
252+
return
253+
}
254+
setFiles({
255+
...files,
256+
[activeFile.path]: value,
257+
})
258+
}}
259+
/>
260+
) : (
261+
<div>No file opened</div>
262+
)}
214263
</div>
215264

216265
<div className={styles.panelWrapper}>

0 commit comments

Comments
 (0)