Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add initial editor
  • Loading branch information
kylecarbs committed Feb 1, 2023
commit 564d40e42062c75cbb65bce09703b0acf878b2f8
2 changes: 2 additions & 0 deletions site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"react-chartjs-2": "4.3.1",
"react-color": "2.19.3",
"react-dom": "18.2.0",
"react-headless-tabs": "^6.0.3",
"react-helmet-async": "1.3.0",
"react-i18next": "12.1.1",
"react-markdown": "8.0.3",
Expand All @@ -75,6 +76,7 @@
"remark-gfm": "3.0.1",
"rollup-plugin-visualizer": "5.9.0",
"sourcemapped-stacktrace": "1.1.11",
"tar-js": "^0.3.0",
"ts-prune": "0.10.3",
"tzdata": "1.0.30",
"ua-parser-js": "1.0.33",
Expand Down
8 changes: 7 additions & 1 deletion site/src/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ const GitAuthPage = lazy(() => import("./pages/GitAuthPage/GitAuthPage"))
const TemplateVersionPage = lazy(
() => import("./pages/TemplateVersionPage/TemplateVersionPage"),
)
const TemplateVersionEditorPage = lazy(
() => import("./pages/TemplateVersionPage/TemplateVersionEditorPage/TemplateVersionEditorPage"),
)
const StarterTemplatesPage = lazy(
() => import("./pages/StarterTemplatesPage/StarterTemplatesPage"),
)
Expand Down Expand Up @@ -154,7 +157,10 @@ export const AppRouter: FC = () => {
<Route path="workspace" element={<CreateWorkspacePage />} />
<Route path="settings" element={<TemplateSettingsPage />} />
<Route path="versions">
<Route path=":version" element={<TemplateVersionPage />} />
<Route path=":version">
<Route index element={<TemplateVersionPage />} />
<Route path="edit" element={<TemplateVersionEditorPage />} />
</Route>
</Route>
</Route>
</Route>
Expand Down
9 changes: 9 additions & 0 deletions site/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,15 @@ export const cancelWorkspaceBuild = async (
return response.data
}

export const cancelTemplateVersionBuild = async (
templateVersionId: TypesGen.TemplateVersion["id"]
): Promise<Types.Message> => {
const response = await axios.patch(
`/api/v2/templateversions/${templateVersionId}/cancel`,
)
return response.data
}

export const createUser = async (
user: TypesGen.CreateUserRequest,
): Promise<TypesGen.User> => {
Expand Down
1 change: 0 additions & 1 deletion site/src/components/Dashboard/DashboardLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,5 @@ const useStyles = makeStyles((theme) => ({
},
siteContent: {
flex: 1,
paddingBottom: theme.spacing(10),
},
}))
175 changes: 175 additions & 0 deletions site/src/components/TemplateVersionEditor/FileTree.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import { makeStyles } from "@material-ui/core/styles"
import ChevronRightIcon from "@material-ui/icons/ChevronRight"
import ExpandMoreIcon from "@material-ui/icons/ExpandMore"
import TreeView from "@material-ui/lab/TreeView"
import TreeItem from "@material-ui/lab/TreeItem"

import { FC, useMemo } from "react"
import { TemplateVersionFiles } from "util/templateVersion"

export interface File {
path: string
content?: string
children: Record<string, File>
}

export const FileTree: FC<{
onSelect: (file: File) => void
files: TemplateVersionFiles
activeFile?: File
}> = ({ activeFile, files, onSelect }) => {
const styles = useStyles()
const fileTree = useMemo<Record<string, File>>(() => {
const paths = Object.keys(files)
const roots: Record<string, File> = {}
paths.forEach((path) => {
const pathParts = path.split("/")
const firstPart = pathParts.shift()
if (!firstPart) {
// Not possible!
return
}
let activeFile = roots[firstPart]
if (!activeFile) {
activeFile = {
path: firstPart,
children: {},
}
roots[firstPart] = activeFile
}
while (pathParts.length > 0) {
const pathPart = pathParts.shift()
if (!pathPart) {
continue
}
if (!activeFile.children[pathPart]) {
activeFile.children[pathPart] = {
path: activeFile.path + "/" + pathPart,
children: {},
}
}
activeFile = activeFile.children[pathPart]
}
activeFile.content = files[path]
activeFile.path = path
})
return roots
}, [files])

const buildTreeItems = (name: string, file: File): JSX.Element => {
let icon: JSX.Element | null = null
if (file.path.endsWith(".tf")) {
icon = <FileTypeTerraform />
}
if (file.path.endsWith(".md")) {
icon = <FileTypeMarkdown />
}

return (
<TreeItem
nodeId={file.path}
key={file.path}
label={name}
className={`${styles.fileTreeItem} ${
file.path === activeFile?.path ? "active" : ""
}`}
onClick={() => {
if (file.content) {
onSelect(file)
}
}}
icon={icon}
>
{Object.entries(file.children || {}).map(([name, file]) => {
return buildTreeItems(name, file)
})}
</TreeItem>
)
}

return (
<TreeView
defaultCollapseIcon={<ExpandMoreIcon />}
defaultExpandIcon={<ChevronRightIcon />}
aria-label="Files"
className={styles.fileTree}
>
{Object.entries(fileTree).map(([name, file]) => {
return buildTreeItems(name, file)
})}
</TreeView>
)
}

const useStyles = makeStyles((theme) => ({
fileTree: {},
fileTreeItem: {
overflow: "hidden",
userSelect: "none",

"&:focus": {
"& > .MuiTreeItem-content": {
background: "rgba(255, 255, 255, 0.1)",
},
},
"& > .MuiTreeItem-content:hover": {
background: theme.palette.background.paperLight,
color: theme.palette.text.primary,
},

"& > .MuiTreeItem-content": {
padding: "1px 16px",
color: theme.palette.text.secondary,

"& svg": {
width: 16,
height: 16,
},

"& > .MuiTreeItem-label": {
marginLeft: 4,
fontSize: 14,
color: "inherit",
},
},

"&.active": {
background: theme.palette.background.paper,

"& > .MuiTreeItem-content": {
color: theme.palette.text.primary,
},
},
},
editor: {
flex: 1,
},
preview: {},
}))

const FileTypeTerraform = () => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="#813cf3">
<title>file_type_terraform</title>
<polygon points="12.042 6.858 20.071 11.448 20.071 20.462 12.042 15.868 12.042 6.858 12.042 6.858" />
<polygon points="20.5 20.415 28.459 15.84 28.459 6.887 20.5 11.429 20.5 20.415 20.5 20.415" />
<polygon points="3.541 11.01 11.571 15.599 11.571 6.59 3.541 2 3.541 11.01 3.541 11.01" />
<polygon points="12.042 25.41 20.071 30 20.071 20.957 12.042 16.368 12.042 25.41 12.042 25.41" />
</svg>
)

