From ce6f6a984b374b189141116433ced80dfa0c2aae Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Thu, 13 Feb 2020 20:00:03 +0100 Subject: webui: move pages components --- webui/src/pages/bug/Bug.graphql | 13 ++ webui/src/pages/bug/Bug.tsx | 98 ++++++++++++++ webui/src/pages/bug/BugQuery.graphql | 9 ++ webui/src/pages/bug/BugQuery.tsx | 23 ++++ webui/src/pages/bug/CommentForm.graphql | 5 + webui/src/pages/bug/CommentForm.tsx | 146 +++++++++++++++++++++ webui/src/pages/bug/LabelChange.tsx | 50 +++++++ webui/src/pages/bug/LabelChangeFragment.graphql | 12 ++ webui/src/pages/bug/Message.tsx | 79 +++++++++++ webui/src/pages/bug/MessageCommentFragment.graphql | 8 ++ webui/src/pages/bug/MessageCreateFragment.graphql | 8 ++ webui/src/pages/bug/SetStatus.tsx | 32 +++++ webui/src/pages/bug/SetStatusFragment.graphql | 7 + webui/src/pages/bug/SetTitle.tsx | 38 ++++++ webui/src/pages/bug/SetTitleFragment.graphql | 8 ++ webui/src/pages/bug/Timeline.tsx | 49 +++++++ webui/src/pages/bug/TimelineQuery.graphql | 39 ++++++ webui/src/pages/bug/TimelineQuery.tsx | 31 +++++ webui/src/pages/bug/index.tsx | 1 + 19 files changed, 656 insertions(+) create mode 100644 webui/src/pages/bug/Bug.graphql create mode 100644 webui/src/pages/bug/Bug.tsx create mode 100644 webui/src/pages/bug/BugQuery.graphql create mode 100644 webui/src/pages/bug/BugQuery.tsx create mode 100644 webui/src/pages/bug/CommentForm.graphql create mode 100644 webui/src/pages/bug/CommentForm.tsx create mode 100644 webui/src/pages/bug/LabelChange.tsx create mode 100644 webui/src/pages/bug/LabelChangeFragment.graphql create mode 100644 webui/src/pages/bug/Message.tsx create mode 100644 webui/src/pages/bug/MessageCommentFragment.graphql create mode 100644 webui/src/pages/bug/MessageCreateFragment.graphql create mode 100644 webui/src/pages/bug/SetStatus.tsx create mode 100644 webui/src/pages/bug/SetStatusFragment.graphql create mode 100644 webui/src/pages/bug/SetTitle.tsx create mode 100644 webui/src/pages/bug/SetTitleFragment.graphql create mode 100644 webui/src/pages/bug/Timeline.tsx create mode 100644 webui/src/pages/bug/TimelineQuery.graphql create mode 100644 webui/src/pages/bug/TimelineQuery.tsx create mode 100644 webui/src/pages/bug/index.tsx (limited to 'webui/src/pages/bug') diff --git a/webui/src/pages/bug/Bug.graphql b/webui/src/pages/bug/Bug.graphql new file mode 100644 index 00000000..498242c0 --- /dev/null +++ b/webui/src/pages/bug/Bug.graphql @@ -0,0 +1,13 @@ +#import "../components/fragments.graphql" + +fragment Bug on Bug { + id + humanId + status + title + labels { + ...Label + } + createdAt + ...authored +} diff --git a/webui/src/pages/bug/Bug.tsx b/webui/src/pages/bug/Bug.tsx new file mode 100644 index 00000000..998c9528 --- /dev/null +++ b/webui/src/pages/bug/Bug.tsx @@ -0,0 +1,98 @@ +import React from 'react'; + +import Typography from '@material-ui/core/Typography/Typography'; +import { makeStyles } from '@material-ui/core/styles'; + +import Author from 'src/components/Author'; +import Date from 'src/components/Date'; +import Label from 'src/components/Label'; + +import { BugFragment } from './Bug.generated'; +import CommentForm from './CommentForm'; +import TimelineQuery from './TimelineQuery'; + +const useStyles = makeStyles(theme => ({ + main: { + maxWidth: 800, + margin: 'auto', + marginTop: theme.spacing(4), + }, + header: { + marginLeft: theme.spacing(1) + 40, + }, + title: { + ...theme.typography.h5, + }, + id: { + ...theme.typography.subtitle1, + marginLeft: theme.spacing(1), + }, + container: { + display: 'flex', + marginBottom: theme.spacing(1), + }, + timeline: { + flex: 1, + marginTop: theme.spacing(2), + marginRight: theme.spacing(2), + minWidth: 0, + }, + sidebar: { + marginTop: theme.spacing(2), + flex: '0 0 200px', + }, + labelList: { + listStyle: 'none', + padding: 0, + margin: 0, + }, + label: { + marginTop: theme.spacing(1), + marginBottom: theme.spacing(1), + '& > *': { + display: 'block', + }, + }, +})); + +type Props = { + bug: BugFragment; +}; + +function Bug({ bug }: Props) { + const classes = useStyles(); + return ( +
+
+ {bug.title} + {bug.humanId} + + + + {' opened this bug '} + + +
+ +
+
+ +
+
+ Labels +
    + {bug.labels.map(l => ( +
  • +
  • + ))} +
