-
-
+
+
+ {' opened this bug '}
+
+
-
-
Labels
-
- {bug.labels.map(l => (
- -
-
-
- ))}
-
+
+
+
+
+
+
+
Labels
+
+ {bug.labels.map(l => (
+ -
+
+
+ ))}
+
+
-
-
-);
+
+ );
+}
Bug.fragment = gql`
fragment Bug on Bug {
@@ -97,4 +100,4 @@ Bug.fragment = gql`
}
`;
-export default withStyles(styles)(Bug);
+export default Bug;
diff --git a/webui/src/bug/LabelChange.js b/webui/src/bug/LabelChange.js
index 6301f35f36a8ed73dab9b1fc2137a57665346889..76b6e6e28f0d2b6bf481b3db1416b5bfb2a8c4bc 100644
--- a/webui/src/bug/LabelChange.js
+++ b/webui/src/bug/LabelChange.js
@@ -1,11 +1,11 @@
-import { withStyles } from '@material-ui/core/styles';
+import { makeStyles } from '@material-ui/styles';
import gql from 'graphql-tag';
import React from 'react';
import Author from '../Author';
import Date from '../Date';
import Label from '../Label';
-const styles = theme => ({
+const useStyles = makeStyles(theme => ({
main: {
...theme.typography.body2,
marginLeft: theme.spacing.unit + 40,
@@ -13,10 +13,11 @@ const styles = theme => ({
author: {
fontWeight: 'bold',
},
-});
+}));
-const LabelChange = ({ op, classes }) => {
+function LabelChange({ op }) {
const { added, removed } = op;
+ const classes = useStyles();
return (
@@ -37,7 +38,7 @@ const LabelChange = ({ op, classes }) => {
);
-};
+}
LabelChange.fragment = gql`
fragment LabelChange on TimelineItem {
@@ -54,4 +55,4 @@ LabelChange.fragment = gql`
}
`;
-export default withStyles(styles)(LabelChange);
+export default LabelChange;
diff --git a/webui/src/bug/Message.js b/webui/src/bug/Message.js
index 493de8ee86857018b8d5a09f1fd8d80bd0d69736..ff03944496461095c7af154b00e8a0247c22f5a9 100644
--- a/webui/src/bug/Message.js
+++ b/webui/src/bug/Message.js
@@ -1,4 +1,4 @@
-import { withStyles } from '@material-ui/core/styles';
+import { makeStyles } from '@material-ui/styles';
import Paper from '@material-ui/core/Paper';
import gql from 'graphql-tag';
import React from 'react';
@@ -6,7 +6,7 @@ import Author from '../Author';
import { Avatar } from '../Author';
import Date from '../Date';
-const styles = theme => ({
+const useStyles = makeStyles(theme => ({
author: {
fontWeight: 'bold',
},
@@ -44,24 +44,27 @@ const styles = theme => ({
padding: '1rem',
whiteSpace: 'pre-wrap',
},
-});
+}));
-const Message = ({ op, classes }) => (
-
-
-
-
-
- {op.edited && Edited
}
-
-
-
-
-);
+function Message({ op }) {
+ const classes = useStyles();
+ return (
+
+
+
+
+
+ {op.edited && Edited
}
+
+
+
+
+ );
+}
Message.createFragment = gql`
fragment Create on TimelineItem {
@@ -95,4 +98,4 @@ Message.commentFragment = gql`
}
`;
-export default withStyles(styles)(Message);
+export default Message;
diff --git a/webui/src/bug/SetStatus.js b/webui/src/bug/SetStatus.js
index 58b81630010d27e582d60dc3626384547f4bc9d3..ab591038e3f4ce21fd7da6dc47a6f8ff203db5ec 100644
--- a/webui/src/bug/SetStatus.js
+++ b/webui/src/bug/SetStatus.js
@@ -1,17 +1,18 @@
-import { withStyles } from '@material-ui/core/styles';
+import { makeStyles } from '@material-ui/styles';
import gql from 'graphql-tag';
import React from 'react';
import Author from '../Author';
import Date from '../Date';
-const styles = theme => ({
+const useStyles = makeStyles(theme => ({
main: {
...theme.typography.body2,
marginLeft: theme.spacing.unit + 40,
},
-});
+}));
-const SetStatus = ({ op, classes }) => {
+function SetStatus({ op }) {
+ const classes = useStyles();
return (
@@ -19,7 +20,7 @@ const SetStatus = ({ op, classes }) => {
);
-};
+}
SetStatus.fragment = gql`
fragment SetStatus on TimelineItem {
@@ -35,4 +36,4 @@ SetStatus.fragment = gql`
}
`;
-export default withStyles(styles)(SetStatus);
+export default SetStatus;
diff --git a/webui/src/bug/SetTitle.js b/webui/src/bug/SetTitle.js
index f5c48568de0431c331da986acb2eda174517164c..d9a09ad54d73e446daf6e2a393a70af3e194f4fe 100644
--- a/webui/src/bug/SetTitle.js
+++ b/webui/src/bug/SetTitle.js
@@ -1,10 +1,10 @@
-import { withStyles } from '@material-ui/core/styles';
+import { makeStyles } from '@material-ui/styles';
import gql from 'graphql-tag';
import React from 'react';
import Author from '../Author';
import Date from '../Date';
-const styles = theme => ({
+const useStyles = makeStyles(theme => ({
main: {
...theme.typography.body2,
marginLeft: theme.spacing.unit + 40,
@@ -12,9 +12,10 @@ const styles = theme => ({
bold: {
fontWeight: 'bold',
},
-});
+}));
-const SetTitle = ({ op, classes }) => {
+function SetTitle({ op }) {
+ const classes = useStyles();
return (
@@ -25,7 +26,7 @@ const SetTitle = ({ op, classes }) => {
);
-};
+}
SetTitle.fragment = gql`
fragment SetTitle on TimelineItem {
@@ -42,4 +43,4 @@ SetTitle.fragment = gql`
}
`;
-export default withStyles(styles)(SetTitle);
+export default SetTitle;
diff --git a/webui/src/bug/Timeline.js b/webui/src/bug/Timeline.js
index 3123f45f9628822a48b6d0491b7b37a690bf0403..d77e0d4b554847d21fddb7b9cac54485a1202b96 100644
--- a/webui/src/bug/Timeline.js
+++ b/webui/src/bug/Timeline.js
@@ -1,51 +1,43 @@
-import { withStyles } from '@material-ui/core/styles';
+import { makeStyles } from '@material-ui/styles';
import React from 'react';
import LabelChange from './LabelChange';
import Message from './Message';
import SetStatus from './SetStatus';
import SetTitle from './SetTitle';
-const styles = theme => ({
+const useStyles = makeStyles(theme => ({
main: {
'& > *:not(:last-child)': {
marginBottom: theme.spacing.unit * 2,
},
},
-});
+}));
-class Timeline extends React.Component {
- props: {
- ops: Array,
- fetchMore: any => any,
- classes: any,
- };
+const componentMap = {
+ CreateTimelineItem: Message,
+ AddCommentTimelineItem: Message,
+ LabelChangeTimelineItem: LabelChange,
+ SetTitleTimelineItem: SetTitle,
+ SetStatusTimelineItem: SetStatus,
+};
- render() {
- const { ops, classes } = this.props;
+function Timeline({ ops }) {
+ 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
;
+ return (
+
+ {ops.map((op, index) => {
+ const Component = componentMap[op.__typename];
- default:
- console.log('unsupported operation type ' + op.__typename);
- return null;
- }
- })}
-
- );
- }
+ if (!Component) {
+ console.warn('unsupported operation type ' + op.__typename);
+ return null;
+ }
+
+ return
;
+ })}
+
+ );
}
-export default withStyles(styles)(Timeline);
+export default Timeline;
diff --git a/webui/src/index.js b/webui/src/index.js
index f5d95ccc289c6dc4b096b4d8c850c2c952223661..885911f5cef774b768840c36b751a1de51e8de1f 100644
--- a/webui/src/index.js
+++ b/webui/src/index.js
@@ -1,22 +1,31 @@
+import { install } from '@material-ui/styles';
+import ThemeProvider from '@material-ui/styles/ThemeProvider';
+import { createMuiTheme } from '@material-ui/core/styles';
import ApolloClient from 'apollo-boost';
import React from 'react';
import { ApolloProvider } from 'react-apollo';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
-import App from './App';
+install();
+
+// TODO(sandhose): this is temporary until Material-UI v4 goes out
+const App = React.lazy(() => import('./App'));
+
+const theme = createMuiTheme();
const client = new ApolloClient({
uri: '/graphql',
- connectToDevTools: true,
});
ReactDOM.render(
-
-
-
+
+
+
+
+
,
document.getElementById('root')
diff --git a/webui/src/list/BugRow.js b/webui/src/list/BugRow.js
index a045770b96e854984a46085c6a916f6ff49eada5..e82d81db091ba786d4c282a81886990ff6e0d614 100644
--- a/webui/src/list/BugRow.js
+++ b/webui/src/list/BugRow.js
@@ -1,4 +1,4 @@
-import { withStyles } from '@material-ui/core/styles';
+import { makeStyles } from '@material-ui/styles';
import TableCell from '@material-ui/core/TableCell/TableCell';
import TableRow from '@material-ui/core/TableRow/TableRow';
import Tooltip from '@material-ui/core/Tooltip/Tooltip';
@@ -33,7 +33,7 @@ const Status = ({ status, className }) => {
}
};
-const styles = theme => ({
+const useStyles = makeStyles(theme => ({
cell: {
display: 'flex',
alignItems: 'center',
@@ -53,36 +53,39 @@ const styles = theme => ({
labels: {
paddingLeft: theme.spacing.unit,
},
-});
+}));
-const BugRow = ({ bug, classes }) => (
-
-
-
-
-
-
-
- {bug.title}
-
- {bug.labels.length > 0 && (
-
- {bug.labels.map(l => (
-
- ))}
-
- )}
-
-
-
- {bug.humanId} opened
-
- by {bug.author.displayName}
-
-
-
-
-);
+function BugRow({ bug }) {
+ const classes = useStyles();
+ return (
+
+
+
+
+
+
+
+ {bug.title}
+
+ {bug.labels.length > 0 && (
+
+ {bug.labels.map(l => (
+
+ ))}
+
+ )}
+
+
+
+ {bug.humanId} opened
+
+ by {bug.author.displayName}
+
+
+
+
+ );
+}
BugRow.fragment = gql`
fragment BugRow on Bug {
@@ -99,4 +102,4 @@ BugRow.fragment = gql`
}
`;
-export default withStyles(styles)(BugRow);
+export default BugRow;
diff --git a/webui/src/list/List.js b/webui/src/list/List.js
index d36be8a1464b4f30028c40b397fedd3b1a609eaf..45c2c963f0c90ed174062d2639ffb936486fe06a 100644
--- a/webui/src/list/List.js
+++ b/webui/src/list/List.js
@@ -1,134 +1,50 @@
-import { withStyles } from '@material-ui/core/styles';
+import { makeStyles } from '@material-ui/styles';
+import IconButton from '@material-ui/core/IconButton';
import Table from '@material-ui/core/Table/Table';
import TableBody from '@material-ui/core/TableBody/TableBody';
-import TablePagination from '@material-ui/core/TablePagination/TablePagination';
+import KeyboardArrowLeft from '@material-ui/icons/KeyboardArrowLeft';
+import KeyboardArrowRight from '@material-ui/icons/KeyboardArrowRight';
import React from 'react';
import BugRow from './BugRow';
-const styles = theme => ({
+const useStyles = makeStyles(theme => ({
main: {
maxWidth: 600,
margin: 'auto',
marginTop: theme.spacing.unit * 4,
},
-});
-
-class List extends React.Component {
- props: {
- bugs: Array,
- fetchMore: any => any,
- classes: any,
- };
-
- state = {
- page: 0,
- rowsPerPage: 10,
- lastQuery: {},
- };
-
- handleChangePage = (event, page) => {
- const { bugs, fetchMore } = this.props;
- const { rowsPerPage } = this.state;
- const pageInfo = bugs.pageInfo;
-
- if (page === this.state.page + 1) {
- if (!pageInfo.hasNextPage) {
- return;
- }
-
- const variables = {
- after: pageInfo.endCursor,
- first: rowsPerPage,
- };
-
- fetchMore({
- variables,
- updateQuery: this.updateQuery,
- });
-
- this.setState({ page, lastQuery: variables });
- return;
- }
-
- if (page === this.state.page - 1) {
- if (!pageInfo.hasPreviousPage) {
- return;
- }
-
- const variables = {
- before: pageInfo.startCursor,
- last: rowsPerPage,
- };
-
- fetchMore({
- variables,
- updateQuery: this.updateQuery,
- });
-
- this.setState({ page, lastQuery: variables });
- return;
- }
-
- throw new Error('non neighbour page pagination is not supported');
- };
-
- handleChangeRowsPerPage = event => {
- const { fetchMore } = this.props;
- const { lastQuery } = this.state;
- const rowsPerPage = event.target.value;
-
- const variables = lastQuery;
-
- if (lastQuery.first) {
- variables.first = rowsPerPage;
- } else if (lastQuery.last) {
- variables.last = rowsPerPage;
- } else {
- variables.first = rowsPerPage;
- }
-
- fetchMore({
- variables,
- updateQuery: this.updateQuery,
- });
-
- this.setState({ rowsPerPage, lastQuery: variables });
- };
-
- updateQuery = (previousResult, { fetchMoreResult }) => {
- return fetchMoreResult ? fetchMoreResult : previousResult;
- };
-
- render() {
- const { classes, bugs } = this.props;
- const { page, rowsPerPage } = this.state;
-
- return (
-
-
-
- {bugs.edges.map(({ cursor, node }) => (
-
- ))}
-
-
-
-
- );
- }
+ pagination: {
+ ...theme.typography.overline,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'flex-end',
+ },
+}));
+
+function List({ bugs, nextPage, prevPage }) {
+ const classes = useStyles();
+ const { hasNextPage, hasPreviousPage } = bugs.pageInfo;
+ return (
+
+
+
+ {bugs.edges.map(({ cursor, node }) => (
+
+ ))}
+
+
+
+
+
Total: {bugs.totalCount}
+
+
+
+
+
+
+
+
+ );
}
-export default withStyles(styles)(List);
+export default List;
diff --git a/webui/src/list/ListQuery.js b/webui/src/list/ListQuery.js
index 9dbe4e53d670f26fa42cbbebe8a04e8aa9b319a6..869bca79824334de3fed0c1a355c3010aeb71321 100644
--- a/webui/src/list/ListQuery.js
+++ b/webui/src/list/ListQuery.js
@@ -1,13 +1,13 @@
// @flow
import CircularProgress from '@material-ui/core/CircularProgress';
import gql from 'graphql-tag';
-import React from 'react';
+import React, { useState } from 'react';
import { Query } from 'react-apollo';
import BugRow from './BugRow';
import List from './List';
const QUERY = gql`
- query($first: Int = 10, $last: Int, $after: String, $before: String) {
+ query($first: Int, $last: Int, $after: String, $before: String) {
defaultRepository {
bugs: allBugs(
first: $first
@@ -35,14 +35,31 @@ const QUERY = gql`
${BugRow.fragment}
`;
-const ListQuery = () => (
-
- {({ loading, error, data, fetchMore }) => {
- if (loading) return ;
- if (error) return Error: {error}
;
- return
;
- }}
-
-);
+function ListQuery() {
+ const [page, setPage] = useState({ first: 10, after: null });
+
+ const perPage = page.first || page.last;
+ const nextPage = pageInfo =>
+ setPage({ first: perPage, after: pageInfo.endCursor });
+ const prevPage = pageInfo =>
+ setPage({ last: perPage, before: pageInfo.startCursor });
+
+ return (
+
+ {({ loading, error, data }) => {
+ if (loading) return ;
+ if (error) return Error: {error}
;
+ const bugs = data.defaultRepository.bugs;
+ return (
+ nextPage(bugs.pageInfo)}
+ prevPage={() => prevPage(bugs.pageInfo)}
+ />
+ );
+ }}
+
+ );
+}
export default ListQuery;