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/dragscrollclickfix.ts | 52 +++++++++++++++
viewer/src/services/indexfactory.ts | 101 ++++++++++++++++++++++++++++++
viewer/src/services/indexsearch.ts | 70 +++++++++++++++++++++
viewer/src/services/navigation.ts | 71 +++++++++++++++++++++
4 files changed, 294 insertions(+)
create mode 100644 viewer/src/services/dragscrollclickfix.ts
create mode 100644 viewer/src/services/indexfactory.ts
create mode 100644 viewer/src/services/indexsearch.ts
create mode 100644 viewer/src/services/navigation.ts
(limited to 'viewer/src/services')
diff --git a/viewer/src/services/dragscrollclickfix.ts b/viewer/src/services/dragscrollclickfix.ts
new file mode 100644
index 0000000..38eb106
--- /dev/null
+++ b/viewer/src/services/dragscrollclickfix.ts
@@ -0,0 +1,52 @@
+/* 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 .
+*/
+
+// https://github.com/donmbelembe/vue-dragscroll/issues/61
+export default class DragScrollClickFix {
+
+ readonly DRAG_DELAY = 250; // This is the minimal delay to consider a click to be a drag, mostly usefull for touch devices
+
+ timer: NodeJS.Timeout | null = null;
+ dragging: boolean = false;
+
+ onDragScrollStart() {
+ this.timer = setTimeout(() => this.onTimer(), this.DRAG_DELAY);
+ }
+
+ onTimer() {
+ this.timer = null;
+ this.dragging = true;
+ }
+
+ onDragScrollEnd() {
+ if (this.timer) {
+ clearTimeout(this.timer);
+ this.timer = null;
+ }
+ setTimeout(() => this.dragging = false);
+ }
+
+ onClickCapture(e: MouseEvent) {
+ if (this.dragging) {
+ this.dragging = false;
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ }
+}
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}` }));
+ }
+}
diff --git a/viewer/src/services/indexsearch.ts b/viewer/src/services/indexsearch.ts
new file mode 100644
index 0000000..3e73fb1
--- /dev/null
+++ b/viewer/src/services/indexsearch.ts
@@ -0,0 +1,70 @@
+/* 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';
+
+export default class IndexSearch {
+
+ // Results of the search (by tags)
+ public static search(searchTags: Tag.Search[], rootPath: string): Gallery.Item[] {
+ const byOperation = this.extractTagsByOperation(searchTags);
+ const intersection = this.extractIntersection(byOperation);
+ const substraction = this.extractSubstraction(byOperation);
+ return this.aggregateAll(byOperation, intersection, substraction)
+ .filter(item => item.path.startsWith(rootPath));
+ }
+
+ private static extractTagsByOperation(searchTags: Tag.Search[]): Tag.SearchByOperation {
+ let byOperation: Tag.SearchByOperation = {};
+ Object.values(Operation).forEach(
+ operation => (byOperation[operation] = searchTags.filter(tag => tag.operation === operation))
+ );
+ return byOperation;
+ }
+
+ private static extractIntersection(byOperation: Tag.SearchByOperation): Set {
+ let intersection = new Set();
+ if (byOperation[Operation.INTERSECTION].length > 0) {
+ byOperation[Operation.INTERSECTION]
+ .map(tag => tag.items)
+ .reduce((a, b) => a.filter(c => b.includes(c)))
+ .flatMap(items => items)
+ .forEach(item => intersection.add(item));
+ }
+ return intersection;
+ }
+
+ private static extractSubstraction(byOperation: Tag.SearchByOperation): Set {
+ let substraction = new Set();
+ if (byOperation[Operation.SUBSTRACTION].length > 0) {
+ byOperation[Operation.SUBSTRACTION].flatMap(tag => tag.items).forEach(item => substraction.add(item));
+ }
+ return substraction;
+ }
+
+ private static aggregateAll(
+ byOperation: Tag.SearchByOperation,
+ intersection: Set,
+ substraction: Set
+ ): Gallery.Item[] {
+ byOperation[Operation.ADDITION].flatMap(tag => tag.items).forEach(item => intersection.add(item));
+ substraction.forEach(item => intersection.delete(item));
+ return [...intersection];
+ }
+}
diff --git a/viewer/src/services/navigation.ts b/viewer/src/services/navigation.ts
new file mode 100644
index 0000000..77fa47a
--- /dev/null
+++ b/viewer/src/services/navigation.ts
@@ -0,0 +1,71 @@
+/* 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 .
+*/
+
+export default class Navigation {
+
+ // Searches for an item by path from a root item (navigation)
+ public static searchCurrentItemPath(root: Gallery.Item, path: string): Gallery.Item[] {
+ if (path === root.path) return [root];
+ if (root.properties.type === "directory" && path.startsWith(root.path)) {
+ const itemChain = root.properties.items
+ .map(item => this.searchCurrentItemPath(item, path))
+ .find(itemChain => itemChain.length > 0);
+ if (itemChain) return [root, ...itemChain];
+ }
+ return [];
+ }
+
+
+ // Normalize a string to lowercase, no-accents
+ public static normalize(value: string) {
+ return value
+ .normalize("NFD")
+ .replace(/[\u0300-\u036f]/g, "")
+ .toLowerCase();
+ }
+
+
+ public static checkType(item: Gallery.Item | null, type: Gallery.ItemType): boolean {
+ return item?.properties.type === type ?? false;
+ }
+
+ public static directoriesFirst(items: Gallery.Item[]) {
+ return [
+ ...items
+ .filter(child => Navigation.checkType(child, "directory"))
+ .sort((a, b) => a.title.localeCompare(b.title)),
+
+ ...items
+ .filter(child => !Navigation.checkType(child, "directory")),
+ ];
+ }
+
+ public static getIcon(item: Gallery.Item): string {
+ if (item.path.length <= 1) return "home";
+ switch (item.properties.type) {
+ case "picture":
+ return "image";
+ case "directory":
+ return "folder";
+ case "other":
+ default:
+ return "file";
+ }
+ }
+}
--
cgit v1.2.3