From 370e3db3455f548699ff5e046e0f8dcc304991ac Mon Sep 17 00:00:00 2001 From: Zero~Informatique Date: Fri, 14 Feb 2020 09:19:53 +0100 Subject: viewer: major code and search mode overhaul Updated libraries to the lastest version SCSS Formatter as suggested VSC extensions Renamed toolbar-color by scrollbar-color LD components use Props in favor of touching the stores directly (when possible) Moved most common algorithms to a "services" folder Complete search overhaul (lots of code change) --- viewer/src/services/indexfactory.ts | 101 ++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 viewer/src/services/indexfactory.ts (limited to 'viewer/src/services/indexfactory.ts') diff --git a/viewer/src/services/indexfactory.ts b/viewer/src/services/indexfactory.ts new file mode 100644 index 0000000..a6bc865 --- /dev/null +++ b/viewer/src/services/indexfactory.ts @@ -0,0 +1,101 @@ +/* ldgallery - A static generator which turns a collection of tagged +-- pictures into a searchable web gallery. +-- +-- Copyright (C) 2019-2020 Guillaume FOUET +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU Affero General Public License as +-- published by the Free Software Foundation, either version 3 of the +-- License, or (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU Affero General Public License for more details. +-- +-- You should have received a copy of the GNU Affero General Public License +-- along with this program. If not, see . +*/ + +import { Operation } from '@/@types/Operation'; +import Navigation from '@/services/navigation'; + +export default class IndexFactory { + + public static generateTags(root: Gallery.Item | null): Tag.Index { + let tagsIndex: Tag.Index = {}; + if (root) IndexFactory.pushTagsForItem(tagsIndex, root); + return tagsIndex; + } + + // Pushes all tags for a root item (and its children) to the index + private static pushTagsForItem(tagsIndex: Tag.Index, item: Gallery.Item): void { + console.log("IndexingTagsFor: ", item.path); + if (item.properties.type === "directory") { + item.properties.items.forEach(item => this.pushTagsForItem(tagsIndex, item)); + return; // Directories are not indexed + } + for (const tag of item.tags) { + const parts = tag.split('.'); + let lastPart: string | null = null; + for (const part of parts) { + if (!tagsIndex[part]) tagsIndex[part] = { tag: part, tagfiltered: Navigation.normalize(part), items: [], children: {} }; + if (!tagsIndex[part].items.includes(item)) tagsIndex[part].items.push(item); + if (lastPart) tagsIndex[lastPart].children[part] = tagsIndex[part]; + lastPart = part; + } + } + } + + // --- + + + public static searchTags(tagsIndex: Tag.Index, filter: string): Tag.Search[] { + let search: Tag.Search[] = []; + if (tagsIndex && filter) { + const operation = IndexFactory.extractOperation(filter); + if (operation !== Operation.INTERSECTION) filter = filter.slice(1); + if (filter.includes(":")) { + const filterParts = filter.split(":"); + search = this.searchTagsFromFilterWithCategory(tagsIndex, operation, filterParts[0], filterParts[1]); + } else { + search = this.searchTagsFromFilter(tagsIndex, operation, filter); + } + } + return search; + } + + private static extractOperation(filter: string): Operation { + const first = filter.slice(0, 1); + switch (first) { + case Operation.ADDITION: + case Operation.SUBSTRACTION: + return first; + default: + return Operation.INTERSECTION; + } + } + + private static searchTagsFromFilterWithCategory( + tagsIndex: Tag.Index, + operation: Operation, + category: string, + disambiguation: string + ): Tag.Search[] { + disambiguation = Navigation.normalize(disambiguation); + return Object.values(tagsIndex) + .filter(node => node.tag.includes(category)) + .flatMap(node => + Object.values(node.children) + .filter(child => child.tagfiltered.includes(disambiguation)) + .map(child => ({ ...child, parent: node, operation, display: `${operation}${node.tag}:${child.tag}` })) + ); + } + + private static searchTagsFromFilter(tagsIndex: Tag.Index, operation: Operation, filter: string): Tag.Search[] { + filter = Navigation.normalize(filter); + return Object.values(tagsIndex) + .filter(node => node.tagfiltered.includes(filter)) + .map(node => ({ ...node, operation, display: `${operation}${node.tag}` })); + } +} -- cgit v1.2.3 From c7fa5bd40d0e5c9ea50190a90a0ccfee8ad96c25 Mon Sep 17 00:00:00 2001 From: pacien Date: Thu, 27 Feb 2020 21:05:51 +0100 Subject: viewer: use colon as tag separator instead of dot For consistency with the query language and allowing the use of the very common dot in tags. This also introduces a migration script. GitHub: closes #164 --- viewer/src/services/indexfactory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'viewer/src/services/indexfactory.ts') diff --git a/viewer/src/services/indexfactory.ts b/viewer/src/services/indexfactory.ts index a6bc865..6fed6cc 100644 --- a/viewer/src/services/indexfactory.ts +++ b/viewer/src/services/indexfactory.ts @@ -36,7 +36,7 @@ export default class IndexFactory { return; // Directories are not indexed } for (const tag of item.tags) { - const parts = tag.split('.'); + const parts = tag.split(':'); let lastPart: string | null = null; for (const part of parts) { if (!tagsIndex[part]) tagsIndex[part] = { tag: part, tagfiltered: Navigation.normalize(part), items: [], children: {} }; -- cgit v1.2.3 From f2ff937fe4a5782741886ef4920fd0e284775463 Mon Sep 17 00:00:00 2001 From: Zero~Informatique Date: Thu, 27 Feb 2020 23:26:00 +0100 Subject: viewer: tag index bugfix Search from the URL requires a strict match instead of a loose match Category search was case sensitive Category + disambiguation was matching like an intersection of both tags instead of being hard-coupled Removed the logs for the release (coming soon) --- viewer/src/services/indexfactory.ts | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) (limited to 'viewer/src/services/indexfactory.ts') diff --git a/viewer/src/services/indexfactory.ts b/viewer/src/services/indexfactory.ts index 6fed6cc..45abcd5 100644 --- a/viewer/src/services/indexfactory.ts +++ b/viewer/src/services/indexfactory.ts @@ -30,7 +30,6 @@ export default class IndexFactory { // Pushes all tags for a root item (and its children) to the index private static pushTagsForItem(tagsIndex: Tag.Index, item: Gallery.Item): void { - console.log("IndexingTagsFor: ", item.path); if (item.properties.type === "directory") { item.properties.items.forEach(item => this.pushTagsForItem(tagsIndex, item)); return; // Directories are not indexed @@ -39,27 +38,35 @@ export default class IndexFactory { const parts = tag.split(':'); let lastPart: string | null = null; for (const part of parts) { - if (!tagsIndex[part]) tagsIndex[part] = { tag: part, tagfiltered: Navigation.normalize(part), items: [], children: {} }; - if (!tagsIndex[part].items.includes(item)) tagsIndex[part].items.push(item); - if (lastPart) tagsIndex[lastPart].children[part] = tagsIndex[part]; + tagsIndex[part] = IndexFactory.pushPartToIndex(tagsIndex[part], part, item); + if (lastPart) { + const children = tagsIndex[lastPart].children; + children[part] = IndexFactory.pushPartToIndex(children[part], part, item); + } lastPart = part; } } } + private static pushPartToIndex(index: Tag.Node, part: string, item: Gallery.Item): Tag.Node { + if (!index) index = { tag: part, tagfiltered: Navigation.normalize(part), items: [], children: {} }; + if (!index.items.includes(item)) index.items.push(item); + return index; + } + // --- - public static searchTags(tagsIndex: Tag.Index, filter: string): Tag.Search[] { + public static searchTags(tagsIndex: Tag.Index, filter: string, strict: boolean): Tag.Search[] { let search: Tag.Search[] = []; if (tagsIndex && filter) { const operation = IndexFactory.extractOperation(filter); if (operation !== Operation.INTERSECTION) filter = filter.slice(1); if (filter.includes(":")) { const filterParts = filter.split(":"); - search = this.searchTagsFromFilterWithCategory(tagsIndex, operation, filterParts[0], filterParts[1]); + search = this.searchTagsFromFilterWithCategory(tagsIndex, operation, filterParts[0], filterParts[1], strict); } else { - search = this.searchTagsFromFilter(tagsIndex, operation, filter); + search = this.searchTagsFromFilter(tagsIndex, operation, filter, strict); } } return search; @@ -80,22 +87,27 @@ export default class IndexFactory { tagsIndex: Tag.Index, operation: Operation, category: string, - disambiguation: string + disambiguation: string, + strict: boolean ): Tag.Search[] { + category = Navigation.normalize(category); disambiguation = Navigation.normalize(disambiguation); return Object.values(tagsIndex) - .filter(node => node.tag.includes(category)) + .filter(node => strict || node.tagfiltered.includes(category)) + .filter(node => !strict || node.tagfiltered === category) .flatMap(node => Object.values(node.children) - .filter(child => child.tagfiltered.includes(disambiguation)) + .filter(child => strict || child.tagfiltered.includes(disambiguation)) + .filter(child => !strict || child.tagfiltered === disambiguation) .map(child => ({ ...child, parent: node, operation, display: `${operation}${node.tag}:${child.tag}` })) ); } - private static searchTagsFromFilter(tagsIndex: Tag.Index, operation: Operation, filter: string): Tag.Search[] { + private static searchTagsFromFilter(tagsIndex: Tag.Index, operation: Operation, filter: string, strict: boolean): Tag.Search[] { filter = Navigation.normalize(filter); return Object.values(tagsIndex) - .filter(node => node.tagfiltered.includes(filter)) + .filter(node => strict || node.tagfiltered.includes(filter)) + .filter(node => !strict || node.tagfiltered === filter) .map(node => ({ ...node, operation, display: `${operation}${node.tag}` })); } } -- cgit v1.2.3 From 8d889762872501eebd5edb5d7cacddfd4cd55ad4 Mon Sep 17 00:00:00 2001 From: Zero~Informatique Date: Fri, 28 Feb 2020 04:09:40 +0100 Subject: viewer: more minor architectural improvement --- viewer/src/services/indexfactory.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'viewer/src/services/indexfactory.ts') diff --git a/viewer/src/services/indexfactory.ts b/viewer/src/services/indexfactory.ts index 45abcd5..a31f3ef 100644 --- a/viewer/src/services/indexfactory.ts +++ b/viewer/src/services/indexfactory.ts @@ -93,12 +93,10 @@ export default class IndexFactory { category = Navigation.normalize(category); disambiguation = Navigation.normalize(disambiguation); return Object.values(tagsIndex) - .filter(node => strict || node.tagfiltered.includes(category)) - .filter(node => !strict || node.tagfiltered === category) + .filter(node => IndexFactory.matches(node, category, strict)) .flatMap(node => Object.values(node.children) - .filter(child => strict || child.tagfiltered.includes(disambiguation)) - .filter(child => !strict || child.tagfiltered === disambiguation) + .filter(child => IndexFactory.matches(child, disambiguation, strict)) .map(child => ({ ...child, parent: node, operation, display: `${operation}${node.tag}:${child.tag}` })) ); } @@ -106,8 +104,12 @@ export default class IndexFactory { private static searchTagsFromFilter(tagsIndex: Tag.Index, operation: Operation, filter: string, strict: boolean): Tag.Search[] { filter = Navigation.normalize(filter); return Object.values(tagsIndex) - .filter(node => strict || node.tagfiltered.includes(filter)) - .filter(node => !strict || node.tagfiltered === filter) + .filter(node => IndexFactory.matches(node, filter, strict)) .map(node => ({ ...node, operation, display: `${operation}${node.tag}` })); } + + private static matches(node: Tag.Node, filter: string, strict: boolean): boolean { + if (strict) return node.tagfiltered === filter; + return node.tagfiltered.includes(filter) + } } -- cgit v1.2.3 From 577f49ab6e1fd9cd8007804a13dea1471ee2fb1f Mon Sep 17 00:00:00 2001 From: Zero~Informatique Date: Fri, 3 Apr 2020 03:42:35 +0200 Subject: viewer: tag categories implementation GitHub: Resolves #29 --- viewer/src/services/indexfactory.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'viewer/src/services/indexfactory.ts') diff --git a/viewer/src/services/indexfactory.ts b/viewer/src/services/indexfactory.ts index a31f3ef..466b509 100644 --- a/viewer/src/services/indexfactory.ts +++ b/viewer/src/services/indexfactory.ts @@ -112,4 +112,23 @@ export default class IndexFactory { if (strict) return node.tagfiltered === filter; return node.tagfiltered.includes(filter) } + + // --- + + public static generateCategories(tagsIndex: Tag.Index, tags?: Gallery.RawTag[]): Tag.Category[] { + if (!tags?.length) return [{ tag: "", index: tagsIndex }]; + + const tagsCategories: Tag.Category[] = []; + const tagsRemaining = new Map(Object.entries(tagsIndex)); + tags + .map(tag => ({ tag, index: tagsIndex[tag]?.children })) + .filter(category => category.index && Object.keys(category.index).length) + .forEach(category => { + tagsCategories.push(category); + tagsRemaining.delete(category.tag); + Object.values(category.index).map(node => node.tag).forEach(tag => tagsRemaining.delete(tag)); + }); + tagsCategories.push({ tag: "", index: Object.fromEntries(tagsRemaining) }); + return tagsCategories; + } } -- cgit v1.2.3 From ce04802f300ba627a3b9e9612d938b825045e63f Mon Sep 17 00:00:00 2001 From: Zero~Informatique Date: Sat, 4 Apr 2020 01:36:34 +0200 Subject: viewer: tag categories implementation fixed single tags not appearing in the "Other filters" special category, following code review GitHub: Resolves #29 --- viewer/src/services/indexfactory.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) (limited to 'viewer/src/services/indexfactory.ts') diff --git a/viewer/src/services/indexfactory.ts b/viewer/src/services/indexfactory.ts index 466b509..0a84951 100644 --- a/viewer/src/services/indexfactory.ts +++ b/viewer/src/services/indexfactory.ts @@ -38,18 +38,19 @@ export default class IndexFactory { const parts = tag.split(':'); let lastPart: string | null = null; for (const part of parts) { - tagsIndex[part] = IndexFactory.pushPartToIndex(tagsIndex[part], part, item); + tagsIndex[part] = IndexFactory.pushPartToIndex(tagsIndex[part], part, item, !Boolean(lastPart)); if (lastPart) { const children = tagsIndex[lastPart].children; - children[part] = IndexFactory.pushPartToIndex(children[part], part, item); + children[part] = IndexFactory.pushPartToIndex(children[part], part, item, false); } lastPart = part; } } } - private static pushPartToIndex(index: Tag.Node, part: string, item: Gallery.Item): Tag.Node { - if (!index) index = { tag: part, tagfiltered: Navigation.normalize(part), items: [], children: {} }; + private static pushPartToIndex(index: Tag.Node, part: string, item: Gallery.Item, rootPart: boolean): Tag.Node { + if (!index) index = { tag: part, tagfiltered: Navigation.normalize(part), rootPart, items: [], children: {} }; + else if (rootPart) index.rootPart = true; if (!index.items.includes(item)) index.items.push(item); return index; } @@ -125,8 +126,14 @@ export default class IndexFactory { .filter(category => category.index && Object.keys(category.index).length) .forEach(category => { tagsCategories.push(category); - tagsRemaining.delete(category.tag); - Object.values(category.index).map(node => node.tag).forEach(tag => tagsRemaining.delete(tag)); + + if (!tagsIndex[category.tag].rootPart) + tagsRemaining.delete(category.tag); + + Object.values(category.index) + .map(node => node.tag) + .filter(tag => !tagsIndex[tag].rootPart) + .forEach(tag => tagsRemaining.delete(tag)); }); tagsCategories.push({ tag: "", index: Object.fromEntries(tagsRemaining) }); return tagsCategories; -- cgit v1.2.3 From 84090e0f534cfa8bf601ae6df21e5df695fd149a Mon Sep 17 00:00:00 2001 From: Zero~Informatique Date: Sat, 4 Apr 2020 02:24:10 +0200 Subject: viewer: tag categories implementation code cleaning GitHub: Resolves #29 --- viewer/src/services/indexfactory.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'viewer/src/services/indexfactory.ts') diff --git a/viewer/src/services/indexfactory.ts b/viewer/src/services/indexfactory.ts index 0a84951..25027f3 100644 --- a/viewer/src/services/indexfactory.ts +++ b/viewer/src/services/indexfactory.ts @@ -127,11 +127,7 @@ export default class IndexFactory { .forEach(category => { tagsCategories.push(category); - if (!tagsIndex[category.tag].rootPart) - tagsRemaining.delete(category.tag); - - Object.values(category.index) - .map(node => node.tag) + [category.tag, ...Object.values(category.index).map(node => node.tag)] .filter(tag => !tagsIndex[tag].rootPart) .forEach(tag => tagsRemaining.delete(tag)); }); -- cgit v1.2.3 From c9c69214dcb16a581525eee319ced6e7d9c98bf3 Mon Sep 17 00:00:00 2001 From: Zero~Informatique Date: Fri, 17 Apr 2020 23:19:49 +0200 Subject: viewer: fixed tag categories proposed again in "other filters" github: resolves #186 --- viewer/src/services/indexfactory.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'viewer/src/services/indexfactory.ts') diff --git a/viewer/src/services/indexfactory.ts b/viewer/src/services/indexfactory.ts index 25027f3..18a2800 100644 --- a/viewer/src/services/indexfactory.ts +++ b/viewer/src/services/indexfactory.ts @@ -45,12 +45,15 @@ export default class IndexFactory { } lastPart = part; } + if (lastPart) tagsIndex[lastPart].childPart = true; } } private static pushPartToIndex(index: Tag.Node, part: string, item: Gallery.Item, rootPart: boolean): Tag.Node { - if (!index) index = { tag: part, tagfiltered: Navigation.normalize(part), rootPart, items: [], children: {} }; + if (!index) index = { tag: part, tagfiltered: Navigation.normalize(part), rootPart, childPart: !rootPart, items: [], children: {} }; else if (rootPart) index.rootPart = true; + else index.childPart = true; + if (!index.items.includes(item)) index.items.push(item); return index; } @@ -116,22 +119,25 @@ export default class IndexFactory { // --- - public static generateCategories(tagsIndex: Tag.Index, tags?: Gallery.RawTag[]): Tag.Category[] { - if (!tags?.length) return [{ tag: "", index: tagsIndex }]; + public static generateCategories(tagsIndex: Tag.Index, categoryTags?: Gallery.RawTag[]): Tag.Category[] { + if (!categoryTags?.length) return [{ tag: "", index: tagsIndex }]; const tagsCategories: Tag.Category[] = []; const tagsRemaining = new Map(Object.entries(tagsIndex)); - tags + categoryTags .map(tag => ({ tag, index: tagsIndex[tag]?.children })) .filter(category => category.index && Object.keys(category.index).length) .forEach(category => { tagsCategories.push(category); - [category.tag, ...Object.values(category.index).map(node => node.tag)] - .filter(tag => !tagsIndex[tag].rootPart) + .filter(tag => IndexFactory.isDiscriminantTagOnly(categoryTags, tagsIndex[tag])) .forEach(tag => tagsRemaining.delete(tag)); }); tagsCategories.push({ tag: "", index: Object.fromEntries(tagsRemaining) }); return tagsCategories; } + + private static isDiscriminantTagOnly(tags: Gallery.RawTag[], node: Tag.Node): boolean { + return !tags.includes(node.tag) || !node.childPart; + } } -- cgit v1.2.3 From ccecfd9421f4550a71134cd46e1388e486f8c564 Mon Sep 17 00:00:00 2001 From: Zero~Informatique Date: Tue, 28 Apr 2020 03:47:39 +0200 Subject: viewer: global formatting unification --- viewer/src/services/indexfactory.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'viewer/src/services/indexfactory.ts') diff --git a/viewer/src/services/indexfactory.ts b/viewer/src/services/indexfactory.ts index 18a2800..e402185 100644 --- a/viewer/src/services/indexfactory.ts +++ b/viewer/src/services/indexfactory.ts @@ -17,8 +17,8 @@ -- along with this program. If not, see . */ -import { Operation } from '@/@types/Operation'; -import Navigation from '@/services/navigation'; +import { Operation } from "@/@types/Operation"; +import Navigation from "@/services/navigation"; export default class IndexFactory { @@ -35,7 +35,7 @@ export default class IndexFactory { return; // Directories are not indexed } for (const tag of item.tags) { - const parts = tag.split(':'); + const parts = tag.split(":"); let lastPart: string | null = null; for (const part of parts) { tagsIndex[part] = IndexFactory.pushPartToIndex(tagsIndex[part], part, item, !Boolean(lastPart)); -- cgit v1.2.3