feat: Display and edit template icons in the UI (#3598)

This commit is contained in:
Bruno Quaresma
2022-08-19 13:09:07 -03:00
committed by GitHub
parent e938e8577f
commit c14a4b92ed
4 changed files with 59 additions and 1 deletions
@@ -1,3 +1,5 @@
import InputAdornment from "@material-ui/core/InputAdornment"
import { makeStyles } from "@material-ui/core/styles"
import TextField from "@material-ui/core/TextField"
import { Template, UpdateTemplateMeta } from "api/typesGenerated"
import { FormFooter } from "components/FormFooter/FormFooter"
@@ -10,6 +12,7 @@ import * as Yup from "yup"
export const Language = {
nameLabel: "Name",
descriptionLabel: "Description",
iconLabel: "Icon",
maxTtlLabel: "Max TTL",
// This is the same from the CLI on https://github.com/coder/coder/blob/546157b63ef9204658acf58cb653aa9936b70c49/cli/templateedit.go#L59
maxTtlHelperText: "Edit the template maximum time before shutdown in milliseconds",
@@ -45,6 +48,7 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
name: template.name,
description: template.description,
max_ttl_ms: template.max_ttl_ms,
icon: template.icon,
},
validationSchema,
onSubmit: (data) => {
@@ -53,6 +57,8 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
initialTouched,
})
const getFieldHelpers = getFormHelpersWithError<UpdateTemplateMeta>(form, error)
const styles = useStyles()
const hasIcon = form.values.icon && form.values.icon !== ""
return (
<form onSubmit={form.handleSubmit} aria-label={Language.formAriaLabel}>
@@ -77,6 +83,29 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
rows={2}
/>
<TextField
{...getFieldHelpers("icon")}
disabled={isSubmitting}
fullWidth
label={Language.iconLabel}
variant="outlined"
InputProps={{
endAdornment: hasIcon ? (
<InputAdornment position="end">
<img
alt=""
src={form.values.icon}
className={styles.adornment}
// This prevent browser to display the ugly error icon if the
// image path is wrong or user didn't finish typing the url
onError={(e) => (e.currentTarget.style.display = "none")}
onLoad={(e) => (e.currentTarget.style.display = "inline")}
/>
</InputAdornment>
) : undefined,
}}
/>
<TextField
{...getFieldHelpers("max_ttl_ms")}
helperText={Language.maxTtlHelperText}
@@ -92,3 +121,10 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
</form>
)
}
const useStyles = makeStyles((theme) => ({
adornment: {
width: theme.spacing(3),
height: theme.spacing(3),
},
}))
@@ -23,6 +23,7 @@ const fillAndSubmitForm = async ({
name,
description,
max_ttl_ms,
icon,
}: Omit<Required<UpdateTemplateMeta>, "min_autostart_interval_ms">) => {
const nameField = await screen.findByLabelText(FormLanguage.nameLabel)
await userEvent.clear(nameField)
@@ -32,6 +33,10 @@ const fillAndSubmitForm = async ({
await userEvent.clear(descriptionField)
await userEvent.type(descriptionField, description)
const iconField = await screen.findByLabelText(FormLanguage.iconLabel)
await userEvent.clear(iconField)
await userEvent.type(iconField, icon)
const maxTtlField = await screen.findByLabelText(FormLanguage.maxTtlLabel)
await userEvent.clear(maxTtlField)
await userEvent.type(maxTtlField, max_ttl_ms.toString())
@@ -54,7 +59,7 @@ describe("TemplateSettingsPage", () => {
name: "edited-template-name",
description: "Edited description",
max_ttl_ms: 4000,
icon: "/icons/new-icon.png",
icon: "/icon/code.svg",
}
jest.spyOn(API, "updateTemplateMeta").mockResolvedValueOnce({
...MockTemplate,
@@ -17,11 +17,13 @@ AllStates.args = {
{
...MockTemplate,
description: "🚀 Some magical template that does some magical things!",
icon: "/icon/goland.svg",
},
{
...MockTemplate,
workspace_owner_count: 150,
description: "😮 Wow, this one has a bunch of usage!",
icon: "",
},
],
}
@@ -142,6 +142,8 @@ export const TemplatesPageView: FC<TemplatesPageViewProps> = (props) => {
)}
{props.templates?.map((template) => {
const templatePageLink = `/templates/${template.name}`
const hasIcon = template.icon && template.icon !== ""
return (
<TableRow
key={template.id}
@@ -160,6 +162,13 @@ export const TemplatesPageView: FC<TemplatesPageViewProps> = (props) => {
title={template.name}
subtitle={template.description}
highlightTitle
avatar={
hasIcon ? (
<div className={styles.templateIconWrapper}>
<img alt="" src={template.icon} />
</div>
) : undefined
}
/>
</TableCellLink>
@@ -211,4 +220,10 @@ const useStyles = makeStyles((theme) => ({
arrowCell: {
display: "flex",
},
templateIconWrapper: {
// Same size then the avatar component
width: 36,
height: 36,
padding: 2,
},
}))