From 00510820a2794efcadbc83f7f8b54318fe198ecb Mon Sep 17 00:00:00 2001 From: Zéro~Informatique Date: Tue, 26 Jul 2022 08:44:34 +0200 Subject: viewer: migrate to vue 3, general refactoring and cleanup Non-exhaustive list of fixes and improvements done at the same time: - html default background to grey (avoids white flash during init) - unified links behavior - added more theme variables - removed the flex-expand transition (it wasn't working) and replaced it with a slide - fixed LdLoading not centered on the content - title on removable tags - fixed an issue with encoded URI from vue-router - unified Item resource URLs - removed the iframe for PlainTextViewer (it wasn't working properly) and replaced it with a pre - fixed clear and search buttons tabindex - fixed the information panel bumping up during the fade animation of tag's dropdown - fixed some focus outlines not appearing correctly - moved CSS variables to the :root context - Code cleaning GitHub: closes #217 GitHub: closes #300 GitHub: closes #297 GitHub: closes #105 GitHub: closes #267 GitHub: closes #275 GitHub: closes #228 GitHub: closes #215 GitHub: closes #112 --- viewer/src/services/indexFactory.ts | 163 ++++++++++++++++++++++++++++++++++++ 1 file changed, 163 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..a414856 --- /dev/null +++ b/viewer/src/services/indexFactory.ts @@ -0,0 +1,163 @@ +/* ldgallery - A static generator which turns a collection of tagged +-- pictures into a searchable web gallery. +-- +-- Copyright (C) 2019-2022 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 { Item, RawTag } from '@/@types/gallery'; +import { Operation } from '@/@types/operation'; +import { TagCategory, TagIndex, TagNode, TagSearch } from '@/@types/tag'; +import { isDirectory } from './itemGuards'; +import { useNavigation } from './navigation'; + +const navigation = useNavigation(); + +function _pushPartToIndex(index: TagNode, part: string, item: Item, rootPart: boolean): TagNode { + 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; +} + +// Pushes all tags for a root item (and its children) to the index +function _pushTagsForItem(tagsIndex: TagIndex, item: Item): void { + if (isDirectory(item)) { + item.properties.items.forEach(item => _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) { + tagsIndex[part] = _pushPartToIndex(tagsIndex[part], part, item, !lastPart); + if (lastPart) { + const children = tagsIndex[lastPart].children; + children[part] = _pushPartToIndex(children[part], part, item, false); + } + lastPart = part; + } + if (lastPart) tagsIndex[lastPart].childPart = true; + } +} + +function _extractOperation(filter: string): Operation { + const first = filter.slice(0, 1); + switch (first) { + case Operation.ADDITION: + case Operation.SUBSTRACTION: + return first; + default: + return Operation.INTERSECTION; + } +} + +function _searchTagsFromFilterWithCategory( + tagsIndex: TagIndex, + operation: Operation, + category: string, + disambiguation: string, + strict: boolean, +): TagSearch[] { + category = navigation.normalize(category); + disambiguation = navigation.normalize(disambiguation); + return Object.values(tagsIndex) + .filter(node => _matches(node, category, strict)) + .flatMap(node => + Object.values(node.children) + .filter(child => _matches(child, disambiguation, strict)) + .map(child => ({ ...child, parent: node, operation, display: `${operation}${node.tag}:${child.tag}` })), + ); +} + +function _searchTagsFromFilter( + tagsIndex: TagIndex, + operation: Operation, + filter: string, + strict: boolean, +): TagSearch[] { + filter = navigation.normalize(filter); + return Object.values(tagsIndex) + .filter(node => _matches(node, filter, strict)) + .map(node => ({ ...node, operation, display: `${operation}${node.tag}` })); +} + +function _matches(node: TagNode, filter: string, strict: boolean): boolean { + if (strict) return node.tagfiltered === filter; + return node.tagfiltered.includes(filter); +} + +function _isDiscriminantTagOnly(tags: RawTag[], node: TagNode): boolean { + return !tags.includes(node.tag) || !node.childPart; +} + +// --- + +export const useIndexFactory = () => { + function generateTags(root: Item | null): TagIndex { + const tagsIndex: TagIndex = {}; + if (root) _pushTagsForItem(tagsIndex, root); + return tagsIndex; + } + + function searchTags(tagsIndex: TagIndex, filter: string, strict: boolean): TagSearch[] { + let search: TagSearch[] = []; + if (tagsIndex && filter) { + const operation = _extractOperation(filter); + if (operation !== Operation.INTERSECTION) filter = filter.slice(1); + if (filter.includes(':')) { + const filterParts = filter.split(':'); + search = _searchTagsFromFilterWithCategory(tagsIndex, operation, filterParts[0], filterParts[1], strict); + } else { + search = _searchTagsFromFilter(tagsIndex, operation, filter, strict); + } + } + return search; + } + + function generateCategories(tagsIndex: TagIndex, categoryTags?: RawTag[]): TagCategory[] { + if (!categoryTags?.length) return [{ tag: '', index: tagsIndex }]; + + const tagsCategories: TagCategory[] = []; + const tagsRemaining = new Map(Object.entries(tagsIndex)); + 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 => _isDiscriminantTagOnly(categoryTags, tagsIndex[tag])) + .forEach(tag => tagsRemaining.delete(tag)); + }); + tagsCategories.push({ tag: '', index: Object.fromEntries(tagsRemaining) }); + return tagsCategories; + } + + return { + generateTags, + searchTags, + generateCategories, + }; +}; -- cgit v1.2.3