+
+
+ + +
+ ); +} + +export default Bug; diff --git a/webui/src/pages/bug/BugQuery.graphql b/webui/src/pages/bug/BugQuery.graphql new file mode 100644 index 00000000..cdc4723f --- /dev/null +++ b/webui/src/pages/bug/BugQuery.graphql @@ -0,0 +1,9 @@ +#import "./Bug.graphql" + +query GetBug($id: String!) { + repository { + bug(prefix: $id) { + ...Bug + } + } +} diff --git a/webui/src/pages/bug/BugQuery.tsx b/webui/src/pages/bug/BugQuery.tsx new file mode 100644 index 00000000..2a70a2f8 --- /dev/null +++ b/webui/src/pages/bug/BugQuery.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { RouteComponentProps } from 'react-router-dom'; + +import CircularProgress from '@material-ui/core/CircularProgress'; + +import Bug from './Bug'; +import { useGetBugQuery } from './BugQuery.generated'; + +type Props = RouteComponentProps<{ + id: string; +}>; + +const BugQuery: React.FC = ({ match }: Props) => { + const { loading, error, data } = useGetBugQuery({ + variables: { id: match.params.id }, + }); + if (loading) return ; + if (error) return

Error: {error}

; + if (!data?.repository?.bug) return

404.

