diff --git a/src/main.js b/src/main.js index 5cfd0b2..d01ebc5 100644 --- a/src/main.js +++ b/src/main.js @@ -8,7 +8,7 @@ import { SearchBar } from './searchbar.js'; import { InternalErrorPage } from './error.js'; import { LoadingPage } from './loading.js'; import { ProvideDB } from './database.js'; -import { UserQuery, user_query_from_search_string, maybe_image_query, do_image_query, maybe_album_query, do_album_query, maybe_tag_query, do_tag_query, ConstFilter, ResultTypeEnum } from './queries.js'; +import { UserQuery, user_query_from_search_string, maybe_image_query, do_image_query, maybe_album_query, do_album_query, maybe_tag_query, do_tag_query, ConstFilter } from './queries.js'; import { Browser } from './browser.js'; import { UserQueryWidget } from './userquerywidget.js'; import { ResultsView } from './resultsview.js'; @@ -53,8 +53,7 @@ export function LoadedMainPage(props) { useEffect(() => { // Single-fire effect to start retrieving the albums and tags lists. var blank_user_query = new UserQuery(); - blank_user_query.album_filter = new ConstFilter(ResultTypeEnum.ALBUM, true); - blank_user_query.tag_filter = new ConstFilter(ResultTypeEnum.TAG, true); + blank_user_query.filter = new ConstFilter(true); var sql_album_query = maybe_album_query(blank_user_query); var sql_tag_query = maybe_tag_query(blank_user_query); @@ -109,13 +108,13 @@ export function LoadedMainPage(props) { } function onNewQuery(q) { - var do_update_photos = !_.isEqual(q.image_filter, gallery_user_query.image_filter); - var do_update_albums = !_.isEqual(q.album_filter, gallery_user_query.album_filter); - var do_update_tags = !_.isEqual(q.tag_filter, gallery_user_query.tag_filter); + var do_update = !_.isEqual(q.filter, gallery_user_query.filter); setGalleryUserQuery(q); - if (do_update_photos) { updatePhotos(q); } - if (do_update_albums) { updateAlbums(q); } - if (do_update_tags) { updateTags(q); } + if (do_update) { + updatePhotos(q); + updateAlbums(q); + updateTags(q); + } } function onSearch(q) { diff --git a/src/queries.js b/src/queries.js index ee71562..a51b3ce 100644 --- a/src/queries.js +++ b/src/queries.js @@ -89,16 +89,15 @@ export const MatchTypeEnum = { MATCH_TAG_NAME_EQUALS: 10, // Match on the local tag name (excluding parents) } +/* export const ResultTypeEnum = { IMAGE: 1, ALBUM: 2, TAG: 3, } +*/ export class ResultFilter { - constructor(type) { this.result_type = type; } - - result_type = ResultTypeEnum.IMAGE; to_sql_where() { return "(1=1)"; } is_true() { return false; } @@ -108,7 +107,7 @@ export class ResultFilter { } export class ConstFilter extends ResultFilter { - constructor(type, val) { super(type); this.constval = val; } + constructor(val) { super(); this.constval = val; } constval = true; // True lets everything through, false rejects everything to_sql_where() { @@ -121,12 +120,12 @@ export class ConstFilter extends ResultFilter { } export class NegationFilter extends ResultFilter { - constructor(rtype, body) { - super(rtype); + constructor(body) { + super(); this.body = body; } - body = new ConstFilter(this.return_type, false); + body = new ConstFilter(false); to_sql_where() { return "NOT (" + this.body.to_sql_where() + ")"; @@ -137,10 +136,10 @@ export class NegationFilter extends ResultFilter { simplify() { var f = this.body.simplify(); if (f.is_true()) { - return new ConstFilter(this.result_type, false); + return new ConstFilter(false); } if (f.is_false()) { - return new ConstFilter(this.result_type, true); + return new ConstFilter(true); } if (f.is_negation()) { return f.body; @@ -155,8 +154,8 @@ export const TimeFilterTypeEnum = { } export class TimeFilter extends ResultFilter { - constructor(rtype, time, type) { - super(rtype); + constructor(time, type) { + super(); this.time = time; this.type = type; } @@ -186,8 +185,8 @@ export const ImageTypeEnum = { } export class ImageTypeFilter extends ResultFilter { - constructor(rtype, type) { - super(rtype); + constructor(type) { + super(); this.type = type; } @@ -202,8 +201,8 @@ export class ImageTypeFilter extends ResultFilter { } export class MatchingFilter extends ResultFilter { - constructor(rtype, from, mtype) { - super(rtype); + constructor(from, mtype) { + super(); this.match_from = from; this.match_type = mtype; } @@ -258,14 +257,16 @@ export const LogicalOperatorEnum = { } export class LogicalOperatorFilter extends ResultFilter { - constructor(type, a, b, op) { super(type); this.sub_filter_a = a; this.sub_filter_b = b; this.operator = op; } - sub_filter_a = false; - sub_filter_b = false; + constructor(operands, op) { super(); this.operands = operands; this.operator = op; } + operands = false; operator = LogicalOperatorEnum.AND; to_sql_where() { - var where1 = this.sub_filter_a.to_sql_where(); - var where2 = this.sub_filter_b.to_sql_where(); + var operand_wheres = []; + for (let i = 0; i < this.operands.length; i++) { + operand_wheres.push(this.operands[i].to_sql_where()); + } + var operator_str = ""; if (this.operator === LogicalOperatorEnum.AND) { operator_str = " AND "; @@ -274,30 +275,51 @@ export class LogicalOperatorFilter extends ResultFilter { } else { throw new Error('Unsupported logical operator: ' + this.operator); } - return "(" + where1 + operator_str + where2 + ")"; + var retstr = "(" + operand_wheres[0]; + for (let i = 1; i < this.operands.length; i++) { + retstr += operator_str + operand_wheres[i]; + } + retstr += ")"; + return retstr; } simplify() { - var a = this.sub_filter_a.simplify(); - var b = this.sub_filter_b.simplify(); + var simple = []; + for (let i = 0; i < this.operands.length; i++) { + simple.push(this.operands[i].simplify()); + } if (this.operator === LogicalOperatorEnum.OR) { - if (a.is_true() || b.is_true()) { - return new ConstFilter(this.return_type, true); + for (let i = simple.length - 1; i >= 0; i--) { + if (simple[i].is_true()) { + return new ConstFilter(true); + } + if (simple[i].is_false()) { + // Remove this element, because OR FALSE means nothing + simple.splice(i, 1); + } } - if (a.is_false()) { return b; } - if (b.is_false()) { return a; } } if (this.operator === LogicalOperatorEnum.AND) { - if (a.is_false() || b.is_false()) { - return new ConstFilter(this.return_type, false); + for (let i = simple.length - 1; i >= 0; i--) { + if (simple[i].is_false()) { + return new ConstFilter(false); + } + if (simple[i].is_true()) { + // Remove this element, because AND TRUE means nothing + simple.splice(i, 1); + } } - if (a.is_true()) { return b; } - if (b.is_true()) { return a; } } - return this; + if(simple.length == 0) { + return null; + } else if(simple.length == 1) { + return simple[0]; + } + + return new LogicalOperatorFilter(simple, this.operator); } } @@ -306,8 +328,8 @@ export class LocationFilter extends ResultFilter { // to a location and filters objects outside said location. // Locations are directly stored from Nominatim API responses, // which have GeoJSON and other metadata. - constructor(rtype, geo_area) { - super(rtype); + constructor(geo_area) { + super(); this.geo_area = geo_area; } @@ -327,9 +349,7 @@ export class LocationFilter extends ResultFilter { } export class UserQuery { - image_filter = new ConstFilter(ResultTypeEnum.IMAGE, false); - album_filter = new ConstFilter(ResultTypeEnum.ALBUM, false); - tag_filter = new ConstFilter(ResultTypeEnum.TAG, false); + filter = new ConstFilter(false); } // This query will return database entries with the fields "id", "uniqueHash", "relativePath" (of the album) and "name" for each matching image. @@ -378,24 +398,24 @@ export function tag_query_with_where(maybe_where) { export function maybe_image_query(user_query, database) { var where = false; - if (user_query.image_filter) { - where = "WHERE " + user_query.image_filter.to_sql_where(); + if (user_query.filter) { + where = "WHERE " + user_query.filter.to_sql_where(); } return image_query_with_where(where, database); } export function maybe_album_query(user_query, database) { var where = false; - if (user_query.album_filter) { - where = "WHERE " + user_query.album_filter.to_sql_where(); + if (user_query.filter) { + where = "WHERE " + user_query.filter.to_sql_where(); } return album_query_with_where(where, database); } export function maybe_tag_query(user_query) { var where = false; - if (user_query.tag_filter) { - where = "WHERE " + user_query.tag_filter.to_sql_where(); + if (user_query.filter) { + where = "WHERE " + user_query.filter.to_sql_where(); } return tag_query_with_where(where); } @@ -408,51 +428,31 @@ export function filter_is_const_false(filter) { return false; } -function filter_from_text_segment(result_type, segment) { +function filter_from_text_segment(segment) { var filter = false; - var name_filter; - - if (result_type === ResultTypeEnum.IMAGE) { - // Option 1: match on image name - name_filter = new MatchingFilter( - result_type, - segment['text'], - MatchTypeEnum.MATCH_IMAGE_NAME_NATURAL - ); - - // Option 2: match on album path (TODO: need natural matching) - var album_filter = new MatchingFilter( - result_type, - segment['text'], - MatchTypeEnum.MATCH_ALBUM_NAME_EQUALS_OR_CHILD - ); - - filter = new LogicalOperatorFilter(result_type, name_filter, album_filter, LogicalOperatorEnum.OR); - if (segment['negated']) { - filter = new NegationFilter(result_type, filter); - } - } else if (result_type === ResultTypeEnum.ALBUM) { - // TODO: We need a natural matcher for album names - name_filter = new MatchingFilter( - result_type, - segment['text'], - MatchTypeEnum.MATCH_ALBUM_NAME_EQUALS_OR_CHILD - ); - filter = name_filter; - if (segment['negated']) { - filter = new NegationFilter(result_type, filter); - } - } else if (result_type === ResultTypeEnum.TAG) { - // TODO: We need a natural matcher for tag names - name_filter = new MatchingFilter( - result_type, - segment['text'], - MatchTypeEnum.MATCH_TAG_NAME_EQUALS_OR_CHILD - ); - filter = name_filter; - if (segment['negated']) { - filter = new NegationFilter(result_type, filter); - } + + // Option 1: match on image name + var image_filter = new MatchingFilter( + segment['text'], + MatchTypeEnum.MATCH_IMAGE_NAME_NATURAL + ); + + // Option 2: match on album path (TODO: need natural matching) + var album_filter = new MatchingFilter( + segment['text'], + MatchTypeEnum.MATCH_ALBUM_EQUALS_OR_CHILD + ); + + // Option 3: match on tag name + // TODO: We need a natural matcher for tag names + var tag_filter = new MatchingFilter( + segment['text'], + MatchTypeEnum.MATCH_TAG_EQUALS_OR_CHILD + ); + + filter = new LogicalOperatorFilter([image_filter, album_filter, tag_filter], LogicalOperatorEnum.OR); + if (segment['negated']) { + filter = new NegationFilter(filter); } return filter.simplify(); @@ -468,12 +468,8 @@ export function user_query_from_search_string(search_string) { var r = new UserQuery(); texts.forEach(text => { - r.image_filter = new LogicalOperatorFilter(ResultTypeEnum.IMAGE, r.image_filter, - filter_from_text_segment(ResultTypeEnum.IMAGE, text), LogicalOperatorEnum.AND).simplify(); - r.album_filter = new LogicalOperatorFilter(ResultTypeEnum.ALBUM, r.album_filter, - filter_from_text_segment(ResultTypeEnum.ALBUM, text), LogicalOperatorEnum.AND).simplify(); - r.tag_filter = new LogicalOperatorFilter(ResultTypeEnum.TAG, r.tag_filter, - filter_from_text_segment(ResultTypeEnum.TAG, text), LogicalOperatorEnum.AND).simplify(); + r.filter = new LogicalOperatorFilter([r.filter, filter_from_text_segment(text)], + LogicalOperatorEnum.OR).simplify(); }); return r; @@ -481,21 +477,8 @@ export function user_query_from_search_string(search_string) { export function user_query_from_browsed_album(album_path) { var r = new UserQuery(); - r.image_filter = + r.filter = new MatchingFilter( - ResultTypeEnum.IMAGE, - album_path, - MatchTypeEnum.MATCH_ALBUM_EQUALS_OR_CHILD, - false).simplify(); - r.album_filter = - new MatchingFilter( - ResultTypeEnum.ALBUM, - album_path, - MatchTypeEnum.MATCH_ALBUM_EQUALS_OR_CHILD, - false).simplify(); - r.tag_filter = - new MatchingFilter( - ResultTypeEnum.TAG, album_path, MatchTypeEnum.MATCH_ALBUM_EQUALS_OR_CHILD, false).simplify(); @@ -504,24 +487,10 @@ export function user_query_from_browsed_album(album_path) { export function user_query_from_browsed_tag(tag_path) { var r = new UserQuery(); - r.image_filter = - new MatchingFilter( - ResultTypeEnum.IMAGE, - tag_path, - MatchTypeEnum.MATCH_TAG_EQUALS_OR_CHILD, - false).simplify(); - r.album_filter = + r.filter = new MatchingFilter( - ResultTypeEnum.ALBUM, tag_path, MatchTypeEnum.MATCH_TAG_EQUALS_OR_CHILD, false).simplify(); - r.tag_filter = - new MatchingFilter( - ResultTypeEnum.TAG, - tag_path, - MatchTypeEnum.MATCH_TAG_EQUALS_OR_CHILD, - false).simplify(); - return r; } \ No newline at end of file diff --git a/src/userquerywidget.js b/src/userquerywidget.js index 725392b..fcb2c1d 100644 --- a/src/userquerywidget.js +++ b/src/userquerywidget.js @@ -17,6 +17,7 @@ import Select from '@material-ui/core/Select'; import TextField from '@material-ui/core/TextField'; import ScheduleIcon from '@material-ui/icons/Schedule'; import VideocamIcon from '@material-ui/icons/Videocam'; +import LocationOnIcon from '@material-ui/icons/LocationOn'; import { MuiPickersUtilsProvider, DateTimePicker } from "@material-ui/pickers"; import DateFnsUtils from '@date-io/date-fns'; @@ -30,7 +31,7 @@ import { SearchBar } from './searchbar.js'; import { filter_is_const_false, ConstFilter, LogicalOperatorFilter, MatchingFilter, - ResultTypeEnum, LogicalOperatorEnum, MatchTypeEnum, NegationFilter, TimeFilterTypeEnum, + LogicalOperatorEnum, MatchTypeEnum, NegationFilter, TimeFilterTypeEnum, TimeFilter, ImageTypeFilter, ImageTypeEnum, LocationFilter } from './queries.js' import { Typography } from '@material-ui/core'; @@ -82,7 +83,6 @@ export function EditLocationFilterExpression(props) { setGeoLayers([layer]); // Update the proposed polygon - console.log("Updated proposal:", result); setProposal(result); } @@ -94,7 +94,6 @@ export function EditLocationFilterExpression(props) { // Do a geometry query. query_geometry(query) .then(result => { - console.log("Query result:", result); if (result) { updateProposal(result); } @@ -327,15 +326,15 @@ export function EditFilterExpressionDialog(props) { const handleTypeChange = e => { var val = e.target.value; if (val == FilterTypeEnum.CONST) { - setFilter(new ConstFilter(filter.result_type, true)); + setFilter(new ConstFilter(true)); } else if (val == FilterTypeEnum.MATCHING) { - setFilter(new MatchingFilter(filter.result_type, "", MatchTypeEnum.MATCH_IMAGE_NAME_NATURAL)); + setFilter(new MatchingFilter("", MatchTypeEnum.MATCH_IMAGE_NAME_NATURAL)); } else if (val == FilterTypeEnum.TIME) { - setFilter(new TimeFilter(filter.result_type, Date.now(), TimeFilterTypeEnum.AFTER)); + setFilter(new TimeFilter(Date.now(), TimeFilterTypeEnum.AFTER)); } else if (val == FilterTypeEnum.IMAGETYPE) { - setFilter(new ImageTypeFilter(filter.result_type, ImageTypeEnum.PHOTO)); + setFilter(new ImageTypeFilter(ImageTypeEnum.PHOTO)); } else if (val == FilterTypeEnum.LOCATION) { - setFilter(new LocationFilter(filter.result_type, [[0.0, 0.0]])); + setFilter(new LocationFilter([[0.0, 0.0]])); } else { throw new Error('Unsupported filter type: ' + val); } @@ -521,24 +520,14 @@ export function LogicalOperatorFilterExpressionControl(props) { } var _ = require('lodash'); - const handleAChanged = (new_a) => { - if (new_a == null) { - onChange(expr.sub_filter_b); - return; - } - + const handleOpChanged = (idx, new_op) => { 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; + if (new_op == null) { + new_me.operands.splice(idx, 1); + } else { + new_me.operands[idx] = new_op; + } onChange(new_me); } @@ -546,16 +535,19 @@ export function LogicalOperatorFilterExpressionControl(props) { <> - - - - - - - - - - + {expr.operands.map((operand, idx) => { + const handleChange = new_op => { + handleOpChanged(idx, new_op); + }; + + return (<> + + + + + + ); + })} ); } @@ -763,7 +756,7 @@ export function FilterExpressionControl(props) { const handleNegation = () => { handleCloseMenu(); - var new_filter = new NegationFilter(expr.result_type, expr); + var new_filter = new NegationFilter(expr); onChange(new_filter.simplify()); } @@ -818,18 +811,7 @@ export function FilterExpressionControl(props) { 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)); - } - } + const { filter, onChange } = props; // The root node may in principle never be removed, // except if it is a transformation node (then the body becomes the new root). @@ -838,16 +820,6 @@ export function FilterControl(props) { return ( <> - - } - label={resultTypeString + ':'} - /> @@ -856,55 +828,17 @@ export function FilterControl(props) { 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) { + function handleFilterChange(filter) { var q = _.cloneDeep(userQuery); - q.tag_filter = filter; + q.filter = filter; onChange(q); } return ( - <> - - - - - - - - - - - - - - - - + ); } \ No newline at end of file