From fa13550115144a6f39888960a80cc24890f83536 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Fri, 24 Jan 2020 00:43:02 +0100 Subject: webui: enhance the issue list page This starts some ground work for filtering & moves the pagination logic in the query params. Also has a nice loading placeholder. --- webui/src/list/Filter.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 webui/src/list/Filter.js (limited to 'webui/src/list/Filter.js') diff --git a/webui/src/list/Filter.js b/webui/src/list/Filter.js new file mode 100644 index 00000000..ce457d03 --- /dev/null +++ b/webui/src/list/Filter.js @@ -0,0 +1,32 @@ +import React from 'react'; +import { makeStyles } from '@material-ui/styles'; + +const useStyles = makeStyles(theme => ({ + element: { + ...theme.typography.body2, + color: ({ active }) => (active ? '#333' : '#444'), + padding: theme.spacing(0, 1), + fontWeight: ({ active }) => (active ? 500 : 400), + textDecoration: 'none', + display: 'flex', + alignSelf: ({ end }) => (end ? 'flex-end' : 'auto'), + background: 'none', + border: 'none', + }, + icon: { + paddingRight: theme.spacing(0.5), + }, +})); + +function Filter({ active, children, icon: Icon, end, ...props }) { + const classes = useStyles({ active, end }); + + return ( + + ); +} + +export default Filter; -- cgit From 4d97e3a19a96e2361b35a0ccc0be74e0ba887214 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Sat, 25 Jan 2020 11:40:08 +0100 Subject: webui: implement filtering --- webui/src/list/Filter.js | 73 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 3 deletions(-) (limited to 'webui/src/list/Filter.js') diff --git a/webui/src/list/Filter.js b/webui/src/list/Filter.js index ce457d03..c93b2d35 100644 --- a/webui/src/list/Filter.js +++ b/webui/src/list/Filter.js @@ -1,6 +1,58 @@ import React from 'react'; +import { Link } from 'react-router-dom'; import { makeStyles } from '@material-ui/styles'; +function parse(query) { + // TODO: extract the rest of the query? + const params = {}; + + // TODO: support escaping without quotes + const re = /(\w+):(\w+|(["'])(([^\3]|\\.)*)\3)+/g; + let matches; + while ((matches = re.exec(query)) !== null) { + if (!params[matches[1]]) { + params[matches[1]] = []; + } + + let value; + if (matches[4]) { + value = matches[4]; + } else { + value = matches[2]; + } + value = value.replace(/\\(.)/g, '$1'); + params[matches[1]].push(value); + } + return params; +} + +function quote(value) { + const hasSingle = value.includes("'"); + const hasDouble = value.includes('"'); + const hasSpaces = value.includes(' '); + if (!hasSingle && !hasDouble && !hasSpaces) { + return value; + } + + if (!hasDouble) { + return `"${value}"`; + } + + if (!hasSingle) { + return `'${value}'`; + } + + value = value.replace(/"/g, '\\"'); + return `"${value}"`; +} + +function stringify(params) { + const parts = Object.entries(params).map(([key, values]) => { + return values.map(value => `${key}:${quote(value)}`); + }); + return [].concat(...parts).join(' '); +} + const useStyles = makeStyles(theme => ({ element: { ...theme.typography.body2, @@ -18,15 +70,30 @@ const useStyles = makeStyles(theme => ({ }, })); -function Filter({ active, children, icon: Icon, end, ...props }) { +function Filter({ active, to, children, icon: Icon, end, ...props }) { const classes = useStyles({ active, end }); - return ( - ); } export default Filter; +export { parse, stringify, quote }; -- cgit From ead5bad7854bc2342e0998c8a45f62e9aace7887 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Thu, 30 Jan 2020 02:05:36 +0100 Subject: webui: implement issue list sort --- webui/src/list/Filter.js | 77 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 66 insertions(+), 11 deletions(-) (limited to 'webui/src/list/Filter.js') 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 ( + <> + + setOpen(false)} + anchorEl={buttonRef.current} + > + {dropdown.map(([key, value]) => ( + setOpen(false)} + key={key} + > + {value} + + ))} + + + ); +} + +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 ( + + {content} + + ); + } + if (to) { return ( @@ -88,11 +147,7 @@ function Filter({ active, to, children, icon: Icon, end, ...props }) { ); } - return ( - - ); + return
{content}
; } export default Filter; -- cgit