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 ScheduleIcon from '@material-ui/icons/Schedule'; import DateFnsUtils from '@date-io/date-fns'; import { MuiPickersUtilsProvider, DateTimePicker } from "@material-ui/pickers"; import { makeStyles } from '@material-ui/core/styles'; import { filter_is_const_false, ConstFilter, LogicalOperatorFilter, MatchingFilter, ResultTypeEnum, LogicalOperatorEnum, MatchTypeEnum, NegationFilter, TimeFilterTypeEnum, TimeFilter } from './queries.js' import { Typography } from '@material-ui/core'; const useStyles = makeStyles(theme => ({ root: {}, filterexpcontrol: {}, logic_op_outer: { display: "flex", }, logic_op_sbs: { "box-sizing": "border-box", }, logic_op_subexpr: { float: "right", }, filtercontrol: { display: "flex", }, bordered: { border: "1px solid black", "border-radius": "3px", }, margined: { margin: "6px", }, })); export function EditTimeFilterExpression(props) { const { onChange, filter } = props; var _ = require('lodash'); const id = _.uniqueId("time_filter_"); const labelid = _.uniqueId("time_filter_label_"); function handleChangeType(e) { var newfilter = _.cloneDeep(filter); newfilter.type = e.target.value; onChange(newfilter); } function handleChangeDate(datetime) { var newfilter = _.cloneDeep(filter); newfilter.time = datetime; onChange(newfilter); } return ( ); } export function EditConstFilterExpression(props) { const { onChange, filter } = props; var _ = require('lodash'); function handleResultToggled() { var newfilter = _.cloneDeep(filter); newfilter.constval = !filter.constval; onChange(newfilter); } return ( } 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 ( ); } 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 ( TODO: make nice icons and autocomplete here ); } 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, TIME: 4, }; 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 if (val == FilterTypeEnum.TIME) { setFilter(new TimeFilter(filter.result_type, Date.now(), TimeFilterTypeEnum.AFTER)); } 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 if (filter instanceof TimeFilter) { return FilterTypeEnum.TIME; } 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 = } else if (filter instanceof LogicalOperatorFilter) { control = } else if (filter instanceof MatchingFilter) { control = } else if (filter instanceof TimeFilter) { 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 TagEqualsExpressionControl(props) { const classes = useStyles(); const { name, onClick } = props; return ( <> ); } export function AlbumEqualsExpressionControl(props) { const classes = useStyles(); const { name, onClick } = props; return ( <> ); } export function ImageNameEqualsExpressionControl(props) { const classes = useStyles(); const { name, onClick } = props; return ( <> ); } export function ImageNameNaturalMatchExpressionControl(props) { const classes = useStyles(); const { name, onClick } = props; return ( <> ); } export function MatchingFilterExpressionControl(props) { const classes = useStyles(); const { expr, onClick } = props; var pretty_name = expr.match_from .replace(/^"/g, '') .replace(/"$/g, ''); if (expr.match_type === MatchTypeEnum.MATCH_TAG_EQUALS) { return } else if (expr.match_type === MatchTypeEnum.MATCH_TAG_EQUALS_OR_CHILD) { return } else if (expr.match_type === MatchTypeEnum.MATCH_ALBUM_EQUALS) { return } else if (expr.match_type === MatchTypeEnum.MATCH_ALBUM_EQUALS_OR_CHILD) { return } else if (expr.match_type === MatchTypeEnum.MATCH_IMAGE_NAME_EQUALS) { return } else if (expr.match_type === MatchTypeEnum.MATCH_IMAGE_NAME_NATURAL) { return } else if (expr.match_type === MatchTypeEnum.MATCH_ALBUM_NAME_EQUALS) { return } throw new Error('Cannot render matching filter control: unsupported type.'); } export function LogicalOperatorFilterExpressionControl(props) { const classes = useStyles(); const { expr, onClick, onChange } = props; var opstring = ""; if (expr.operator === LogicalOperatorEnum.AND) { opstring = " AND "; } else if (expr.operator === LogicalOperatorEnum.OR) { 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 ( <> ) } export function ConstFilterExpressionControl(props) { const classes = useStyles(); const { expr, onClick } = props; return ( ); } 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 ( <> ) } export function TimeFilterExpressionControl(props) { const classes = useStyles(); const { expr, onClick, onChange } = props; const relation = ""; if (expr.type == TimeFilterTypeEnum.BEFORE) { relation = "Before: "; } else if (expr.type == TimeFilterTypeEnum.AFTER) { relation = "After: "; } else { throw new Error("Unsupported time filter type."); } return ( ); } export function FilterExpressionControl(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) { filter_elem = } else if (expr instanceof LogicalOperatorFilter) { filter_elem = } else if (expr instanceof MatchingFilter) { filter_elem = } else if (expr instanceof NegationFilter) { filter_elem = } else if (expr instanceof TimeFilter) { 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} Edit... And... Or... Negate {allowRemove && Remove} ); } 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)); } else { onChange(new ConstFilter(resultType, true)); } } return ( <> } label={resultTypeString + ':'} /> ); } export function UserQueryWidget(props) { const { userQuery, onChange } = props; const classes = useStyles(); var _ = require('lodash'); function handleImageFilterChange(filter) { var q = _.cloneDeep(userQuery); q.image_filter = filter; onChange(q); } function handleAlbumFilterChange(filter) { var q = _.cloneDeep(userQuery); q.album_filter = filter; onChange(q); } function handleTagFilterChange(filter) { var q = _.cloneDeep(userQuery); q.tag_filter = filter; onChange(q); } return ( <> ); }