; + return ; +}; + +export default BugQuery; diff --git a/webui/src/pages/bug/CommentForm.graphql b/webui/src/pages/bug/CommentForm.graphql new file mode 100644 index 00000000..33d21193 --- /dev/null +++ b/webui/src/pages/bug/CommentForm.graphql @@ -0,0 +1,5 @@ +mutation AddComment($input: AddCommentInput!) { + addComment(input: $input) { + operation { id } + } +} diff --git a/webui/src/pages/bug/CommentForm.tsx b/webui/src/pages/bug/CommentForm.tsx new file mode 100644 index 00000000..3724baf0 --- /dev/null +++ b/webui/src/pages/bug/CommentForm.tsx @@ -0,0 +1,146 @@ +import React, { useState, useRef } from 'react'; + +import Button from '@material-ui/core/Button'; +import Paper from '@material-ui/core/Paper'; +import Tab from '@material-ui/core/Tab'; +import Tabs from '@material-ui/core/Tabs'; +import TextField from '@material-ui/core/TextField'; +import { makeStyles, Theme } from '@material-ui/core/styles'; + +import Content from 'src/components/Content'; + +import { useAddCommentMutation } from './CommentForm.generated'; +import { TimelineDocument } from './TimelineQuery.generated'; + +type StyleProps = { loading: boolean }; +const useStyles = makeStyles(theme => ({ + container: { + margin: theme.spacing(2, 0), + padding: theme.spacing(0, 2, 2, 2), + }, + textarea: {}, + tabContent: { + margin: theme.spacing(2, 0), + }, + preview: { + borderBottom: `solid 3px ${theme.palette.grey['200']}`, + minHeight: '5rem', + }, + actions: { + display: 'flex', + justifyContent: 'flex-end', + }, +})); + +type TabPanelProps = { + children: React.ReactNode; + value: number; + index: number; +} & React.HTMLProps; +function TabPanel({ children, value, index, ...props }: TabPanelProps) { + return ( + + ); +} + +const a11yProps = (index: number) => ({ + id: `editor-tab-${index}`, + 'aria-controls': `editor-tabpanel-${index}`, +}); + +type Props = { + bugId: string; +}; + +function CommentForm({ bugId }: Props) { + const [addComment, { loading }] = useAddCommentMutation(); + const [input, setInput] = useState(''); + const [tab, setTab] = useState(0); + const classes = useStyles({ loading }); + const form = useRef(null); + + const submit = () => { + addComment({ + variables: { + input: { + prefix: bugId, + message: input, + }, + }, + refetchQueries: [ + // TODO: update the cache instead of refetching + { + query: TimelineDocument, + variables: { + id: bugId, + first: 100, + }, + }, + ], + awaitRefetchQueries: true, + }).then(() => setInput('')); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + submit(); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + // Submit on cmd/ctrl+enter + if ((e.metaKey || e.altKey) && e.keyCode === 13) { + submit(); + } + }; + + return ( + +
+ setTab(t)}> + + + +
+ + setInput(e.target.value)} + disabled={loading} + /> + + + + +
+
+ +
+
+
+ ); +} + +export default CommentForm; diff --git a/webui/src/pages/bug/LabelChange.tsx b/webui/src/pages/bug/LabelChange.tsx new file mode 100644 index 00000000..764947ee --- /dev/null +++ b/webui/src/pages/bug/LabelChange.tsx @@ -0,0 +1,50 @@ +import React from 'react'; + +import { makeStyles } from '@material-ui/core/styles'; + +import Author from 'src/components/Author'; +import Date from 'src/components/Date'; +import Label from 'src/components/Label'; + +import { LabelChangeFragment } from './LabelChangeFragment.generated'; + +const useStyles = makeStyles(theme => ({ + main: { + ...theme.typography.body1, + marginLeft: theme.spacing(1) + 40, + }, + author: { + fontWeight: 'bold', + }, +})); + +type Props = { + op: LabelChangeFragment; +}; + +function LabelChange({ op }: Props) { + const { added, removed } = op; + const classes = useStyles(); + return ( +
+ + {added.length > 0 && added the } + {added.map((label, index) => ( +
+ ); +} + +export default LabelChange; diff --git a/webui/src/pages/bug/LabelChangeFragment.graphql b/webui/src/pages/bug/LabelChangeFragment.graphql new file mode 100644 index 00000000..82d41235 --- /dev/null +++ b/webui/src/pages/bug/LabelChangeFragment.graphql @@ -0,0 +1,12 @@ +#import "../../components/fragments.graphql" + +fragment LabelChange on LabelChangeTimelineItem { + date + ...authored + added { + ...Label + } + removed { + ...Label + } +} diff --git a/webui/src/pages/bug/Message.tsx b/webui/src/pages/bug/Message.tsx new file mode 100644 index 00000000..ebb42f6b --- /dev/null +++ b/webui/src/pages/bug/Message.tsx @@ -0,0 +1,79 @@ +import React from 'react'; + +import Paper from '@material-ui/core/Paper'; +import { makeStyles } from '@material-ui/core/styles'; + +import Author, { Avatar } from 'src/components/Author'; +import Content from 'src/components/Content'; +import Date from 'src/components/Date'; + +import { AddCommentFragment } from './MessageCommentFragment.generated'; +import { CreateFragment } from './MessageCreateFragment.generated'; + +const useStyles = makeStyles(theme => ({ + author: { + fontWeight: 'bold', + }, + container: { + display: 'flex', + }, + avatar: { + marginTop: 2, + }, + bubble: { + flex: 1, + marginLeft: theme.spacing(1), + minWidth: 0, + }, + header: { + ...theme.typography.body1, + color: '#444', + padding: '0.5rem 1rem', + borderBottom: '1px solid #ddd', + display: 'flex', + }, + title: { + flex: 1, + }, + tag: { + ...theme.typography.button, + color: '#888', + border: '#ddd solid 1px', + padding: '0 0.5rem', + fontSize: '0.75rem', + borderRadius: 2, + marginLeft: '0.5rem', + }, + body: { + ...theme.typography.body2, + padding: '0 1rem', + }, +})); + +type Props = { + op: AddCommentFragment | CreateFragment; +}; + +function Message({ op }: Props) { + const classes = useStyles(); + return ( +
+ + +
+
+ + commented + +
+ {op.edited &&
Edited
} +
+
+ +
+
+
+ ); +} + +export default Message; diff --git a/webui/src/pages/bug/MessageCommentFragment.graphql b/webui/src/pages/bug/MessageCommentFragment.graphql new file mode 100644 index 00000000..00f8342d --- /dev/null +++ b/webui/src/pages/bug/MessageCommentFragment.graphql @@ -0,0 +1,8 @@ +#import "../../components/fragments.graphql" + +fragment AddComment on AddCommentTimelineItem { + createdAt + ...authored + edited + message +} diff --git a/webui/src/pages/bug/MessageCreateFragment.graphql b/webui/src/pages/bug/MessageCreateFragment.graphql new file mode 100644 index 00000000..4cae819d --- /dev/null +++ b/webui/src/pages/bug/MessageCreateFragment.graphql @@ -0,0 +1,8 @@ +#import "../../components/fragments.graphql" + +fragment Create on CreateTimelineItem { + createdAt + ...authored + edited + message +} diff --git a/webui/src/pages/bug/SetStatus.tsx b/webui/src/pages/bug/SetStatus.tsx new file mode 100644 index 00000000..251abf69 --- /dev/null +++ b/webui/src/pages/bug/SetStatus.tsx @@ -0,0 +1,32 @@ +import React from 'react'; + +import { makeStyles } from '@material-ui/core/styles'; + +import Author from 'src/components/Author'; +import Date from 'src/components/Date'; + +import { SetStatusFragment } from './SetStatusFragment.generated'; + +const useStyles = makeStyles(theme => ({ + main: { + ...theme.typography.body1, + marginLeft: theme.spacing(1) + 40, + }, +})); + +type Props = { + op: SetStatusFragment; +}; + +function SetStatus({ op }: Props) { + const classes = useStyles(); + return ( +
+ + {op.status.toLowerCase()} this + +
+ ); +} + +export default SetStatus; diff --git a/webui/src/pages/bug/SetStatusFragment.graphql b/webui/src/pages/bug/SetStatusFragment.graphql new file mode 100644 index 00000000..d8380409 --- /dev/null +++ b/webui/src/pages/bug/SetStatusFragment.graphql @@ -0,0 +1,7 @@ +#import "../../components/fragments.graphql" + +fragment SetStatus on SetStatusTimelineItem { + date + ...authored + status +} diff --git a/webui/src/pages/bug/SetTitle.tsx b/webui/src/pages/bug/SetTitle.tsx new file mode 100644 index 00000000..304fd2e2 --- /dev/null +++ b/webui/src/pages/bug/SetTitle.tsx @@ -0,0 +1,38 @@ +import React from 'react'; + +import { makeStyles } from '@material-ui/core/styles'; + +import Author from 'src/components/Author'; +import Date from 'src/components/Date'; + +import { SetTitleFragment } from './SetTitleFragment.generated'; + +const useStyles = makeStyles(theme => ({ + main: { + ...theme.typography.body1, + marginLeft: theme.spacing(1) + 40, + }, + bold: { + fontWeight: 'bold', + }, +})); + +type Props = { + op: SetTitleFragment; +}; + +function SetTitle({ op }: Props) { + const classes = useStyles(); + return ( +
+ + changed the title from + {op.was} + to + {op.title} + +
+ ); +} + +export default SetTitle; diff --git a/webui/src/pages/bug/SetTitleFragment.graphql b/webui/src/pages/bug/SetTitleFragment.graphql new file mode 100644 index 00000000..2225dfd3 --- /dev/null +++ b/webui/src/pages/bug/SetTitleFragment.graphql @@ -0,0 +1,8 @@ +#import "../../components/fragments.graphql" + +fragment SetTitle on SetTitleTimelineItem { + date + ...authored + title + was +} diff --git a/webui/src/pages/bug/Timeline.tsx b/webui/src/pages/bug/Timeline.tsx new file mode 100644 index 00000000..73c88cdf --- /dev/null +++ b/webui/src/pages/bug/Timeline.tsx @@ -0,0 +1,49 @@ +import React from 'react'; + +import { makeStyles } from '@material-ui/core/styles'; + +import LabelChange from './LabelChange'; +import Message from './Message'; +import SetStatus from './SetStatus'; +import SetTitle from './SetTitle'; +import { TimelineItemFragment } from './TimelineQuery.generated'; + +const useStyles = makeStyles(theme => ({ + main: { + '& > *:not(:last-child)': { + marginBottom: theme.spacing(2), + }, + }, +})); + +type Props = { + ops: Array; +}; + +function Timeline({ ops }: Props) { + const classes = useStyles(); + + return ( +
+ {ops.map((op, index) => { + switch (op.__typename) { + case 'CreateTimelineItem': + return ; + case 'AddCommentTimelineItem': + return ; + case 'LabelChangeTimelineItem': + return ; + case 'SetTitleTimelineItem': + return ; + case 'SetStatusTimelineItem': + return ; + } + + console.warn('unsupported operation type ' + op.__typename); + return null; + })} +
+ ); +} + +export default Timeline; diff --git a/webui/src/pages/bug/TimelineQuery.graphql b/webui/src/pages/bug/TimelineQuery.graphql new file mode 100644 index 00000000..6d78ab7f --- /dev/null +++ b/webui/src/pages/bug/TimelineQuery.graphql @@ -0,0 +1,39 @@ +#import "./MessageCreateFragment.graphql" +#import "./MessageCommentFragment.graphql" +#import "./LabelChangeFragment.graphql" +#import "./SetTitleFragment.graphql" +#import "./SetStatusFragment.graphql" + +query Timeline($id: String!, $first: Int = 10, $after: String) { + repository { + bug(prefix: $id) { + timeline(first: $first, after: $after) { + nodes { + ...TimelineItem + } + pageInfo { + hasNextPage + endCursor + } + } + } + } +} + +fragment TimelineItem on TimelineItem { + ... on LabelChangeTimelineItem { + ...LabelChange + } + ... on SetStatusTimelineItem { + ...SetStatus + } + ... on SetTitleTimelineItem { + ...SetTitle + } + ... on AddCommentTimelineItem { + ...AddComment + } + ... on CreateTimelineItem { + ...Create + } +} diff --git a/webui/src/pages/bug/TimelineQuery.tsx b/webui/src/pages/bug/TimelineQuery.tsx new file mode 100644 index 00000000..74eed52b --- /dev/null +++ b/webui/src/pages/bug/TimelineQuery.tsx @@ -0,0 +1,31 @@ +import React from 'react'; + +import CircularProgress from '@material-ui/core/CircularProgress'; + +import Timeline from './Timeline'; +import { useTimelineQuery } from './TimelineQuery.generated'; + +type Props = { + id: string; +}; + +const TimelineQuery = ({ id }: Props) => { + const { loading, error, data } = useTimelineQuery({ + variables: { + id, + first: 100, + }, + }); + + if (loading) return ; + if (error) return

Error: {error}

; + + const nodes = data?.repository?.bug?.timeline.nodes; + if (!nodes) { + return null; + } + + return ; +}; + +export default TimelineQuery; diff --git a/webui/src/pages/bug/index.tsx b/webui/src/pages/bug/index.tsx new file mode 100644 index 00000000..a3bbcea4 --- /dev/null +++ b/webui/src/pages/bug/index.tsx @@ -0,0 +1 @@ +export { default } from './BugQuery'; -- cgit