Skip to content

Commit c428395

Browse files
feat(site): move history into sidebar (#11413)
1 parent f0132b5 commit c428395

18 files changed

+531
-420
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { Interpolation, Theme, useTheme } from "@mui/material/styles";
2+
import { ComponentProps, HTMLAttributes } from "react";
3+
import { Link, LinkProps } from "react-router-dom";
4+
import { TopbarIconButton } from "./Topbar";
5+
6+
export const Sidebar = (props: HTMLAttributes<HTMLDivElement>) => {
7+
const theme = useTheme();
8+
return (
9+
<div
10+
css={{
11+
width: 260,
12+
borderRight: `1px solid ${theme.palette.divider}`,
13+
height: "100%",
14+
overflow: "auto",
15+
flexShrink: 0,
16+
padding: "8px 0",
17+
display: "flex",
18+
flexDirection: "column",
19+
gap: 1,
20+
}}
21+
{...props}
22+
/>
23+
);
24+
};
25+
26+
export const SidebarLink = (props: LinkProps) => {
27+
return <Link css={styles.sidebarItem} {...props} />;
28+
};
29+
30+
export const SidebarItem = (props: HTMLAttributes<HTMLButtonElement>) => {
31+
return <button css={styles.sidebarItem} {...props} />;
32+
};
33+
34+
export const SidebarCaption = (props: HTMLAttributes<HTMLSpanElement>) => {
35+
return (
36+
<span
37+
css={{
38+
fontSize: 10,
39+
lineHeight: 1.2,
40+
padding: "12px 16px",
41+
display: "block",
42+
textTransform: "uppercase",
43+
fontWeight: 500,
44+
letterSpacing: "0.1em",
45+
}}
46+
{...props}
47+
/>
48+
);
49+
};
50+
51+
export const SidebarIconButton = (
52+
props: { isActive: boolean } & ComponentProps<typeof TopbarIconButton>,
53+
) => {
54+
const theme = useTheme();
55+
56+
return (
57+
<TopbarIconButton
58+
css={[
59+
{ opacity: 0.75, "&:hover": { opacity: 1 } },
60+
props.isActive && {
61+
opacity: 1,
62+
position: "relative",
63+
"&::before": {
64+
content: '""',
65+
position: "absolute",
66+
left: 0,
67+
top: 0,
68+
bottom: 0,
69+
width: 2,
70+
backgroundColor: theme.palette.primary.main,
71+
height: "100%",
72+
},
73+
},
74+
]}
75+
{...props}
76+
/>
77+
);
78+
};
79+
80+
const styles = {
81+
sidebarItem: (theme) => ({
82+
fontSize: 13,
83+
lineHeight: 1.2,
84+
color: theme.palette.text.primary,
85+
textDecoration: "none",
86+
padding: "8px 16px",
87+
display: "block",
88+
textAlign: "left",
89+
background: "none",
90+
border: 0,
91+
92+
"&:hover": {
93+
backgroundColor: theme.palette.action.hover,
94+
},
95+
}),
96+
} satisfies Record<string, Interpolation<Theme>>;

site/src/components/Resources/AgentRow.tsx

+3-4
Original file line numberDiff line numberDiff line change
@@ -560,10 +560,11 @@ const styles = {
560560
}),
561561

