Skip to content

Commit 290a31d

Browse files
committed
Pretty up form
1 parent d3a4485 commit 290a31d

File tree

6 files changed

+87
-38
lines changed

6 files changed

+87
-38
lines changed

enterprise/coderd/servicebanner.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ package coderd
22

33
import (
44
"database/sql"
5+
"encoding/hex"
56
"encoding/json"
67
"errors"
78
"fmt"
89
"net/http"
910

11+
"golang.org/x/xerrors"
12+
1013
"github.com/coder/coder/coderd/httpapi"
1114
"github.com/coder/coder/coderd/rbac"
1215
"github.com/coder/coder/codersdk"
@@ -53,6 +56,17 @@ func (api *API) serviceBanner(rw http.ResponseWriter, r *http.Request) {
5356
httpapi.Write(r.Context(), rw, http.StatusOK, serviceBanner)
5457
}
5558

59+
func validateHexColor(color string) error {
60+
if len(color) != 7 {
61+
return xerrors.New("expected 7 characters")
62+
}
63+
if color[0] != '#' {
64+
return xerrors.New("no # prefix")
65+
}
66+
_, err := hex.DecodeString(color[1:])
67+
return err
68+
}
69+
5670
func (api *API) putServiceBanner(rw http.ResponseWriter, r *http.Request) {
5771
ctx := r.Context()
5872

@@ -68,6 +82,13 @@ func (api *API) putServiceBanner(rw http.ResponseWriter, r *http.Request) {
6882
return
6983
}
7084

85+
if err := validateHexColor(serviceBanner.BackgroundColor); err != nil {
86+
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
87+
Message: fmt.Sprintf("parse color: %+v", err),
88+
})
89+
return
90+
}
91+
7192
serviceBannerJSON, err := json.Marshal(serviceBanner)
7293
if err != nil {
7394
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{

enterprise/coderd/servicebanner_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,15 @@ func TestServiceBanners(t *testing.T) {
5252
wantBanner := sb
5353
wantBanner.Enabled = true
5454
wantBanner.Message = "Hey"
55+
wantBanner.BackgroundColor = "#00FF00"
5556
err = adminClient.SetServiceBanner(ctx, wantBanner)
5657
require.NoError(t, err)
5758
gotBanner, err := adminClient.ServiceBanner(ctx)
5859
require.NoError(t, err)
5960
require.Equal(t, wantBanner, gotBanner)
61+
62+
// But even an admin can't give a bad color
63+
wantBanner.BackgroundColor = "#bad color"
64+
err = adminClient.SetServiceBanner(ctx, wantBanner)
65+
require.Error(t, err)
6066
}

site/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"@material-ui/icons": "4.5.1",
3434
"@material-ui/lab": "4.0.0-alpha.42",
3535
"@testing-library/react-hooks": "8.0.1",
36+
"@types/color-convert": "^2.0.0",
3637
"@types/react-color": "^3.0.6",
3738
"@vitejs/plugin-react": "2.1.0",
3839
"@xstate/inspect": "0.6.5",
@@ -41,6 +42,7 @@
4142
"can-ndjson-stream": "1.0.2",
4243
"chart.js": "3.9.1",
4344
"chartjs-adapter-date-fns": "2.0.0",
45+
"color-convert": "^2.0.1",
4446
"cron-parser": "4.7.0",
4547
"cronstrue": "2.14.0",
4648
"date-fns": "2.29.3",

site/src/components/ServiceBanner/ServiceBannerView.tsx

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { makeStyles } from "@material-ui/core/styles"
22
import { Pill } from "components/Pill/Pill"
33
import ReactMarkdown from "react-markdown"
44
import { colors } from "theme/colors"
5+
import { hex } from "color-convert"
56

67
export interface ServiceBannerViewProps {
78
message: string
@@ -15,21 +16,31 @@ export const ServiceBannerView: React.FC<ServiceBannerViewProps> = ({
1516
preview,
1617
}) => {
1718
const styles = useStyles()
19+
// We don't want anything funky like an image or a heading in the service
20+
// banner.
1821
const markdownElementsAllowed = [
1922
"text",
2023
"a",
24+
"pre",
25+
"ul",
2126
"strong",
22-
"delete",
2327
"emphasis",
28+
"italic",
2429
"link",
30+
"em",
2531
]
2632
return (
2733
<div
2834
className={`${styles.container}`}
2935
style={{ backgroundColor: backgroundColor }}
3036
>
31-
{preview && <Pill text="Preview" type="primary" lightBorder />}
32-
<div className={styles.centerContent}>
37+
{preview && <Pill text="Preview" type="info" lightBorder />}
38+
<div
39+
className={styles.centerContent}
40+
style={{
41+
color: readableForegroundColor(backgroundColor),
42+
}}
43+
>
3344
<ReactMarkdown
3445
allowedElements={markdownElementsAllowed}
3546
linkTarget="_blank"
@@ -48,7 +59,6 @@ const useStyles = makeStyles((theme) => ({
4859
backgroundColor: theme.palette.warning.main,
4960
display: "flex",
5061
alignItems: "center",
51-
5262
"&.error": {
5363
backgroundColor: colors.red[12],
5464
},
@@ -59,21 +69,14 @@ const useStyles = makeStyles((theme) => ({
5969
centerContent: {
6070
marginRight: "auto",
6171
marginLeft: "auto",
62-
// Automatically pick high-contrast foreground text.
63-
// "difference" is the most correct way of implementing this
64-
// but "exclusion" looks prettier for most colors.
65-
mixBlendMode: "exclusion",
66-
},
67-
link: {
68-
color: "inherit",
69-
textDecoration: "none",
70-
fontWeight: "bold",
71-
},
72-
list: {
73-
padding: theme.spacing(1),
74-
margin: 0,
75-
},
76-
listItem: {
77-
margin: theme.spacing(0.5),
72+
fontWeight: 400,
7873
},
7974
}))
75+
76+
const readableForegroundColor = (backgroundColor: string): string => {
77+
const [_, __, lum] = hex.hsl(backgroundColor)
78+
if (lum > 50) {
79+
return "black"
80+
}
81+
return "white"
82+
}

site/src/pages/DeploySettingsPage/ServiceBannerSettingsPage.tsx

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ import FormControlLabel from "@material-ui/core/FormControlLabel"
2222
import Switch from "@material-ui/core/Switch"
2323
import { BlockPicker } from "react-color"
2424
import { useTheme } from "@material-ui/core/styles"
25-
26-
import { colors } from "theme/colors"
25+
import FormHelperText from "@material-ui/core/FormHelperText"
2726

2827
export const Language = {
2928
messageLabel: "Message",
@@ -36,7 +35,6 @@ export interface ServiceBannerFormValues {
3635
message?: string
3736
backgroundColor?: string
3837
enabled?: boolean
39-
preview: boolean
4038
}
4139

4240
// TODO:
@@ -62,13 +60,13 @@ const ServiceBannerSettingsPage: React.FC = () => {
6260
entitlementsState.context.entitlements.features[FeatureNames.ServiceBanners]
6361
.entitlement !== "not_entitled"
6462

65-
const onSubmit = (values: ServiceBannerFormValues) => {
63+
const setBanner = (values: ServiceBannerFormValues, preview: boolean) => {
6664
const newBanner = {
6765
message: values.message,
6866
enabled: true,
6967
background_color: values.backgroundColor,
7068
}
71-
if (values.preview) {
69+
if (preview) {
7270
serviceBannerSend({
7371
type: "SET_PREVIEW_BANNER",
7472
serviceBanner: newBanner,
@@ -85,14 +83,13 @@ const ServiceBannerSettingsPage: React.FC = () => {
8583
message: serviceBanner.message,
8684
enabled: serviceBanner.enabled,
8785
backgroundColor: serviceBanner.background_color,
88-
preview: false,
8986
}
9087

9188
const form: FormikContextType<ServiceBannerFormValues> =
9289
useFormik<ServiceBannerFormValues>({
9390
initialValues,
9491
validationSchema,
95-
onSubmit,
92+
onSubmit: (values) => setBanner(values, false),
9693
})
9794
const getFieldHelpers = getFormHelpers<ServiceBannerFormValues>(form)
9895

@@ -125,14 +122,19 @@ const ServiceBannerSettingsPage: React.FC = () => {
125122
control={<Switch {...getFieldHelpers("enabled")} color="primary" />}
126123
label="Enabled"
127124
/>
128-
<TextField
129-
fullWidth
130-
{...getFieldHelpers("message")}
131-
label={Language.messageLabel}
132-
variant="outlined"
133-
/>
125+
<Stack spacing={0}>
126+
<TextField
127+
fullWidth
128+
{...getFieldHelpers("message")}
129+
label={Language.messageLabel}
130+
variant="outlined"
131+
/>
132+
<FormHelperText>
133+
Markdown bold, italics, and links are supported.
134+
</FormHelperText>
135+
</Stack>
134136

135-
<Stack>
137+
<Stack spacing={0}>
136138
<h3>Background Color</h3>
137139
<BlockPicker
138140
color={backgroundColor}
@@ -141,6 +143,7 @@ const ServiceBannerSettingsPage: React.FC = () => {
141143
form.setFieldValue("backgroundColor", color.hex)
142144
}}
143145
triangle="hide"
146+
colors={["#004852", "#D65D0F", "#4CD473", "#D94A5D", "#00BDD6"]}
144147
styles={{
145148
default: {
146149
input: {
@@ -151,6 +154,9 @@ const ServiceBannerSettingsPage: React.FC = () => {
151154
backgroundColor: "black",
152155
color: "white",
153156
},
157+
card: {
158+
backgroundColor: "black",
159+
},
154160
},
155161
}}
156162
/>
@@ -162,8 +168,7 @@ const ServiceBannerSettingsPage: React.FC = () => {
162168
// aria-disabled={!editable}
163169
// disabled={!editable}
164170
onClick={() => {
165-
form.setFieldValue("preview", true)
166-
onSubmit(form.values)
171+
setBanner(form.values, true)
167172
}}
168173
variant="contained"
169174
>
@@ -174,8 +179,7 @@ const ServiceBannerSettingsPage: React.FC = () => {
174179
// aria-disabled={!editable}
175180
// disabled={!editable}
176181
onClick={() => {
177-
form.setFieldValue("preview", false)
178-
onSubmit(form.values)
182+
setBanner(form.values, false)
179183
}}
180184
variant="contained"
181185
>
@@ -187,6 +191,7 @@ const ServiceBannerSettingsPage: React.FC = () => {
187191
</>
188192
)
189193
}
194+
190195
const useStyles = makeStyles(() => ({
191196
form: {
192197
maxWidth: "500px",

site/yarn.lock

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2902,6 +2902,18 @@
29022902
"@types/connect" "*"
29032903
"@types/node" "*"
29042904

2905+
"@types/color-convert@^2.0.0":
2906+
version "2.0.0"
2907+
resolved "https://registry.yarnpkg.com/@types/color-convert/-/color-convert-2.0.0.tgz#8f5ee6b9e863dcbee5703f5a517ffb13d3ea4e22"
2908+
integrity sha512-m7GG7IKKGuJUXvkZ1qqG3ChccdIM/qBBo913z+Xft0nKCX4hAU/IxKwZBU4cpRZ7GS5kV4vOblUkILtSShCPXQ==
2909+
dependencies:
2910+
"@types/color-name" "*"
2911+
2912+
"@types/color-name@*":
2913+
version "1.1.1"
2914+
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
2915+
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
2916+
29052917
"@types/connect@*":
29062918
version "3.4.35"
29072919
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1"

0 commit comments

Comments
 (0)