|
|
|
@ -1,20 +1,28 @@ |
|
|
|
|
import React from 'react'; |
|
|
|
|
import React, { useEffect } from 'react'; |
|
|
|
|
|
|
|
|
|
import Switch from '@material-ui/core/Switch'; |
|
|
|
|
import Box from '@material-ui/core/Box'; |
|
|
|
|
import FormControlLabel from '@material-ui/core/FormControlLabel'; |
|
|
|
|
import FormControl from '@material-ui/core/FormControl'; |
|
|
|
|
import Button from '@material-ui/core/Button'; |
|
|
|
|
import LabelIcon from '@material-ui/icons/Label'; |
|
|
|
|
import ImportContactsIcon from '@material-ui/icons/ImportContacts'; |
|
|
|
|
import PhotoIcon from '@material-ui/icons/Photo'; |
|
|
|
|
import SearchIcon from '@material-ui/icons/Search'; |
|
|
|
|
import Menu from '@material-ui/core/Menu'; |
|
|
|
|
import MenuItem from '@material-ui/core/MenuItem'; |
|
|
|
|
import DialogTitle from '@material-ui/core/DialogTitle'; |
|
|
|
|
import Dialog from '@material-ui/core/Dialog'; |
|
|
|
|
import Select from '@material-ui/core/Select'; |
|
|
|
|
import TextField from '@material-ui/core/TextField'; |
|
|
|
|
|
|
|
|
|
import { makeStyles } from '@material-ui/core/styles'; |
|
|
|
|
|
|
|
|
|
import { |
|
|
|
|
filter_is_const_false, ConstFilter, LogicalOperatorFilter, MatchingFilter, |
|
|
|
|
ResultTypeEnum, LogicalOperatorEnum, MatchTypeEnum |
|
|
|
|
ResultTypeEnum, LogicalOperatorEnum, MatchTypeEnum, NegationFilter |
|
|
|
|
} from './queries.js' |
|
|
|
|
import { Typography } from '@material-ui/core'; |
|
|
|
|
|
|
|
|
|
const useStyles = makeStyles(theme => ({ |
|
|
|
|
root: {}, |
|
|
|
@ -40,14 +48,202 @@ const useStyles = makeStyles(theme => ({ |
|
|
|
|
}, |
|
|
|
|
})); |
|
|
|
|
|
|
|
|
|
export function EditConstFilterExpression(props) { |
|
|
|
|
const { onChange, filter } = props; |
|
|
|
|
|
|
|
|
|
var _ = require('lodash'); |
|
|
|
|
|
|
|
|
|
function handleResultToggled() { |
|
|
|
|
var newfilter = _.cloneDeep(filter); |
|
|
|
|
newfilter.constval = !filter.constval; |
|
|
|
|
onChange(newfilter); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<FormControlLabel |
|
|
|
|
control={ |
|
|
|
|
<Switch |
|
|
|
|
checked={filter.constval} |
|
|
|
|
onChange={handleResultToggled} |
|
|
|
|
color="primary" |
|
|
|
|
/> |
|
|
|
|
} |
|
|
|
|
label={filter.constval ? "TRUE" : "FALSE"} |
|
|
|
|
/> |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export function EditLogicalOperatorFilterExpression(props) { |
|
|
|
|
const { onChange, filter } = props; |
|
|
|
|
|
|
|
|
|
var _ = require('lodash'); |
|
|
|
|
const id = _.uniqueId("logic_op_"); |
|
|
|
|
const labelid = _.uniqueId("logic_op_label_"); |
|
|
|
|
|
|
|
|
|
function handleChange(e) { |
|
|
|
|
var newfilter = _.cloneDeep(filter); |
|
|
|
|
newfilter.operator = e.target.value; |
|
|
|
|
onChange(newfilter); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<FormControl> |
|
|
|
|
<Select |
|
|
|
|
labelId={labelid} |
|
|
|
|
id={id} |
|
|
|
|
value={filter.operator} |
|
|
|
|
onChange={handleChange} |
|
|
|
|
> |
|
|
|
|
<MenuItem value={LogicalOperatorEnum.AND}>AND</MenuItem> |
|
|
|
|
<MenuItem value={LogicalOperatorEnum.OR}>OR</MenuItem> |
|
|
|
|
</Select> |
|
|
|
|
</FormControl> |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export function EditMatchingFilterExpression(props) { |
|
|
|
|
const { onChange, filter } = props; |
|
|
|
|
const classes = useStyles(); |
|
|
|
|
|
|
|
|
|
function handleTypeChange(e) { |
|
|
|
|
var new_filter = _.cloneDeep(filter); |
|
|
|
|
new_filter.match_type = e.target.value; |
|
|
|
|
onChange(new_filter); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function handleMatchChange(e) { |
|
|
|
|
var new_filter = _.cloneDeep(filter); |
|
|
|
|
new_filter.match_from = e.target.value; |
|
|
|
|
onChange(new_filter); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var _ = require('lodash'); |
|
|
|
|
const typeSelectId = _.uniqueId("select_"); |
|
|
|
|
const typeSelectLabelId = _.uniqueId("select_label_"); |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<FormControl> |
|
|
|
|
<Typography className={classes.margined}>TODO: make nice icons and autocomplete here</Typography> |
|
|
|
|
<Select |
|
|
|
|
className={classes.margined} |
|
|
|
|
labelId={typeSelectLabelId} |
|
|
|
|
id={typeSelectId} |
|
|
|
|
value={filter.match_type} |
|
|
|
|
onChange={handleTypeChange} |
|
|
|
|
> |
|
|
|
|
<MenuItem value={MatchTypeEnum.MATCH_IMAGE_NAME_EQUALS}>Image Name Equals</MenuItem> |
|
|
|
|
<MenuItem value={MatchTypeEnum.MATCH_IMAGE_NAME_NATURAL}>Image Name Natural</MenuItem> |
|
|
|
|
<MenuItem value={MatchTypeEnum.MATCH_ALBUM_EQUALS}>Album Equals</MenuItem> |
|
|
|
|
<MenuItem value={MatchTypeEnum.MATCH_ALBUM_EQUALS_OR_CHILD}>Album Equals Or Child</MenuItem> |
|
|
|
|
<MenuItem value={MatchTypeEnum.MATCH_ALBUM_NATURAL}>Album Natural</MenuItem> |
|
|
|
|
<MenuItem value={MatchTypeEnum.MATCH_ALBUM_NAME_EQUALS}>Album Name Equals</MenuItem> |
|
|
|
|
<MenuItem value={MatchTypeEnum.MATCH_TAG_EQUALS}>Tag Equals</MenuItem> |
|
|
|
|
</Select> |
|
|
|
|
<TextField className={classes.margined} label="Value" type="text" onChange={handleMatchChange} /> |
|
|
|
|
</FormControl> |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export function EditFilterExpressionDialog(props) { |
|
|
|
|
const classes = useStyles(); |
|
|
|
|
const { onClose, startingFilter, open } = props; |
|
|
|
|
const [filter, setFilter] = React.useState(startingFilter); |
|
|
|
|
|
|
|
|
|
const FilterTypeEnum = { |
|
|
|
|
CONST: 0, |
|
|
|
|
NEGATION: 1, |
|
|
|
|
MATCHING: 2, |
|
|
|
|
LOGICAL: 3, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
setFilter(startingFilter); |
|
|
|
|
}, [startingFilter]); |
|
|
|
|
|
|
|
|
|
const handleClose = () => { |
|
|
|
|
onClose(filter); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const handleTypeChange = e => { |
|
|
|
|
var val = e.target.value; |
|
|
|
|
if (val == FilterTypeEnum.CONST) { |
|
|
|
|
setFilter(new ConstFilter(filter.result_type, true)); |
|
|
|
|
} else if (val == FilterTypeEnum.MATCHING) { |
|
|
|
|
setFilter(new MatchingFilter(filter.result_type, "", MatchTypeEnum.MATCH_IMAGE_NAME_NATURAL)); |
|
|
|
|
} else { |
|
|
|
|
throw new Error('Unsupported filter type: ' + val); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const getFilterType = filter => { |
|
|
|
|
if (filter instanceof ConstFilter) { return FilterTypeEnum.CONST; } |
|
|
|
|
else if (filter instanceof NegationFilter) { return FilterTypeEnum.NEGATION; } |
|
|
|
|
else if (filter instanceof MatchingFilter) { return FilterTypeEnum.MATCHING; } |
|
|
|
|
else if (filter instanceof LogicalOperatorFilter) { return FilterTypeEnum.LOGICAL; } |
|
|
|
|
else { |
|
|
|
|
throw new Error('Unsupported filter type: ' + filter); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var _ = require('lodash'); |
|
|
|
|
const id = _.uniqueId("simple_dialog_title_"); |
|
|
|
|
const selectId = _.uniqueId("select_"); |
|
|
|
|
const selectLabelId = _.uniqueId("select_label_"); |
|
|
|
|
|
|
|
|
|
var control = <></>; |
|
|
|
|
var subprops = { |
|
|
|
|
onChange: setFilter, |
|
|
|
|
filter: filter, |
|
|
|
|
}; |
|
|
|
|
if (filter instanceof ConstFilter) { |
|
|
|
|
control = <EditConstFilterExpression {...subprops} /> |
|
|
|
|
} else if (filter instanceof LogicalOperatorFilter) { |
|
|
|
|
control = <EditLogicalOperatorFilterExpression {...subprops} /> |
|
|
|
|
} else if (filter instanceof MatchingFilter) { |
|
|
|
|
control = <EditMatchingFilterExpression {...subprops} /> |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// If this is a "leaf" filter, we will allow changing the filter type in the dialog.
|
|
|
|
|
// But if it is a combination filter (i.e. AND / OR / NOT), we won't allow it because
|
|
|
|
|
// That throws away all its children.
|
|
|
|
|
const allowTypeChange = |
|
|
|
|
(filter instanceof ConstFilter) || |
|
|
|
|
(filter instanceof MatchingFilter); |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<Dialog aria-labelledby={id} open={open}> |
|
|
|
|
<DialogTitle id={id}>Edit expression</DialogTitle> |
|
|
|
|
{allowTypeChange && |
|
|
|
|
<FormControl className={classes.margined}> |
|
|
|
|
<Select |
|
|
|
|
labelId={selectLabelId} |
|
|
|
|
id={selectId} |
|
|
|
|
value={getFilterType(filter)} |
|
|
|
|
onChange={handleTypeChange} |
|
|
|
|
> |
|
|
|
|
<MenuItem value={FilterTypeEnum.CONST}>Constant</MenuItem> |
|
|
|
|
<MenuItem value={FilterTypeEnum.MATCHING}>Matching</MenuItem> |
|
|
|
|
</Select> |
|
|
|
|
</FormControl> |
|
|
|
|
} |
|
|
|
|
{control} |
|
|
|
|
<Button onClick={handleClose} className={classes.margined}> |
|
|
|
|
Done |
|
|
|
|
</Button> |
|
|
|
|
</Dialog> |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export function TagEqualsExpressionControl(props) { |
|
|
|
|
const classes = useStyles(); |
|
|
|
|
const { name } = props; |
|
|
|
|
const { name, onClick } = props; |
|
|
|
|
return ( |
|
|
|
|
<> |
|
|
|
|
<Button |
|
|
|
|
variant="outlined" |
|
|
|
|
className={classes.filterexpcontrol} |
|
|
|
|
aria-controls="simple-menu" aria-haspopup="true" |
|
|
|
|
onClick={onClick} |
|
|
|
|
startIcon={<LabelIcon />}> |
|
|
|
|
{name} |
|
|
|
|
</Button> |
|
|
|
@ -57,12 +253,14 @@ export function TagEqualsExpressionControl(props) { |
|
|
|
|
|
|
|
|
|
export function AlbumEqualsExpressionControl(props) { |
|
|
|
|
const classes = useStyles(); |
|
|
|
|
const { name } = props; |
|
|
|
|
const { name, onClick } = props; |
|
|
|
|
return ( |
|
|
|
|
<> |
|
|
|
|
<Button |
|
|
|
|
variant="outlined" |
|
|
|
|
className={classes.filterexpcontrol} |
|
|
|
|
aria-controls="simple-menu" aria-haspopup="true" |
|
|
|
|
onClick={onClick} |
|
|
|
|
startIcon={<ImportContactsIcon />}> |
|
|
|
|
{name} |
|
|
|
|
</Button> |
|
|
|
@ -72,12 +270,14 @@ export function AlbumEqualsExpressionControl(props) { |
|
|
|
|
|
|
|
|
|
export function ImageNameEqualsExpressionControl(props) { |
|
|
|
|
const classes = useStyles(); |
|
|
|
|
const { name } = props; |
|
|
|
|
const { name, onClick } = props; |
|
|
|
|
return ( |
|
|
|
|
<> |
|
|
|
|
<Button |
|
|
|
|
variant="outlined" |
|
|
|
|
className={classes.filterexpcontrol} |
|
|
|
|
aria-controls="simple-menu" aria-haspopup="true" |
|
|
|
|
onClick={onClick} |
|
|
|
|
startIcon={<PhotoIcon />}> |
|
|
|
|
{name} |
|
|
|
|
</Button> |
|
|
|
@ -87,12 +287,14 @@ export function ImageNameEqualsExpressionControl(props) { |
|
|
|
|
|
|
|
|
|
export function ImageNameNaturalMatchExpressionControl(props) { |
|
|
|
|
const classes = useStyles(); |
|
|
|
|
const { name } = props; |
|
|
|
|
const { name, onClick } = props; |
|
|
|
|
return ( |
|
|
|
|
<> |
|
|
|
|
<Button |
|
|
|
|
variant="outlined" |
|
|
|
|
className={classes.filterexpcontrol} |
|
|
|
|
aria-controls="simple-menu" aria-haspopup="true" |
|
|
|
|
onClick={onClick} |
|
|
|
|
startIcon={<><PhotoIcon /><SearchIcon /></>}> |
|
|
|
|
{name} |
|
|
|
|
</Button> |
|
|
|
@ -102,23 +304,23 @@ export function ImageNameNaturalMatchExpressionControl(props) { |
|
|
|
|
|
|
|
|
|
export function MatchingFilterExpressionControl(props) { |
|
|
|
|
const classes = useStyles(); |
|
|
|
|
const { expr } = props; |
|
|
|
|
const { expr, onClick } = props; |
|
|
|
|
|
|
|
|
|
var pretty_name = expr.match_from |
|
|
|
|
.replace(/^"/g, '') |
|
|
|
|
.replace(/"$/g, ''); |
|
|
|
|
if (expr.match_type === MatchTypeEnum.MATCH_TAG_EQUALS) { |
|
|
|
|
return <TagEqualsExpressionControl name={pretty_name} /> |
|
|
|
|
return <TagEqualsExpressionControl name={pretty_name} onClick={onClick} /> |
|
|
|
|
} else if (expr.match_type === MatchTypeEnum.MATCH_ALBUM_EQUALS) { |
|
|
|
|
return <AlbumEqualsExpressionControl name={pretty_name} /> |
|
|
|
|
return <AlbumEqualsExpressionControl name={pretty_name} onClick={onClick} /> |
|
|
|
|
} else if (expr.match_type === MatchTypeEnum.MATCH_ALBUM_EQUALS_OR_CHILD) { |
|
|
|
|
return <AlbumEqualsExpressionControl name={pretty_name + '(/...)'} /> |
|
|
|
|
return <AlbumEqualsExpressionControl name={pretty_name + '(/...)'} onClick={onClick} /> |
|
|
|
|
} else if (expr.match_type === MatchTypeEnum.MATCH_IMAGE_NAME_EQUALS) { |
|
|
|
|
return <ImageNameEqualsExpressionControl name={pretty_name} /> |
|
|
|
|
return <ImageNameEqualsExpressionControl name={pretty_name} onClick={onClick} /> |
|
|
|
|
} else if (expr.match_type === MatchTypeEnum.MATCH_IMAGE_NAME_NATURAL) { |
|
|
|
|
return <ImageNameNaturalMatchExpressionControl name={pretty_name} /> |
|
|
|
|
return <ImageNameNaturalMatchExpressionControl name={pretty_name} onClick={onClick} /> |
|
|
|
|
} else if (expr.match_type === MatchTypeEnum.MATCH_ALBUM_NAME_EQUALS) { |
|
|
|
|
return <AlbumEqualsExpressionControl name={'(.../)' + pretty_name + '(/...)'} /> |
|
|
|
|
return <AlbumEqualsExpressionControl name={'(.../)' + pretty_name + '(/...)'} onClick={onClick} /> |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
throw new Error('Cannot render matching filter control: unsupported type.'); |
|
|
|
@ -126,7 +328,7 @@ export function MatchingFilterExpressionControl(props) { |
|
|
|
|
|
|
|
|
|
export function LogicalOperatorFilterExpressionControl(props) { |
|
|
|
|
const classes = useStyles(); |
|
|
|
|
const { expr } = props; |
|
|
|
|
const { expr, onClick, onChange } = props; |
|
|
|
|
|
|
|
|
|
var opstring = ""; |
|
|
|
|
if (expr.operator === LogicalOperatorEnum.AND) { |
|
|
|
@ -135,24 +337,49 @@ export function LogicalOperatorFilterExpressionControl(props) { |
|
|
|
|
opstring = " OR "; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var _ = require('lodash'); |
|
|
|
|
const handleAChanged = (new_a) => { |
|
|
|
|
if (new_a == null) { |
|
|
|
|
onChange(expr.sub_filter_b); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var new_me = _.cloneDeep(expr); |
|
|
|
|
new_me.sub_filter_a = new_a; |
|
|
|
|
onChange(new_me); |
|
|
|
|
} |
|
|
|
|
const handleBChanged = (new_b) => { |
|
|
|
|
if (new_b == null) { |
|
|
|
|
onChange(expr.sub_filter_a); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var new_me = _.cloneDeep(expr); |
|
|
|
|
new_me.sub_filter_b = new_b; |
|
|
|
|
onChange(new_me); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<> |
|
|
|
|
<Box className={classes.logic_op_outer}> |
|
|
|
|
<Box className={classes.logic_op_sbs}> |
|
|
|
|
<Box> |
|
|
|
|
<Box className={classes.logic_op_subexpr}> |
|
|
|
|
<FilterExpressionControl expr={expr.sub_filter_a} /> |
|
|
|
|
<FilterExpressionControl expr={expr.sub_filter_a} onChange={handleAChanged} isRoot={false} /> |
|
|
|
|
</Box> |
|
|
|
|
</Box> |
|
|
|
|
<Box> |
|
|
|
|
<Box className={classes.logic_op_subexpr}> |
|
|
|
|
<FilterExpressionControl expr={expr.sub_filter_b} /> |
|
|
|
|
<FilterExpressionControl expr={expr.sub_filter_b} onChange={handleBChanged} isRoot={false} /> |
|
|
|
|
</Box> |
|
|
|
|
</Box> |
|
|
|
|
</Box> |
|
|
|
|
<Button |
|
|
|
|
variant="outlined" |
|
|
|
|
className={classes.filterexpcontrol + " " + classes.logic_op_sbs}> |
|
|
|
|
className={classes.filterexpcontrol + " " + classes.logic_op_sbs} |
|
|
|
|
aria-controls="simple-menu" aria-haspopup="true" |
|
|
|
|
onClick={onClick} |
|
|
|
|
> |
|
|
|
|
{opstring} |
|
|
|
|
</Button> |
|
|
|
|
</Box> |
|
|
|
@ -162,33 +389,184 @@ export function LogicalOperatorFilterExpressionControl(props) { |
|
|
|
|
|
|
|
|
|
export function ConstFilterExpressionControl(props) { |
|
|
|
|
const classes = useStyles(); |
|
|
|
|
const { expr } = props; |
|
|
|
|
const { expr, onClick } = props; |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<Button variant="outlined" className={classes.filterexpcontrol}> |
|
|
|
|
<Button |
|
|
|
|
variant="outlined" |
|
|
|
|
className={classes.filterexpcontrol} |
|
|
|
|
aria-controls="simple-menu" aria-haspopup="true" |
|
|
|
|
onClick={onClick} |
|
|
|
|
> |
|
|
|
|
{JSON.stringify(expr.constval)} |
|
|
|
|
</Button> |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export function NegationExpressionControl(props) { |
|
|
|
|
const classes = useStyles(); |
|
|
|
|
const { expr, onClick, onChange } = props; |
|
|
|
|
|
|
|
|
|
var _ = require('lodash'); |
|
|
|
|
function handleBodyChanged(body) { |
|
|
|
|
var new_filter = _.cloneDeep(expr); |
|
|
|
|
new_filter.body = body; |
|
|
|
|
onChange(new_filter); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<> |
|
|
|
|
<Box className={classes.logic_op_outer}> |
|
|
|
|
<Box className={classes.logic_op_sbs}> |
|
|
|
|
<Box> |
|
|
|
|
<Box className={classes.logic_op_subexpr}> |
|
|
|
|
<FilterExpressionControl expr={expr.body} onChange={handleBodyChanged} isRoot={false} /> |
|
|
|
|
</Box> |
|
|
|
|
</Box> |
|
|
|
|
</Box> |
|
|
|
|
<Button |
|
|
|
|
variant="outlined" |
|
|
|
|
className={classes.filterexpcontrol + " " + classes.logic_op_sbs} |
|
|
|
|
aria-controls="simple-menu" aria-haspopup="true" |
|
|
|
|
onClick={onClick} |
|
|
|
|
> |
|
|
|
|
NOT |
|
|
|
|
</Button> |
|
|
|
|
</Box> |
|
|
|
|
</> |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export function FilterExpressionControl(props) { |
|
|
|
|
const { expr } = props; |
|
|
|
|
const { expr, onChange, isRoot } = props; |
|
|
|
|
const [anchorEl, setAnchorEl] = React.useState(null); |
|
|
|
|
const [editDialogOpen, setEditDialogOpen] = React.useState(false); |
|
|
|
|
const [combineDialogOpen, setCombineDialogOpen] = React.useState(false); |
|
|
|
|
const [combineExpr, setCombineExpr] = React.useState(new LogicalOperatorFilter( |
|
|
|
|
expr.result_type, |
|
|
|
|
expr, |
|
|
|
|
new ConstFilter(expr.result_type, true), |
|
|
|
|
LogicalOperatorEnum.AND) |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
var _ = require('lodash'); |
|
|
|
|
const menu_id = _.uniqueId("filter_menu_"); |
|
|
|
|
|
|
|
|
|
const handleClick = event => { |
|
|
|
|
setAnchorEl(event.currentTarget); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const handleCloseMenu = () => { |
|
|
|
|
setAnchorEl(null); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const handleOpenEditDialog = (filter) => { |
|
|
|
|
handleCloseMenu(); |
|
|
|
|
setEditDialogOpen(true); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const handleCloseEditFilterDialog = (filter) => { |
|
|
|
|
setEditDialogOpen(false); |
|
|
|
|
onChange(filter.simplify()); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const handleRemove = () => { |
|
|
|
|
// For negation filters, removal means replacing the negation node by its child.
|
|
|
|
|
// In all other cases, removal means complete deletion of the subtree.
|
|
|
|
|
if (expr instanceof NegationFilter) { |
|
|
|
|
onChange(expr.body); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
onChange(null); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const handleCloseCombineDialog = (filter) => { |
|
|
|
|
setCombineDialogOpen(false); |
|
|
|
|
var new_filter = _.cloneDeep(combineExpr); |
|
|
|
|
new_filter.sub_filter_b = filter; |
|
|
|
|
onChange(new_filter.simplify()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const handleAnd = () => { |
|
|
|
|
handleCloseMenu(); |
|
|
|
|
setCombineExpr(new LogicalOperatorFilter( |
|
|
|
|
expr.result_type, |
|
|
|
|
expr, |
|
|
|
|
new ConstFilter(expr.result_type, true), LogicalOperatorEnum.AND |
|
|
|
|
)); |
|
|
|
|
setCombineDialogOpen(true); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const handleOr = () => { |
|
|
|
|
handleCloseMenu(); |
|
|
|
|
setCombineExpr(new LogicalOperatorFilter( |
|
|
|
|
expr.result_type, |
|
|
|
|
expr, |
|
|
|
|
new ConstFilter(expr.result_type, true), LogicalOperatorEnum.OR |
|
|
|
|
)); |
|
|
|
|
setCombineDialogOpen(true); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const handleNegation = () => { |
|
|
|
|
handleCloseMenu(); |
|
|
|
|
var new_filter = new NegationFilter(expr.result_type, expr); |
|
|
|
|
onChange(new_filter.simplify()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var filter_elem = false; |
|
|
|
|
if (expr instanceof ConstFilter) { |
|
|
|
|
return <ConstFilterExpressionControl {...props} /> |
|
|
|
|
filter_elem = <ConstFilterExpressionControl {...props} onClick={handleClick} /> |
|
|
|
|
} else if (expr instanceof LogicalOperatorFilter) { |
|
|
|
|
return <LogicalOperatorFilterExpressionControl {...props} /> |
|
|
|
|
filter_elem = <LogicalOperatorFilterExpressionControl {...props} onClick={handleClick} onChange={onChange} /> |
|
|
|
|
} else if (expr instanceof MatchingFilter) { |
|
|
|
|
return <MatchingFilterExpressionControl {...props} /> |
|
|
|
|
filter_elem = <MatchingFilterExpressionControl {...props} onClick={handleClick} /> |
|
|
|
|
} else if (expr instanceof NegationFilter) { |
|
|
|
|
filter_elem = <NegationExpressionControl {...props} onClick={handleClick} onChange={onChange} /> |
|
|
|
|
} else { |
|
|
|
|
throw new Error('Unsupported filter expression'); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
throw new Error('Unsupported filter expression'); |
|
|
|
|
// If this is the root node, removing it is not allowed.
|
|
|
|
|
// Other nodes may be removed.
|
|
|
|
|
// The only exception is a negation node: removing that will replace it by its child.
|
|
|
|
|
var allowRemove = !isRoot || (expr instanceof NegationFilter); |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<> |
|
|
|
|
{filter_elem} |
|
|
|
|
<Menu |
|
|
|
|
id={menu_id} |
|
|
|
|
anchorEl={anchorEl} |
|
|
|
|
keepMounted |
|
|
|
|
open={Boolean(anchorEl)} |
|
|
|
|
onClose={handleCloseMenu} |
|
|
|
|
> |
|
|
|
|
<MenuItem onClick={handleOpenEditDialog}>Edit...</MenuItem> |
|
|
|
|
<MenuItem onClick={handleAnd}>And...</MenuItem> |
|
|
|
|
<MenuItem onClick={handleOr}>Or...</MenuItem> |
|
|
|
|
<MenuItem onClick={handleNegation}>Negate</MenuItem> |
|
|
|
|
{allowRemove && <MenuItem onClick={handleRemove}>Remove</MenuItem>} |
|
|
|
|
</Menu> |
|
|
|
|
<EditFilterExpressionDialog |
|
|
|
|
onClose={handleCloseEditFilterDialog} |
|
|
|
|
startingFilter={expr} |
|
|
|
|
open={editDialogOpen} |
|
|
|
|
/> |
|
|
|
|
<EditFilterExpressionDialog |
|
|
|
|
onClose={handleCloseCombineDialog} |
|
|
|
|
startingFilter={combineExpr.sub_filter_b} |
|
|
|
|
open={combineDialogOpen} |
|
|
|
|
/> |
|
|
|
|
</> |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export function FilterControl(props) { |
|
|
|
|
const classes = useStyles(); |
|
|
|
|
const { filter, onChange, resultType, resultTypeString } = props; |
|
|
|
|
|
|
|
|
|
const enabled = !filter_is_const_false(filter); |
|
|
|
|
|
|
|
|
|
function handleResultToggled() { |
|
|
|
|
if (enabled) { |
|
|
|
|
onChange(new ConstFilter(resultType, false)); |
|
|
|
@ -198,7 +576,6 @@ export function FilterControl(props) { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const enabled = !filter_is_const_false(filter); |
|
|
|
|
return ( |
|
|
|
|
<> |
|
|
|
|
<Box className={classes.filtercontrol}> |
|
|
|
@ -212,7 +589,7 @@ export function FilterControl(props) { |
|
|
|
|
} |
|
|
|
|
label={resultTypeString + ':'} |
|
|
|
|
/> |
|
|
|
|
<FilterExpressionControl expr={filter} /> |
|
|
|
|
<FilterExpressionControl expr={filter} onChange={onChange} isRoot={true} /> |
|
|
|
|
</Box> |
|
|
|
|
</> |
|
|
|
|
); |
|
|
|
|