diff --git a/src/browser.js b/src/browser.js
index 6d6ddc0..89220e7 100644
--- a/src/browser.js
+++ b/src/browser.js
@@ -8,46 +8,47 @@ import ExpandLess from '@material-ui/icons/ExpandLess';
import ExpandMore from '@material-ui/icons/ExpandMore';
import Collapse from '@material-ui/core/Collapse';
-import { user_query_from_browsed_album } from './queries';
+import { user_query_from_browsed_album, user_query_from_browsed_tag } from './queries';
-class AlbumTreeItem {
- name = false;
- relative_path = false;
+class NavTreeItem {
+ display_name = false;
+ data = false;
children = [];
- constructor(name, relative_path, children) {
- this.name = name;
- this.relative_path = relative_path;
+ constructor(display_name, data, children) {
+ this.display_name = display_name;
+ this.data = data;
this.children = children;
}
}
-export function split_tree_item_location(treeitem) {
- var r = treeitem.relative_path.split("/");
+export function split_relative_path(path) {
+ var r = path.split("/");
r.shift();
return r;
}
export function insert_into_album_tree(treebaseitem, treeitem) {
- var parts = split_tree_item_location(treeitem);
+ var parts = split_relative_path(treeitem.data);
var current_item = treebaseitem;
for (var i = 0; i < parts.length; i++) {
var part = parts[i];
var subitem = false;
+ var required_relative_path = (current_item.data == "/") ?
+ current_item.data + part :
+ current_item.data + "/" + part;
+
for (var j = 0; j < current_item.children.length; j++) {
var child = current_item.children[j];
- if (child.name == part) {
+ if (child.data == required_relative_path) {
subitem = child;
break;
}
}
if (!subitem) {
- var new_path = (current_item.relative_path == "/") ?
- current_item.relative_path + part :
- current_item.relative_path + "/" + part;
- var new_sub = new AlbumTreeItem(
+ var new_sub = new NavTreeItem(
part,
- new_path,
+ required_relative_path,
[]
);
current_item.children.push(new_sub);
@@ -58,11 +59,11 @@ export function insert_into_album_tree(treebaseitem, treeitem) {
}
export function build_albums_tree(all_db_albums) {
- var tree = new AlbumTreeItem("", "/", []);
+ var tree = new NavTreeItem("", "/", []);
for (var i = 0; i < all_db_albums.length; i++) {
var album = all_db_albums[i];
- if (album.state.relative_path != "/") { // we already made the base
- var item = new AlbumTreeItem(
+ if (album.state.relative_path != "/") { // we already made the base, skip that one
+ var item = new NavTreeItem(
album.state.name,
album.state.relative_path,
[]
@@ -73,6 +74,24 @@ export function build_albums_tree(all_db_albums) {
return tree;
}
+export function insert_into_tag_tree(treebaseitem, treeitem) {
+ treebaseitem.children.push(treeitem);
+}
+
+export function build_tags_tree(all_db_tags) {
+ var tree = new NavTreeItem("", "", []);
+ for (var i = 0; i < all_db_tags.length; i++) {
+ var tag = all_db_tags[i];
+ var item = new NavTreeItem(
+ tag.state.name,
+ tag.state.name,
+ []
+ );
+ insert_into_tag_tree(tree, item);
+ };
+ return tree;
+}
+
const useStyles = makeStyles(theme => ({
root: {
width: '100%',
@@ -84,40 +103,39 @@ const useStyles = makeStyles(theme => ({
},
}));
-export function AlbumListItem(props) {
+export function NavListItem(props) {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
- const { album, onNewQuery } = props;
+ const { navitem, onSelect } = props;
const handleExpandClick = () => {
setOpen(!open);
}
const handleClick = () => {
- if (onNewQuery) {
- var query = user_query_from_browsed_album(album.relative_path);
- onNewQuery(query);
+ if (onSelect) {
+ onSelect(navitem);
}
}
- if (album.children.length == 0) {
+ if (navitem.children.length == 0) {
return (
-
+
)
} else {
return (
<>
-
+
{open ? : }
{
- album.children.map(elem => {
- return
+ navitem.children.map(elem => {
+ return
})
}
@@ -129,7 +147,7 @@ export function AlbumListItem(props) {
export function Browser(props) {
const classes = useStyles();
- const { albums, onNewQuery } = props;
+ const { albums, tags, onNewQuery } = props;
const propagateQuery = (query) => {
if (onNewQuery) {
@@ -137,7 +155,20 @@ export function Browser(props) {
}
}
- const tree = build_albums_tree(albums);
+ const propagateAlbumQuery = (navitem) => {
+ propagateQuery(
+ user_query_from_browsed_album(navitem.data)
+ );
+ }
+
+ const propagateTagQuery = (navitem) => {
+ propagateQuery(
+ user_query_from_browsed_tag(navitem.data)
+ );
+ }
+
+ const albums_tree = build_albums_tree(albums);
+ const tags_tree = build_tags_tree(tags);
return (
<>
@@ -146,14 +177,30 @@ export function Browser(props) {
aria-labelledby="nested-list-subheader"
subheader={
+ }
+ className={classes.root}
+ >
+ {
+ albums_tree.children.map(elem => {
+ return
+ })
+ }
+
+
diff --git a/src/database.js b/src/database.js
index cb3a64a..b9e2bdc 100644
--- a/src/database.js
+++ b/src/database.js
@@ -16,7 +16,7 @@ export function alasql_async_queries(alasql_object, queries) {
var p = Promise.resolve(null);
for (let i = 0; i < queries.length; i++) {
p = p.then(() => {
- return alasql_object.promise(queries[i]);
+ return alasql_object.promise(queries[i]);
});
}
@@ -296,7 +296,7 @@ export class ProvideDB extends React.Component {
export class DBQueryBar extends React.Component {
onChangeHandler = (event) => {
- if(this.props.onChange) { this.props.onChange(event.target.value) };
+ if (this.props.onChange) { this.props.onChange(event.target.value) };
}
render() {
diff --git a/src/debuggingpage.js b/src/debuggingpage.js
index afa9348..ce1228b 100644
--- a/src/debuggingpage.js
+++ b/src/debuggingpage.js
@@ -103,11 +103,9 @@ export class TestDBStringQuery extends React.Component {
var sql_album_query = maybe_album_query(q);
this.setState({ query: q, sql_image_query: sql_image_query, sql_album_query: sql_album_query, photos: false, albums: false });
do_image_query(sql_image_query, this.props.db, "/test_photos", "/test_photos_thumbs").then(photos => {
- console.log(photos);
this.setState({ done: true, photos: photos });
});
do_album_query(sql_album_query, this.props.db, "/test_photos", "/test_photos_thumbs").then(albums => {
- console.log(albums);
this.setState({ done: true, albums: albums });
});
}
diff --git a/src/main.js b/src/main.js
index 52dde08..0c6d219 100644
--- a/src/main.js
+++ b/src/main.js
@@ -9,7 +9,7 @@ import { InternalErrorPage } from './error.js';
import { LoadingPage } from './loading.js';
import { ProvideDB, DBTypeEnum, DBSourceEnum } from './database.js';
import { GridGallery } from './gridgallery.js';
-import { UserQuery, user_query_from_search_string, maybe_image_query, do_image_query, maybe_album_query, do_album_query } 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 } from './queries.js';
import { Browser } from './browser.js';
const useStyles = makeStyles(theme => ({
@@ -42,15 +42,23 @@ export function LoadedMainPage(props) {
const [gallery_user_query, setGalleryUserQuery] = React.useState(false);
const [photos, setPhotos] = React.useState(false);
const [albums, setAlbums] = React.useState(false);
+ const [tags, setTags] = React.useState(false);
useEffect(() => {
- // Single-fire effect to start retrieving the albums list.
+ // Single-fire effect to start retrieving the albums and tags lists.
var blank_user_query = new UserQuery;
var sql_album_query = maybe_album_query(blank_user_query);
+ var sql_tag_query = maybe_tag_query(blank_user_query);
+
do_album_query(sql_album_query, props.database)
.then(albums => {
setAlbums(albums);
});
+
+ do_tag_query(sql_tag_query, props.database)
+ .then(tags => {
+ setTags(tags);
+ });
}, []);
useEffect(() => {
@@ -83,7 +91,7 @@ export function LoadedMainPage(props) {
<>
- {albums && }
+ {albums && }
diff --git a/src/media.js b/src/media.js
index caebc73..926011c 100644
--- a/src/media.js
+++ b/src/media.js
@@ -1,6 +1,5 @@
import React from 'react';
import './TableLine.css';
-import { findAllByDisplayValue } from '@testing-library/dom';
export class Media { }
@@ -38,6 +37,13 @@ export class Album extends Media {
}
}
+export class Tag extends Media {
+ state = {
+ id: false,
+ name: false
+ }
+}
+
export function create_photo(maybe_id, maybe_name, maybe_path, maybe_thumbnail_path) {
var p = new Photo();
if (maybe_id) { p.state.id = maybe_id; }
@@ -55,6 +61,13 @@ export function create_album(maybe_id, maybe_name, maybe_relative_path) {
return a;
}
+export function create_tag(maybe_id, maybe_name) {
+ var t = new Tag();
+ if(maybe_id) { t.state.id = maybe_id; }
+ if(maybe_name) { t.state.name = maybe_name; }
+ return t;
+}
+
export const PhotoView = ({ photo }) =>
;
export class PhotoThumbView extends React.Component {
diff --git a/src/queries.js b/src/queries.js
index dd0eb5a..6ac9764 100644
--- a/src/queries.js
+++ b/src/queries.js
@@ -1,22 +1,25 @@
-import { create_photo, create_album } from './media.js';
+import { create_photo, create_album, create_tag } from './media.js';
export function escape_regex(s) {
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
};
export function do_image_query(query, database, collection_path, collection_thumbs_path) {
- console.log("Doing image query:", query);
return new Promise(function (resolve, reject) {
var queries = [];
+ var ids = []; // TODO: this is for always uniquifying because of GROUP BY apparent bug in AlaSQL
queries.push(query);
database.queries_async(queries).then(res => {
var photos = [];
if (res && Array.isArray(res)) {
res.forEach(row => {
- var imagepath = process.env.PUBLIC_URL + collection_path + "/" + row["relativePath"] + "/" + row["name"];
- var thumbpath = process.env.PUBLIC_URL + collection_thumbs_path + "/" + row["uniqueHash"] + ".jpg";
- photos.push(create_photo(row["id"], row["name"], imagepath, thumbpath));
+ if (!ids.includes(row["id"])) { //uniquify
+ ids.push(row["id"]); //uniquify
+ var imagepath = process.env.PUBLIC_URL + collection_path + "/" + row["relativePath"] + "/" + row["name"];
+ var thumbpath = process.env.PUBLIC_URL + collection_thumbs_path + "/" + row["uniqueHash"] + ".jpg";
+ photos.push(create_photo(row["id"], row["name"], imagepath, thumbpath));
+ }
});
}
resolve(photos);
@@ -41,6 +44,22 @@ export function do_album_query(query, database) {
});
}
+export function do_tag_query(query, database) {
+ return new Promise(function (resolve, reject) {
+ var queries = [];
+ queries.push(query);
+ database.queries_async(queries).then(res => {
+ var tags = [];
+ if (res && Array.isArray(res)) {
+ res.forEach(row => {
+ tags.push(create_tag(row["id"], row["name"]));
+ });
+ }
+ resolve(tags);
+ });
+ });
+}
+
export const MatchTypeEnum = {
MATCH_EQUALS: 1,
MATCH_REGEXP_CASEINSENSITIVE: 2,
@@ -48,13 +67,15 @@ export const MatchTypeEnum = {
export const MatchAgainstEnum = {
MATCH_RESULT_NAME: 1, // Match on the name of whatever object type we are querying for
- MATCH_IMAGE_NAME: 2, // Match on the name of the relevant image, if any
- MATCH_ALBUM_NAME: 3, // Match on the name of the relevant album, if any
+ MATCH_IMAGE_NAME: 2, // Match on the name of the relevant image(s), if any
+ MATCH_ALBUM_NAME: 3, // Match on the name of the relevant album(s), if any
+ MATCH_TAG_NAME: 4, // Match on the name of the relevant tag(s), if any
}
export const ResultTypeEnum = {
IMAGE: 1,
ALBUM: 2,
+ TAG: 3,
}
export class ResultFilter {
@@ -143,11 +164,14 @@ export class LogicalOperatorFilter extends ResultFilter {
export class UserQuery {
image_filter = new ConstFilter(ResultTypeEnum.IMAGE, true);
album_filter = new ConstFilter(ResultTypeEnum.ALBUM, true);
+ tag_filter = new ConstFilter(ResultTypeEnum.TAG, true);
}
// This query will return database entries with the fields "id", "uniqueHash", "relativePath" (of the album) and "name" for each matching image.
export function image_query_with_where(maybe_where) {
- return "SELECT Images.id, Images.name, Images.uniqueHash, Albums.relativePath FROM Images INNER JOIN Albums ON Images.album=Albums.id " + (maybe_where ? maybe_where : "") + ";";
+ return "SELECT Images.id, Images.name, Images.uniqueHash, Albums.relativePath FROM Images INNER JOIN Albums ON Images.album=Albums.id LEFT JOIN ImageTags ON Images.id=ImageTags.imageid LEFT JOIN Tags ON ImageTags.tagid=Tags.id " + (maybe_where ? maybe_where : "");
+ // TODO: the following for some reason breaks the query:
+ //+ " GROUP BY Images.id;";
}
// This query will return database entries with the fields "id" and "relativePath" for each matching album.
@@ -155,6 +179,11 @@ export function album_query_with_where(maybe_where) {
return "SELECT Albums.id, Albums.relativePath FROM Albums " + (maybe_where ? maybe_where : "") + ";";
}
+// This query will return database entries with the fields "id" and "name" for each matching tag.
+export function tag_query_with_where(maybe_where) {
+ return "SELECT Tags.id, Tags.name FROM Tags " + (maybe_where ? maybe_where : "") + ";";
+}
+
export function maybe_image_query(user_query) {
var where = false;
if (user_query.image_filter) {
@@ -171,6 +200,14 @@ export function maybe_album_query(user_query) {
return album_query_with_where(where);
}
+export function maybe_tag_query(user_query) {
+ var where = false;
+ if (user_query.tag_filter) {
+ where = "WHERE (" + user_query.tag_filter.to_sql_where() + " AND Tags.pid = 0 AND NOT Tags.name=\"_Digikam_Internal_Tags_\")"; // TODO this way of doing the pid is hacky
+ }
+ return tag_query_with_where(where);
+}
+
function filter_from_text_segment(result_type, segment) {
var filter = false;
@@ -205,6 +242,16 @@ function filter_from_text_segment(result_type, segment) {
segment['negated']
);
filter = name_filter;
+ } else if (result_type == ResultTypeEnum.TAG) {
+ // Match against the tag name.
+ var name_filter = new MatchingFilter(
+ result_type,
+ "Tags.name",
+ '"' + '.*' + escape_regex(segment['text']) + '.*' + '"',
+ MatchTypeEnum.MATCH_REGEXP_CASEINSENSITIVE,
+ segment['negated']
+ );
+ filter = name_filter;
}
return filter;
@@ -220,11 +267,12 @@ export function user_query_from_search_string(search_string) {
var r = new UserQuery();
texts.forEach(text => {
- console.log(text);
r.image_filter = new LogicalOperatorFilter(ResultTypeEnum.IMAGE, r.image_filter,
filter_from_text_segment(ResultTypeEnum.IMAGE, text), LogicalOperatorEnum.AND);
r.album_filter = new LogicalOperatorFilter(ResultTypeEnum.ALBUM, r.album_filter,
filter_from_text_segment(ResultTypeEnum.ALBUM, text), LogicalOperatorEnum.AND);
+ r.tag_filter = new LogicalOperatorFilter(ResultTypeEnum.TAG, r.tag_filter,
+ filter_from_text_segment(ResultTypeEnum.TAG, text), LogicalOperatorEnum.AND);
});
return r;
@@ -235,8 +283,20 @@ export function user_query_from_browsed_album(album_path) {
var match_type = MatchTypeEnum.MATCH_REGEXP_CASEINSENSITIVE;
var match_text = '"' + escape_regex(album_path) + '(\/[^\/]+)*' + '"';
var match_against = "Albums.relativePath";
- r.image_filter = new MatchingFilter(ResultTypeEnum.ALBUM, match_against, match_text, match_type, false);
+ r.image_filter = new MatchingFilter(ResultTypeEnum.IMAGE, match_against, match_text, match_type, false);
+ r.album_filter = new ConstFilter(ResultTypeEnum.ALBUM, false);
+
+ return r;
+}
+
+export function user_query_from_browsed_tag(name) {
+ var r = new UserQuery();
+ var match_type = MatchTypeEnum.MATCH_EQUALS;
+ var match_text = '"' + name + '"';
+ var match_against = "Tags.name";
+ r.image_filter = new MatchingFilter(ResultTypeEnum.IMAGE, match_against, match_text, match_type, false);
r.album_filter = new ConstFilter(ResultTypeEnum.ALBUM, false);
+ r.tag_filter = new ConstFilter(ResultTypeEnum.TAG, false);
return r;
}
\ No newline at end of file