const FileTypeMarkdown = () => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="#755838">
<rect
x="2.5"
y="7.955"
width="27"
height="16.091"
style={{
fill: "none",
stroke: "#755838",
}}
/>
<polygon points="5.909 20.636 5.909 11.364 8.636 11.364 11.364 14.773 14.091 11.364 16.818 11.364 16.818 20.636 14.091 20.636 14.091 15.318 11.364 18.727 8.636 15.318 8.636 20.636 5.909 20.636" />
<polygon points="22.955 20.636 18.864 16.136 21.591 16.136 21.591 11.364 24.318 11.364 24.318 16.136 27.045 16.136 22.955 20.636" />
</svg>
)
96 changes: 96 additions & 0 deletions site/src/components/TemplateVersionEditor/MonacoEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { useTheme } from "@material-ui/core/styles"
import Editor from "@monaco-editor/react"
import { FC, useEffect, useState } from "react"
import { MONOSPACE_FONT_FAMILY } from "theme/constants"
import { hslToHex } from "util/colors"

export const MonacoEditor: FC<{
value?: string
language?: string
onChange?: (value: string) => void
}> = ({ onChange, value, language }) => {
const theme = useTheme()
const [editor, setEditor] = useState<any>()
useEffect(() => {
if (!editor) {
return
}
const resizeListener = () => {
editor.layout({})
}
window.addEventListener("resize", resizeListener)
return () => {
window.removeEventListener("resize", resizeListener)
}
}, [editor])

return (
<Editor
value={value || ""}
language={language || "hcl"}
theme="vs-dark"
options={{
automaticLayout: true,
fontFamily: MONOSPACE_FONT_FAMILY,
fontSize: 16,
wordWrap: "on",
}}
onChange={(newValue) => {
if (onChange && newValue) {
onChange(newValue)
}
}}
onMount={(editor, monaco) => {
setEditor(editor)

document.fonts.ready
.then(() => {
// Ensures that all text is measured properly.
// If this isn't done, there can be weird selection issues.
monaco.editor.remeasureFonts()
})
.catch(() => {
// Not a biggie!
})

monaco.editor.defineTheme("min", {
base: "vs-dark",
inherit: true,
rules: [
{
token: "comment",
foreground: "6B737C",
},
{
token: "type",
foreground: "B392F0",
},
{
token: "string",
foreground: "9DB1C5",
},
{
token: "variable",
foreground: "BBBBBB",
},
{
token: "identifier",
foreground: "B392F0",
},
{
token: "delimiter.curly",
foreground: "EBB325",
},
],
colors: {
"editor.foreground": hslToHex(theme.palette.text.primary),
"editor.background": hslToHex(theme.palette.background.paper),
},
})
editor.updateOptions({
theme: "min",
})
}}
/>
)
}
Loading