Skip to content

do not merge: prototyping of v2 workspaces of page #219

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 14 commits into from
Closed
Prev Previous commit
Next Next commit
Update timeline and components
  • Loading branch information
bryphe-coder committed Feb 1, 2022
commit 8b73c2c34243934f3344f86378b88e3311f9e8f5
245 changes: 145 additions & 100 deletions site/components/Timeline/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import { Avatar, Box, SvgIcon, Typography } from "@material-ui/core"
import makeStyles from "@material-ui/styles/makeStyles";
import { Avatar, Box, CircularProgress, SvgIcon, Typography } from "@material-ui/core"
import makeStyles from "@material-ui/styles/makeStyles"
import React, { useState } from "react"
import { TerminalOutput } from "./TerminalOutput";
import { TerminalOutput } from "./TerminalOutput"
import StageCompleteIcon from "@material-ui/icons/Done"
import StageExpandedIcon from "@material-ui/icons/KeyboardArrowDown"
import StageErrorIcon from "@material-ui/icons/Warning"

export type BuildLogStatus = "success" | "failed" | "pending"

export interface TimelineEntry {
date: Date
title: string
description?: string
status: BuildLogStatus
buildSummary: string
buildLogs: string[]
}

const today = new Date();
const today = new Date()
const yesterday = new Date()
yesterday.setHours(-24)
const weekAgo = new Date()
Expand All @@ -36,24 +44,40 @@ Pulling image "gcr.io/coder-enterprise-nightlies/coder/envbox:1.27.0-rc.0-145-g8
Successfully pulled image "gcr.io/coder-enterprise-nightlies/coder/envbox:1.27.0-rc.0-145-g8d4ee2e9e-20220131" in 7.423772294s
`.split("\n")

export const mockEntries: TimelineEntry[] = [{
date: weekAgo,
description: "Created Workspace",
title: "Admin",
}, {
date: yesterday,
description: "Modified Workspace",
title: "Admin"
}, {
date: today,
description: "Modified Workspace",
title: "Admin"
}, {
date: today,
description: "Restarted Workspace",
title: "Admin"

}]
export const mockEntries: TimelineEntry[] = [
{
date: weekAgo,
description: "Created Workspace",
title: "Admin",
status: "success",
buildLogs: sampleOutput,
buildSummary: "Succeeded in 82s",
},
{
date: yesterday,
description: "Modified Workspace",
title: "Admin",
status: "failed",
buildLogs: sampleOutput,
buildSummary: "Encountered error after 49s",
},
{
date: today,
description: "Modified Workspace",
title: "Admin",
status: "pending",
buildLogs: sampleOutput,
buildSummary: "Operation in progress...",
},
{
date: today,
description: "Restarted Workspace",
title: "Admin",
status: "success",
buildLogs: sampleOutput,
buildSummary: "Succeeded in 15s",
},
]

export interface TimelineEntryProps {
entries: TimelineEntry[]
Expand All @@ -69,42 +93,39 @@ const getDateWithoutTime = (date: Date) => {
}

export const groupByDate = (entries: TimelineEntry[]): Record<string, TimelineEntry[]> => {
const initial: Record<string, TimelineEntry[]> = {};
const initial: Record<string, TimelineEntry[]> = {}
return entries.reduce<Record<string, TimelineEntry[]>>((acc, curr) => {
const dateWithoutTime = getDateWithoutTime(curr.date);
const dateWithoutTime = getDateWithoutTime(curr.date)
const key = dateWithoutTime.getTime().toString()
const currentEntry = acc[key];
const currentEntry = acc[key]
if (currentEntry) {
return {
...acc,
[key]: [...currentEntry, curr]
[key]: [...currentEntry, curr],
}
} else {
return {
...acc,
[key]: [curr]
[key]: [curr],
}
}
}, initial)

}

const formatDate = (date: Date) => {
let formatter = new Intl.DateTimeFormat("en", {
dateStyle: "long"
});
dateStyle: "long",
})
return formatter.format(date)
}

const formatTime = (date: Date) => {
let formatter = new Intl.DateTimeFormat("en", {
timeStyle: "short"
});
timeStyle: "short",
})
return formatter.format(date)
}



export interface EntryProps {
entry: TimelineEntry
}
Expand All @@ -117,110 +138,134 @@ export const Entry: React.FC<EntryProps> = ({ entry }) => {
setExpanded((prev: boolean) => !prev)
}

return <Box display={"flex"} flexDirection={"column"} onClick={toggleExpanded}>
<Box display={"flex"} flexDirection={"row"} justifyContent={"flex-start"} alignItems={"center"}>
<Box display={"flex"} flexDirection={"column"} justifyContent={"flex-start"} alignItems={"center"} mb={"auto"}>
<Avatar>{"A"}</Avatar>
</Box>
<Box m={"0em 1em"} flexDirection={"column"} flex={"1"}>

<Box display={"flex"} flexDirection={"row"} alignItems={"center"}>
<Typography variant={"h6"}>{entry.title}</Typography>
<Typography variant={"caption"} style={{ marginLeft: "1em" }}>{formatTime(entry.date)}</Typography>
return (
<Box display={"flex"} flexDirection={"column"} onClick={toggleExpanded}>
<Box display={"flex"} flexDirection={"row"} justifyContent={"flex-start"} alignItems={"center"}>
<Box display={"flex"} flexDirection={"column"} justifyContent={"flex-start"} alignItems={"center"} mb={"auto"}>
<Avatar>{"A"}</Avatar>
</Box>
<Typography variant={"body2"}>{entry.description}</Typography>
<Box>
<BuildLog summary={"testing"} status={"success"} expanded={expanded} onToggleClicked={toggleExpanded} />
<Box m={"0em 1em"} flexDirection={"column"} flex={"1"}>
<Box display={"flex"} flexDirection={"row"} alignItems={"center"}>
<Typography variant={"h6"}>{entry.title}</Typography>
<Typography variant={"caption"} style={{ marginLeft: "1em" }}>
{formatTime(entry.date)}
</Typography>
</Box>
<Typography variant={"body2"}>{entry.description}</Typography>
<Box>
<BuildLog
summary={entry.buildSummary}
status={entry.status}
expanded={expanded}
onToggleClicked={toggleExpanded}
/>
</Box>
</Box>
</Box>
</Box>

</Box >
)
}

export const useEntryStyles = makeStyles((theme) => ({

}))

export type BuildLogStatus = "success" | "failure" | "pending"
export const useEntryStyles = makeStyles((theme) => ({}))

export interface BuildLogProps {
summary: string
status: BuildLogStatus
expanded?: boolean
}

const STATUS_ICON_SIZE = 18
const LOADING_SPINNER_SIZE = 14
export const BuildLog: React.FC<BuildLogProps> = ({ summary, status, expanded }) => {
const styles = useBuildLogStyles(status)()
let icon: JSX.Element
if (status === "failed") {
icon = <StageErrorIcon className={`${styles.statusIcon} ${styles.statusIconError}`} />
} else if (status === "pending") {
icon = <CircularProgress size={LOADING_SPINNER_SIZE} />
} else {
icon = <StageCompleteIcon className={`${styles.statusIcon} ${styles.statusIconSuccess}`} />
}

return <div className={styles.container}>
<button className={styles.collapseButton}>
<Box m={"0.25em 0em"}>
<Typography variant={"caption"}>{summary}</Typography>
{expanded && <TerminalOutput output={sampleOutput} />}
</Box>
</button>
</div>

return (
<div className={styles.container}>
<button className={styles.collapseButton}>
<Box m={"0.25em 0em"} display={"flex"} flexDirection={"row"} alignItems={"center"}>
<Typography variant={"caption"}>{summary}</Typography>
<Box m={"0.25em"}>{icon}</Box>
</Box>
</button>
{expanded && <TerminalOutput output={sampleOutput} />}
</div>
)
}

const useBuildLogStyles = (status: BuildLogStatus) => makeStyles((theme) => ({
container: {
borderLeft: `2px solid ${status === "failure" ? theme.palette.error.main : theme.palette.info.main}`,
margin: "1em 0em",
},
collapseButton: {
color: "inherit",
textAlign: "left",
width: "100%",
background: "none",
border: 0,
alignItems: "center",
borderRadius: theme.spacing(0.5),
cursor: "pointer",
"&:disabled": {
const useBuildLogStyles = (status: BuildLogStatus) =>
makeStyles((theme) => ({
container: {
borderLeft: `2px solid ${theme.palette.info.main}`,
margin: "1em 0em",
},
collapseButton: {
color: "inherit",
cursor: "initial",
textAlign: "left",
width: "100%",
background: "none",
border: 0,
alignItems: "center",
borderRadius: theme.spacing(0.5),
cursor: "pointer",
"&:disabled": {
color: "inherit",
cursor: "initial",
},
"&:hover:not(:disabled)": {
backgroundColor: theme.palette.type === "dark" ? theme.palette.grey[800] : theme.palette.grey[100],
},
},
"&:hover:not(:disabled)": {
backgroundColor: theme.palette.type === "dark" ? theme.palette.grey[800] : theme.palette.grey[100],
statusIcon: {
width: STATUS_ICON_SIZE,
height: STATUS_ICON_SIZE,
color: theme.palette.text.secondary,
},
},
}))
statusIconError: {
color: theme.palette.error.main,
},
statusIconSuccess: {
color: theme.palette.success.main,
},
}))

export const Timeline: React.FC = () => {
const styles = useStyles()

const entries = mockEntries
const groupedByDate = groupByDate(entries)
const allDates = Object.keys(groupedByDate);
const allDates = Object.keys(groupedByDate)
const sortedDates = allDates.sort((a, b) => b.localeCompare(a))

const days = sortedDates.map((date) => {

const entriesForDay = groupedByDate[date];
const entriesForDay = groupedByDate[date]

const entryElements = entriesForDay.map((entry) => <Entry entry={entry} isExpanded={false} />)


return <div className={styles.root}>
<Typography className={styles.header} variant="caption" color="textSecondary">{formatDate(new Date(Number.parseInt(date)))}</Typography>
{entryElements}

</div>
return (
<div className={styles.root}>
<Typography className={styles.header} variant="caption" color="textSecondary">
{formatDate(new Date(Number.parseInt(date)))}
</Typography>
{entryElements}
</div>
)
})

return <div className={styles.root}>
{days}
</div>

return <div className={styles.root}>{days}</div>
}

export const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
width: "100%",
flexDirection: "column"
flexDirection: "column",
},
container: {
display: "flex",
Expand All @@ -231,5 +276,5 @@ export const useStyles = makeStyles((theme) => ({
justifyContent: "center",
alignItems: "center",
//textTransform: "uppercase"
}
}))
},
}))
Loading