diff --git a/src/queries.js b/src/queries.js index 551456e..0d51707 100644 --- a/src/queries.js +++ b/src/queries.js @@ -80,6 +80,7 @@ export class ResultFilter { to_sql_where() { return "(1=1)"; } is_true() { return false; } is_false() { return false; } + is_negation() { return false; } simplify() { return this; } } @@ -105,17 +106,23 @@ export class NegationFilter extends ResultFilter { body = new ConstFilter(this.return_type, false); to_sql_where() { - return "NOT (" + this.body.to_sql_where + ")"; + return "NOT (" + this.body.to_sql_where() + ")"; } + is_negation() { return true; } + simplify() { var f = this.body.simplify(); + console.log("NOT body:", f); if (f.is_true()) { return new ConstFilter(this.result_type, false); } if (f.is_false()) { return new ConstFilter(this.result_type, true); } + if (f.is_negation()) { + return f.body; + } return this; } } diff --git a/src/userquerywidget.js b/src/userquerywidget.js index 2ecfc50..cf18280 100644 --- a/src/userquerywidget.js +++ b/src/userquerywidget.js @@ -203,20 +203,29 @@ export function EditFilterExpressionDialog(props) { control = } + // 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 ( Edit expression - - - + {allowTypeChange && + + + + } {control} + + + ) +} + export function FilterExpressionControl(props) { const { expr, onChange, isRoot } = props; const [anchorEl, setAnchorEl] = React.useState(null); @@ -428,6 +471,12 @@ export function FilterExpressionControl(props) { }; 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); } @@ -458,6 +507,12 @@ export function FilterExpressionControl(props) { 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) { filter_elem = @@ -465,10 +520,17 @@ export function FilterExpressionControl(props) { filter_elem = } else if (expr instanceof MatchingFilter) { filter_elem = + } else if (expr instanceof NegationFilter) { + filter_elem = } else { 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} @@ -482,7 +544,8 @@ export function FilterExpressionControl(props) { Edit... And... Or... - {!isRoot && Remove} + Negate + {allowRemove && Remove}