Skip to content

Commit c14a4b9

Browse files
feat: Display and edit template icons in the UI (#3598)
1 parent e938e85 commit c14a4b9

File tree

4 files changed

+59
-1
lines changed

4 files changed

+59
-1
lines changed

site/src/pages/TemplateSettingsPage/TemplateSettingsForm.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import InputAdornment from "@material-ui/core/InputAdornment"
2+
import { makeStyles } from "@material-ui/core/styles"
13
import TextField from "@material-ui/core/TextField"
24
import { Template, UpdateTemplateMeta } from "api/typesGenerated"
35
import { FormFooter } from "components/FormFooter/FormFooter"
@@ -10,6 +12,7 @@ import * as Yup from "yup"
1012
export const Language = {
1113
nameLabel: "Name",
1214
descriptionLabel: "Description",
15+
iconLabel: "Icon",
1316
maxTtlLabel: "Max TTL",
1417
// This is the same from the CLI on https://github.com/coder/coder/blob/546157b63ef9204658acf58cb653aa9936b70c49/cli/templateedit.go#L59
1518
maxTtlHelperText: "Edit the template maximum time before shutdown in milliseconds",
@@ -45,6 +48,7 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
4548
name: template.name,
4649
description: template.description,
4750
max_ttl_ms: template.max_ttl_ms,
51+
icon: template.icon,
4852
},
4953
validationSchema,
5054
onSubmit: (data) => {
@@ -53,6 +57,8 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
5357
initialTouched,
5458
})
5559
const getFieldHelpers = getFormHelpersWithError<UpdateTemplateMeta>(form, error)
60+
const styles = useStyles()
61+
const hasIcon = form.values.icon && form.values.icon !== ""
5662

5763
return (
5864
<form onSubmit={form.handleSubmit} aria-label={Language.formAriaLabel}>
@@ -77,6 +83,29 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
7783
rows={2}
7884
/>
7985

86+
<TextField
87+
{...getFieldHelpers("icon")}
88+
disabled={isSubmitting}
89+
fullWidth
90+
label={Language.iconLabel}
91+
variant="outlined"
92+
InputProps={{
93+
endAdornment: hasIcon ? (
94+
<InputAdornment position="end">
95+
<img
96+
alt=""
97+
src={form.values.icon}
98+
className={styles.adornment}
99+
// This prevent browser to display the ugly error icon if the
100+
// image path is wrong or user didn't finish typing the url
101+
onError={(e) => (e.currentTarget.style.display = "none")}
102+
onLoad={(e) => (e.currentTarget.style.display = "inline")}
103+
/>
104+
</InputAdornment>
105+
) : undefined,
106+
}}
107+
/>
108+
80109
<TextField
81110
{...getFieldHelpers("max_ttl_ms")}
82111
helperText={Language.maxTtlHelperText}
@@ -92,3 +121,10 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
92121
</form>
93122
)
94123
}
124+
125+
const useStyles = makeStyles((theme) => ({
126+
adornment: {
127+
width: theme.spacing(3),
128+
height: theme.spacing(3),
129+
},
130+
}))

site/src/pages/TemplateSettingsPage/TemplateSettingsPage.test.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const fillAndSubmitForm = async ({
2323
name,
2424
description,
2525
max_ttl_ms,
26+
icon,
2627
}: Omit<Required<UpdateTemplateMeta>, "min_autostart_interval_ms">) => {
2728
const nameField = await screen.findByLabelText(FormLanguage.nameLabel)
2829
await userEvent.clear(nameField)
@@ -32,6 +33,10 @@ const fillAndSubmitForm = async ({
3233
await userEvent.clear(descriptionField)
3334
await userEvent.type(descriptionField, description)
3435

36+
const iconField = await screen.findByLabelText(FormLanguage.iconLabel)
37+
await userEvent.clear(iconField)
38+
await userEvent.type(iconField, icon)
39+
3540
const maxTtlField = await screen.findByLabelText(FormLanguage.maxTtlLabel)
3641
await userEvent.clear(maxTtlField)
3742
await userEvent.type(maxTtlField, max_ttl_ms.toString())
@@ -54,7 +59,7 @@ describe("TemplateSettingsPage", () => {
5459
name: "edited-template-name",
5560
description: "Edited description",
5661
max_ttl_ms: 4000,
57-
icon: "/icons/new-icon.png",
62+
icon: "/icon/code.svg",
5863
}
5964
jest.spyOn(API, "updateTemplateMeta").mockResolvedValueOnce({
6065
...MockTemplate,

site/src/pages/TemplatesPage/TemplatesPageView.stories.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ AllStates.args = {
1717
{
1818
...MockTemplate,
1919
description: "🚀 Some magical template that does some magical things!",
20+
icon: "/icon/goland.svg",
2021
},
2122
{
2223
...MockTemplate,
2324
workspace_owner_count: 150,
2425
description: "😮 Wow, this one has a bunch of usage!",
26+
icon: "",
2527
},
2628
],
2729
}

site/src/pages/TemplatesPage/TemplatesPageView.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ export const TemplatesPageView: FC<TemplatesPageViewProps> = (props) => {
142142
)}
143143
{props.templates?.map((template) => {
144144
const templatePageLink = `/templates/${template.name}`
145+
const hasIcon = template.icon && template.icon !== ""
146+
145147
return (
146148
<TableRow
147149
key={template.id}
@@ -160,6 +162,13 @@ export const TemplatesPageView: FC<TemplatesPageViewProps> = (props) => {
160162
title={template.name}
161163
subtitle={template.description}
162164
highlightTitle
165+
avatar={
166+
hasIcon ? (
167+
<div className={styles.templateIconWrapper}>
168+
<img alt="" src={template.icon} />
169+
</div>
170+
) : undefined
171+
}
163172
/>
164173
</TableCellLink>
165174

@@ -211,4 +220,10 @@ const useStyles = makeStyles((theme) => ({
211220
arrowCell: {
212221
display: "flex",
213222
},
223+
templateIconWrapper: {
224+
// Same size then the avatar component
225+
width: 36,
226+
height: 36,
227+
padding: 2,
228+
},
214229
}))

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy