Thanks to visit codestin.com
Credit goes to github.com

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

+36
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

+6-1
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

+2
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

+15
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)