aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorQuentin Gliech <quentingliech@gmail.com>2020-01-30 02:05:36 +0100
committerQuentin Gliech <quentingliech@gmail.com>2020-01-30 02:05:36 +0100
commitead5bad7854bc2342e0998c8a45f62e9aace7887 (patch)
treef4c38f38ac2d746597ad0b639661cd08cadca6fe
parent4d97e3a19a96e2361b35a0ccc0be74e0ba887214 (diff)
downloadgit-bug-ead5bad7854bc2342e0998c8a45f62e9aace7887.tar.gz
webui: implement issue list sort
-rw-r--r--webui/src/__tests__/query.js4
-rw-r--r--webui/src/list/Filter.js77
-rw-r--r--webui/src/list/FilterToolbar.js17
-rw-r--r--webui/src/list/ListQuery.js21
4 files changed, 97 insertions, 22 deletions
diff --git a/webui/src/__tests__/query.js b/webui/src/__tests__/query.js
index 1415af02..5f4b58eb 100644
--- a/webui/src/__tests__/query.js
+++ b/webui/src/__tests__/query.js
@@ -7,9 +7,9 @@ it('parses a simple query', () => {
});
it('parses a query with multiple filters', () => {
- expect(parse('foo:bar baz:foobar')).toEqual({
+ expect(parse('foo:bar baz:foo-bar')).toEqual({
foo: ['bar'],
- baz: ['foobar'],
+ baz: ['foo-bar'],
});
});
diff --git a/webui/src/list/Filter.js b/webui/src/list/Filter.js
index c93b2d35..a6cf3633 100644
--- a/webui/src/list/Filter.js
+++ b/webui/src/list/Filter.js
@@ -1,13 +1,16 @@
-import React from 'react';
+import React, { useState, useRef } from 'react';
import { Link } from 'react-router-dom';
import { makeStyles } from '@material-ui/styles';
+import Menu from '@material-ui/core/Menu';
+import MenuItem from '@material-ui/core/MenuItem';
+import ArrowDropDown from '@material-ui/icons/ArrowDropDown';
function parse(query) {
// TODO: extract the rest of the query?
const params = {};
// TODO: support escaping without quotes
- const re = /(\w+):(\w+|(["'])(([^\3]|\\.)*)\3)+/g;
+ const re = /(\w+):([A-Za-z0-9-]+|(["'])(([^\3]|\\.)*)\3)+/g;
let matches;
while ((matches = re.exec(query)) !== null) {
if (!params[matches[1]]) {
@@ -58,20 +61,63 @@ const useStyles = makeStyles(theme => ({
...theme.typography.body2,
color: ({ active }) => (active ? '#333' : '#444'),
padding: theme.spacing(0, 1),
- fontWeight: ({ active }) => (active ? 500 : 400),
+ fontWeight: ({ active }) => (active ? 600 : 400),
textDecoration: 'none',
display: 'flex',
- alignSelf: ({ end }) => (end ? 'flex-end' : 'auto'),
background: 'none',
border: 'none',
},
+ itemActive: {
+ fontWeight: 600,
+ },
icon: {
paddingRight: theme.spacing(0.5),
},
}));
-function Filter({ active, to, children, icon: Icon, end, ...props }) {
- const classes = useStyles({ active, end });
+function Dropdown({ children, dropdown, itemActive, to, ...props }) {
+ const [open, setOpen] = useState(false);
+ const buttonRef = useRef();
+ const classes = useStyles();
+
+ return (
+ <>
+ <button ref={buttonRef} onClick={() => setOpen(!open)} {...props}>
+ {children}
+ <ArrowDropDown fontSize="small" />
+ </button>
+ <Menu
+ getContentAnchorEl={null}
+ anchorOrigin={{
+ vertical: 'bottom',
+ horizontal: 'left',
+ }}
+ transformOrigin={{
+ vertical: 'top',
+ horizontal: 'left',
+ }}
+ open={open}
+ onClose={() => setOpen(false)}
+ anchorEl={buttonRef.current}
+ >
+ {dropdown.map(([key, value]) => (
+ <MenuItem
+ component={Link}
+ to={to(key)}
+ className={itemActive(key) ? classes.itemActive : null}
+ onClick={() => setOpen(false)}
+ key={key}
+ >
+ {value}
+ </MenuItem>
+ ))}
+ </Menu>
+ </>
+ );
+}
+
+function Filter({ active, to, children, icon: Icon, dropdown, ...props }) {
+ const classes = useStyles({ active });
const content = (
<>
@@ -80,6 +126,19 @@ function Filter({ active, to, children, icon: Icon, end, ...props }) {
</>
);
+ if (dropdown) {
+ return (
+ <Dropdown
+ {...props}
+ to={to}
+ dropdown={dropdown}
+ className={classes.element}
+ >
+ {content}
+ </Dropdown>
+ );
+ }
+
if (to) {
return (
<Link to={to} {...props} className={classes.element}>
@@ -88,11 +147,7 @@ function Filter({ active, to, children, icon: Icon, end, ...props }) {
);
}
- return (
- <button {...props} className={classes.element}>
- {content}
- </button>
- );
+ return <div className={classes.element}>{content}</div>;
}
export default Filter;
diff --git a/webui/src/list/FilterToolbar.js b/webui/src/list/FilterToolbar.js
index e6d6f4ed..9f5f14c5 100644
--- a/webui/src/list/FilterToolbar.js
+++ b/webui/src/list/FilterToolbar.js
@@ -32,7 +32,7 @@ function FilterToolbar({ query, queryLocation }) {
};
// TODO: open/closed count
- // TODO: author/label/sort filters
+ // TODO: author/label filters
return (
<Toolbar className={classes.toolbar}>
<Filter
@@ -52,7 +52,20 @@ function FilterToolbar({ query, queryLocation }) {
<div className={classes.spacer} />
<Filter active={hasKey('author')}>Author</Filter>
<Filter active={hasKey('label')}>Label</Filter>
- <Filter active={hasKey('sort')}>Sort</Filter>
+ <Filter
+ dropdown={[
+ ['id', 'ID'],
+ ['creation', 'Newest'],
+ ['creation-asc', 'Oldest'],
+ ['edit', 'Recently updated'],
+ ['edit-asc', 'Least recently updated'],
+ ]}
+ active={hasKey('sort')}
+ itemActive={key => hasValue('sort', key)}
+ to={key => replaceParam('sort', key)}
+ >
+ Sort
+ </Filter>
</Toolbar>
);
}
diff --git a/webui/src/list/ListQuery.js b/webui/src/list/ListQuery.js
index b6a29702..01113f6c 100644
--- a/webui/src/list/ListQuery.js
+++ b/webui/src/list/ListQuery.js
@@ -45,11 +45,13 @@ const useStyles = makeStyles(theme => ({
borderWidth: '1px',
backgroundColor: fade(theme.palette.primary.main, 0.05),
padding: theme.spacing(0, 1),
- ':focus': {
- // TODO
- borderColor: fade(theme.palette.primary.main, 0.4),
- backgroundColor: theme.palette.background.paper,
- },
+ width: ({ searching }) => (searching ? '20rem' : '15rem'),
+ transition: theme.transitions.create(),
+ },
+ searchFocused: {
+ borderColor: fade(theme.palette.primary.main, 0.4),
+ backgroundColor: theme.palette.background.paper,
+ width: '20rem!important',
},
placeholderRow: {
padding: theme.spacing(1),
@@ -182,7 +184,6 @@ const Error = ({ error }) => {
};
function ListQuery() {
- const classes = useStyles();
const location = useLocation();
const history = useHistory();
const params = new URLSearchParams(location.search);
@@ -190,6 +191,8 @@ function ListQuery() {
const [input, setInput] = useState(query);
+ const classes = useStyles({ searching: !!input });
+
// TODO is this the right way to do it?
const lastQuery = useRef();
useEffect(() => {
@@ -291,9 +294,13 @@ function ListQuery() {
<h1>Issues</h1>
<form onSubmit={formSubmit}>
<InputBase
+ placeholder="Filter"
value={input}
onInput={e => setInput(e.target.value)}
- className={classes.search}
+ classes={{
+ root: classes.search,
+ focused: classes.searchFocused,
+ }}
/>
<button type="submit" hidden>
Search