562562
agentInfo: (theme) => ({
563-
padding: "16px 32px",
563+
padding: "24px 32px",
564564
display: "flex",
565+
gap: 16,
565566
alignItems: "center",
566-
gap: 48,
567+
justifyContent: "space-between",
567568
flexWrap: "wrap",
568569
backgroundColor: theme.palette.background.paper,
569570

@@ -586,9 +587,7 @@ const styles = {
586587
agentButtons: (theme) => ({
587588
display: "flex",
588589
gap: 8,
589-
justifyContent: "flex-end",
590590
flexWrap: "wrap",
591-
flex: 1,
592591

593592
[theme.breakpoints.down("md")]: {
594593
marginLeft: 0,

site/src/components/Resources/Resources.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { WorkspaceAgent, WorkspaceResource } from "api/typesGenerated";
55
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
66
import { Stack } from "../Stack/Stack";
77
import { ResourceCard } from "./ResourceCard";
8+
import { useTheme } from "@mui/material/styles";
89

910
const countAgents = (resource: WorkspaceResource) => {
1011
return resource.agents ? resource.agents.length : 0;
@@ -19,6 +20,7 @@ export const Resources: FC<React.PropsWithChildren<ResourcesProps>> = ({
1920
resources,
2021
agentRow,
2122
}) => {
23+
const theme = useTheme();
2224
const [shouldDisplayHideResources, setShouldDisplayHideResources] =
2325
useState(false);
2426
const displayResources = shouldDisplayHideResources
@@ -30,7 +32,11 @@ export const Resources: FC<React.PropsWithChildren<ResourcesProps>> = ({
3032
const hasHideResources = resources.some((r) => r.hide);
3133

3234
return (
33-
<Stack direction="column" spacing={0}>
35+
<Stack
36+
direction="column"
37+
spacing={0}
38+
css={{ background: theme.palette.background.default }}
39+
>
3440
{displayResources.map((resource) => (
3541
<ResourceCard
3642
key={resource.id}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { Interpolation, Theme, useTheme } from "@emotion/react";
2+
import Skeleton from "@mui/material/Skeleton";
3+
import { WorkspaceBuild } from "api/typesGenerated";
4+
import { BuildIcon } from "components/BuildIcon/BuildIcon";
5+
import { createDayString } from "utils/createDayString";
6+
import {
7+
getDisplayWorkspaceBuildStatus,
8+
getDisplayWorkspaceBuildInitiatedBy,
9+
} from "utils/workspace";
10+
11+
export const WorkspaceBuildData = ({ build }: { build: WorkspaceBuild }) => {
12+
const theme = useTheme();
13+
const statusType = getDisplayWorkspaceBuildStatus(theme, build).type;
14+
15+
return (
16+
<div css={styles.root}>
17+
<BuildIcon
18+
transition={build.transition}
19+
css={{
20+
width: 16,
21+
height: 16,
22+
color: theme.palette[statusType].light,
23+
}}
24+
/>
25+
<div css={{ overflow: "hidden" }}>
26+
<div
27+
css={{
28+
textTransform: "capitalize",
29+
color: theme.palette.text.primary,
30+
textOverflow: "ellipsis",
31+
overflow: "hidden",
32+
whiteSpace: "nowrap",
33+
}}
34+
>
35+
{build.transition} by{" "}
36+
<span css={{ fontWeight: 500 }}>
37+
{getDisplayWorkspaceBuildInitiatedBy(build)}
38+
</span>
39+
</div>
40+
<div
41+
css={{
42+
fontSize: 12,
43+
color: theme.palette.text.secondary,
44+
marginTop: 2,
45+
}}
46+
>
47+
{createDayString(build.created_at)}
48+
</div>
49+
</div>
50+
</div>
51+
);
52+
};
53+
54+
export const WorkspaceBuildDataSkeleton = () => {
55+
return (
56+
<div css={styles.root}>
57+
<Skeleton variant="circular" width={16} height={16} />
58+
<div>
59+
<Skeleton variant="text" width={94} height={16} />
60+
<Skeleton
61+
variant="text"
62+
width={60}
63+
height={14}
64+
css={{ marginTop: 2 }}
65+
/>
66+
</div>
67+
</div>
68+
);
69+
};
70+
71+
const styles = {
72+
root: {
73+
display: "flex",
74+
flexDirection: "row",
75+
alignItems: "center",
76+
gap: 12,
77+
lineHeight: "1.4",
78+
},
79+
} satisfies Record<string, Interpolation<Theme>>;

site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx

+3-8
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import {
5252
TopbarDivider,
5353
TopbarIconButton,
5454
} from "components/FullPageLayout/Topbar";
55+
import { Sidebar } from "components/FullPageLayout/Sidebar";
5556

5657
type Tab = "logs" | "resources" | undefined; // Undefined is to hide the tab
5758

@@ -301,13 +302,7 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
301302
</div>
302303
)}
303304

304-
<div
305-
css={{
306-
width: 240,
307-
borderRight: `1px solid ${theme.palette.divider}`,
308-
flexShrink: 0,
309-
}}
310-
>
305+
<Sidebar>
311306
<div
312307
css={{
313308
height: 42,
@@ -409,7 +404,7 @@ export const TemplateVersionEditor: FC<TemplateVersionEditorProps> = ({
409404
onRename={(file) => setRenameFileOpen(file)}
410405
activePath={activePath}
411406
/>
412-
</div>
407+
</Sidebar>
413408

414409
<div
415410
css={{

site/src/pages/WorkspaceBuildPage/WorkspaceBuildPageView.tsx

+15-84
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,14 @@ import {
1212
} from "components/PageHeader/FullWidthPageHeader";
1313
import { Link } from "react-router-dom";
1414
import { Stats, StatsItem } from "components/Stats/Stats";
15-
import {
16-
displayWorkspaceBuildDuration,
17-
getDisplayWorkspaceBuildInitiatedBy,
18-
getDisplayWorkspaceBuildStatus,
19-
} from "utils/workspace";
15+
import { displayWorkspaceBuildDuration } from "utils/workspace";
2016
import { Sidebar, SidebarCaption, SidebarItem } from "./Sidebar";
21-
import { BuildIcon } from "components/BuildIcon/BuildIcon";
22-
import Skeleton from "@mui/material/Skeleton";
2317
import { Alert } from "components/Alert/Alert";
2418
import { DashboardFullPage } from "components/Dashboard/DashboardLayout";
19+
import {
20+
WorkspaceBuildData,
21+
WorkspaceBuildDataSkeleton,
22+
} from "components/WorkspaceBuild/WorkspaceBuildData";
2523

2624
const sortLogsByCreatedAt = (logs: ProvisionerJobLog[]) => {
2725
return [...logs].sort(
@@ -112,15 +110,20 @@ export const WorkspaceBuildPageView: FC<WorkspaceBuildPageViewProps> = ({
112110
<SidebarCaption>Builds</SidebarCaption>
113111
{!builds &&
114112
Array.from({ length: 15 }, (_, i) => (
115-
<BuildSidebarItemSkeleton key={i} />
113+
<SidebarItem key={i}>
114+
<WorkspaceBuildDataSkeleton />
115+
</SidebarItem>
116116
))}
117117

118118
{builds?.map((build) => (
119-
<BuildSidebarItem
119+
<Link
120120
key={build.id}
121-
build={build}
122-
active={build.build_number === activeBuildNumber}
123-
/>
121+
to={`/@${build.workspace_owner_name}/${build.workspace_name}/builds/${build.build_number}`}
122+
>
123+
<SidebarItem active={build.build_number === activeBuildNumber}>
124+
<WorkspaceBuildData build={build} />
125+
</SidebarItem>
126+
</Link>
124127
))}
125128
</Sidebar>
126129

@@ -167,78 +170,6 @@ export const WorkspaceBuildPageView: FC<WorkspaceBuildPageViewProps> = ({
167170
);
168171
};
169172

170-
interface BuildSidebarItemProps {
171-
build: WorkspaceBuild;
172-
active: boolean;
173-
}
174-
175-
const BuildSidebarItem: FC<BuildSidebarItemProps> = ({ build, active }) => {
176-
const theme = useTheme();
177-
const statusType = getDisplayWorkspaceBuildStatus(theme, build).type;
178-
179-
return (
180-
<Link
181-
key={build.id}
182-
to={`/@${build.workspace_owner_name}/${build.workspace_name}/builds/${build.build_number}`}
183-
>
184-
<SidebarItem active={active}>
185-
<div css={{ display: "flex", alignItems: "start", gap: 8 }}>
186-
<BuildIcon
187-
transition={build.transition}
188-
css={{
189-
width: 16,
190-
height: 16,
191-
color: theme.palette[statusType].light,
192-
}}
193-
/>
194-
<div css={{ overflow: "hidden" }}>
195-
<div
196-
css={{
197-
textTransform: "capitalize",
198-
color: theme.palette.text.primary,
199-
textOverflow: "ellipsis",
200-
overflow: "hidden",
201-
whiteSpace: "nowrap",
202-
}}
203-
>
204-
{build.transition} by{" "}
205-
<strong>{getDisplayWorkspaceBuildInitiatedBy(build)}</strong>
206-
</div>
207-
<div
208-
css={{
209-
fontSize: 12,
210-
color: theme.palette.text.secondary,
211-
marginTop: 2,
212-
}}
213-
>
214-
{displayWorkspaceBuildDuration(build)}
215-
</div>
216-
</div>
217-
</div>
218-
</SidebarItem>
219-
</Link>
220-
);
221-
};
222-
223-
const BuildSidebarItemSkeleton: FC = () => {
224-
return (
225-
<SidebarItem>
226-
<div css={{ display: "flex", alignItems: "start", gap: 8 }}>
227-
<Skeleton variant="circular" width={16} height={16} />
228-
<div>
229-
<Skeleton variant="text" width={94} height={16} />
230-
<Skeleton
231-
variant="text"
232-
width={60}
233-
height={14}
234-
css={{ marginTop: 2 }}
235-
/>
236-
</div>
237-
</div>
238-
</SidebarItem>
239-
);
240-
};
241-
242173
const styles = {
243174
stats: (theme) => ({
244175
padding: 0,

0 commit comments

Comments
 (0)