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 From b252a96d47529749bb1d5e7a8d06fb7ce284b4ee Mon Sep 17 00:00:00 2001 From: Zero~Informatique Date: Sun, 23 Feb 2020 18:19:33 +0100 Subject: viewer: searching when viewing a picture will return to the parent directory Code review fix --- viewer/src/services/navigation.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'viewer/src/services') diff --git a/viewer/src/services/navigation.ts b/viewer/src/services/navigation.ts index 77fa47a..fa17990 100644 --- a/viewer/src/services/navigation.ts +++ b/viewer/src/services/navigation.ts @@ -31,7 +31,6 @@ export default class Navigation { return []; } - // Normalize a string to lowercase, no-accents public static normalize(value: string) { return value @@ -40,11 +39,20 @@ export default class Navigation { .toLowerCase(); } - + // Checks if the type of an item matches public static checkType(item: Gallery.Item | null, type: Gallery.ItemType): boolean { return item?.properties.type === type ?? false; } + public static getLastDirectory(itemPath: Gallery.Item[]): Gallery.Directory { + for (let idx = itemPath.length - 1; idx >= 0; idx--) { + const item = itemPath[idx]; + if (Navigation.checkType(item, "directory")) return item as Gallery.Directory; + } + throw new Error("No directory found"); + } + + // Sort a list of items, moving the directories to the beginning of the list public static directoriesFirst(items: Gallery.Item[]) { return [ ...items @@ -56,6 +64,7 @@ export default class Navigation { ]; } + // Get the icon for an item public static getIcon(item: Gallery.Item): string { if (item.path.length <= 1) return "home"; switch (item.properties.type) { -- cgit v1.2.3 From 4641f35baebd618ec51fa549adf64670c31c647f Mon Sep 17 00:00:00 2001 From: Zero~Informatique Date: Thu, 27 Feb 2020 17:42:24 +0100 Subject: viewer: added a count of results found in other folders when no-results are found --- viewer/src/services/indexsearch.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'viewer/src/services') diff --git a/viewer/src/services/indexsearch.ts b/viewer/src/services/indexsearch.ts index 3e73fb1..cd3383a 100644 --- a/viewer/src/services/indexsearch.ts +++ b/viewer/src/services/indexsearch.ts @@ -22,12 +22,11 @@ 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[] { + public static search(searchTags: Tag.Search[]): 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)); + return this.aggregateAll(byOperation, intersection, substraction); } private static extractTagsByOperation(searchTags: Tag.Search[]): Tag.SearchByOperation { -- 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') 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') 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') 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') 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') 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') 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') 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 b86d96f2ed5dd4f17b047e8aba22512400484bb3 Mon Sep 17 00:00:00 2001 From: pacien Date: Sun, 26 Apr 2020 06:02:33 +0200 Subject: viewer/LdPicture: implement mousewheel zoom GitHub: closes #153 --- viewer/src/services/ldzoom.ts | 92 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 viewer/src/services/ldzoom.ts (limited to 'viewer/src/services') diff --git a/viewer/src/services/ldzoom.ts b/viewer/src/services/ldzoom.ts new file mode 100644 index 0000000..f001805 --- /dev/null +++ b/viewer/src/services/ldzoom.ts @@ -0,0 +1,92 @@ +/* ldgallery - A static generator which turns a collection of tagged +-- pictures into a searchable web gallery. +-- +-- Copyright (C) 2020 Pacien TRAN-GIRARD +-- +-- 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 . +*/ + +// polyfill still required for IE and Safari, see https://caniuse.com/#feat=resizeobserver +import ResizeObserver from 'resize-observer-polyfill'; + +/** + * Mousewheel picture zoom helper. + */ +export default class LdZoom { + readonly containerElement: HTMLDivElement; + readonly imageElement: HTMLImageElement; + readonly maxScaleFactor: number; + readonly zoomSpeed: number; + scaleFactor: number; + + constructor( + containerElement: HTMLDivElement, imageElement: HTMLImageElement, + maxScaleFactor: number, zoomSpeed: number + ) { + this.containerElement = containerElement; + this.imageElement = imageElement; + this.maxScaleFactor = maxScaleFactor; + this.zoomSpeed = zoomSpeed; + this.scaleFactor = imageElement.clientWidth / imageElement.naturalWidth; + } + + public install() { + new ResizeObserver(() => { + this.setImageScale(this.scaleFactor); + this.recenterImageElement(); + }).observe(this.containerElement); + + this.containerElement.addEventListener('wheel', wheelEvent => { + wheelEvent.preventDefault(); + this.zoom(wheelEvent); + }); + + // TODO: handle pinch-to-zoom. + + this.recenterImageElement(); + } + + /** + * Centers the image element inside its container if it fits, or stick to the top and left borders otherwise. + * It's depressingly hard to do in pure CSS… + */ + private recenterImageElement() { + const marginLeft = Math.max((this.containerElement.clientWidth - this.imageElement.clientWidth) / 2, 0); + const marginTop = Math.max((this.containerElement.clientHeight - this.imageElement.clientHeight) / 2, 0); + this.imageElement.style.marginLeft = `${marginLeft}px`; + this.imageElement.style.marginTop = `${marginTop}px`; + } + + private zoom(wheelEvent: WheelEvent) { + const ratioX = wheelEvent.offsetX / this.imageElement.clientWidth; + const ratioY = wheelEvent.offsetY / this.imageElement.clientHeight; + + const zoomDelta = -Math.sign(wheelEvent.deltaY) * this.zoomSpeed; + this.setImageScale(Math.min(this.scaleFactor + zoomDelta, this.maxScaleFactor)); + + this.containerElement.scrollLeft -= wheelEvent.offsetX - ratioX * this.imageElement.clientWidth; + this.containerElement.scrollTop -= wheelEvent.offsetY - ratioY * this.imageElement.clientHeight; + } + + private setImageScale(newScaleFactor: number) { + const horizontalFillRatio = this.containerElement.clientWidth / this.imageElement.naturalWidth; + const verticalFillRatio = this.containerElement.clientHeight / this.imageElement.naturalHeight; + const minScaleFactor = Math.min(horizontalFillRatio, verticalFillRatio, 1.0); + this.scaleFactor = Math.max(newScaleFactor, minScaleFactor); + + this.imageElement.width = this.scaleFactor * this.imageElement.naturalWidth; + this.imageElement.height = this.scaleFactor * this.imageElement.naturalHeight; + this.recenterImageElement(); + } +} -- cgit v1.2.3 From f8a1763c3bee0e236c86ba9f6b46aceb212dea10 Mon Sep 17 00:00:00 2001 From: pacien Date: Sun, 26 Apr 2020 20:01:46 +0200 Subject: viewer/LdPicture: initially fit image in viewport --- viewer/src/services/ldzoom.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'viewer/src/services') diff --git a/viewer/src/services/ldzoom.ts b/viewer/src/services/ldzoom.ts index f001805..61b5dc6 100644 --- a/viewer/src/services/ldzoom.ts +++ b/viewer/src/services/ldzoom.ts @@ -28,7 +28,7 @@ export default class LdZoom { readonly imageElement: HTMLImageElement; readonly maxScaleFactor: number; readonly zoomSpeed: number; - scaleFactor: number; + scaleFactor: number = 0.0; constructor( containerElement: HTMLDivElement, imageElement: HTMLImageElement, @@ -38,9 +38,12 @@ export default class LdZoom { this.imageElement = imageElement; this.maxScaleFactor = maxScaleFactor; this.zoomSpeed = zoomSpeed; - this.scaleFactor = imageElement.clientWidth / imageElement.naturalWidth; } + /** + * Register event listeners. + * The dimension of the image should be known before calling this method. + */ public install() { new ResizeObserver(() => { this.setImageScale(this.scaleFactor); @@ -53,8 +56,6 @@ export default class LdZoom { }); // TODO: handle pinch-to-zoom. - - this.recenterImageElement(); } /** -- cgit v1.2.3 From 29d432e64e0482935ef91dbfed37d4d4cf26c42f Mon Sep 17 00:00:00 2001 From: pacien Date: Sun, 26 Apr 2020 21:51:37 +0200 Subject: viewer/LdZoom: add support for pinch-to-zoom GitHub: closes #106 --- viewer/src/services/ldzoom.ts | 46 ++++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 14 deletions(-) (limited to 'viewer/src/services') diff --git a/viewer/src/services/ldzoom.ts b/viewer/src/services/ldzoom.ts index 61b5dc6..27debb5 100644 --- a/viewer/src/services/ldzoom.ts +++ b/viewer/src/services/ldzoom.ts @@ -19,25 +19,26 @@ // polyfill still required for IE and Safari, see https://caniuse.com/#feat=resizeobserver import ResizeObserver from 'resize-observer-polyfill'; +import "hammerjs"; /** - * Mousewheel picture zoom helper. + * Mousewheel and pinch zoom handler. */ export default class LdZoom { readonly containerElement: HTMLDivElement; readonly imageElement: HTMLImageElement; readonly maxScaleFactor: number; - readonly zoomSpeed: number; + readonly scrollZoomSpeed: number; scaleFactor: number = 0.0; constructor( containerElement: HTMLDivElement, imageElement: HTMLImageElement, - maxScaleFactor: number, zoomSpeed: number + maxScaleFactor: number, scrollZoomSpeed: number ) { this.containerElement = containerElement; this.imageElement = imageElement; this.maxScaleFactor = maxScaleFactor; - this.zoomSpeed = zoomSpeed; + this.scrollZoomSpeed = scrollZoomSpeed; } /** @@ -52,10 +53,30 @@ export default class LdZoom { this.containerElement.addEventListener('wheel', wheelEvent => { wheelEvent.preventDefault(); - this.zoom(wheelEvent); + const zoomDelta = -Math.sign(wheelEvent.deltaY) * this.scrollZoomSpeed; + this.zoom(wheelEvent.offsetX, wheelEvent.offsetY, zoomDelta); }); - // TODO: handle pinch-to-zoom. + const pinchListener = new Hammer(this.containerElement); + pinchListener.get('pinch').set({enable: true}); + this.installPinchHandler(pinchListener); + } + + private installPinchHandler(pinchListener: HammerManager) { + let lastScaleFactor = 0.0; + + pinchListener.on('pinchstart', (pinchEvent: HammerInput) => { + lastScaleFactor = pinchEvent.scale; + }); + + pinchListener.on('pinchmove', (pinchEvent: HammerInput) => { + // FIXME: pinchEvent.center isn't always well-centered + const focusX = pinchEvent.center.x + this.containerElement.scrollLeft; + const focusY = pinchEvent.center.y + this.containerElement.scrollTop; + const zoomDelta = pinchEvent.scale - lastScaleFactor; + lastScaleFactor = pinchEvent.scale; + this.zoom(focusX, focusY, zoomDelta); + }); } /** @@ -69,15 +90,12 @@ export default class LdZoom { this.imageElement.style.marginTop = `${marginTop}px`; } - private zoom(wheelEvent: WheelEvent) { - const ratioX = wheelEvent.offsetX / this.imageElement.clientWidth; - const ratioY = wheelEvent.offsetY / this.imageElement.clientHeight; - - const zoomDelta = -Math.sign(wheelEvent.deltaY) * this.zoomSpeed; + private zoom(focusX: number, focusY: number, zoomDelta: number) { + const ratioX = focusX / this.imageElement.clientWidth; + const ratioY = focusY / this.imageElement.clientHeight; this.setImageScale(Math.min(this.scaleFactor + zoomDelta, this.maxScaleFactor)); - - this.containerElement.scrollLeft -= wheelEvent.offsetX - ratioX * this.imageElement.clientWidth; - this.containerElement.scrollTop -= wheelEvent.offsetY - ratioY * this.imageElement.clientHeight; + this.containerElement.scrollLeft -= focusX - ratioX * this.imageElement.clientWidth; + this.containerElement.scrollTop -= focusY - ratioY * this.imageElement.clientHeight; } private setImageScale(newScaleFactor: number) { -- cgit v1.2.3 From 058d6b945c06b6daa2824ac837fc6ec003bc9fa6 Mon Sep 17 00:00:00 2001 From: pacien Date: Mon, 27 Apr 2020 22:20:47 +0200 Subject: viewer/LdZoom: properly scale with pinch-to-zoom Previous scaling was too aggressive with large pictures. --- viewer/src/services/ldzoom.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) (limited to 'viewer/src/services') diff --git a/viewer/src/services/ldzoom.ts b/viewer/src/services/ldzoom.ts index 27debb5..a494211 100644 --- a/viewer/src/services/ldzoom.ts +++ b/viewer/src/services/ldzoom.ts @@ -53,8 +53,8 @@ export default class LdZoom { this.containerElement.addEventListener('wheel', wheelEvent => { wheelEvent.preventDefault(); - const zoomDelta = -Math.sign(wheelEvent.deltaY) * this.scrollZoomSpeed; - this.zoom(wheelEvent.offsetX, wheelEvent.offsetY, zoomDelta); + const scaleFactor = this.scaleFactor - Math.sign(wheelEvent.deltaY) * this.scrollZoomSpeed; + this.zoom(wheelEvent.offsetX, wheelEvent.offsetY, scaleFactor); }); const pinchListener = new Hammer(this.containerElement); @@ -63,19 +63,18 @@ export default class LdZoom { } private installPinchHandler(pinchListener: HammerManager) { - let lastScaleFactor = 0.0; + let startScaleFactor = 0.0; pinchListener.on('pinchstart', (pinchEvent: HammerInput) => { - lastScaleFactor = pinchEvent.scale; + startScaleFactor = this.scaleFactor; }); pinchListener.on('pinchmove', (pinchEvent: HammerInput) => { - // FIXME: pinchEvent.center isn't always well-centered + // FIXME: v-dragscroll interferes with our focus point scroll adjustment const focusX = pinchEvent.center.x + this.containerElement.scrollLeft; const focusY = pinchEvent.center.y + this.containerElement.scrollTop; - const zoomDelta = pinchEvent.scale - lastScaleFactor; - lastScaleFactor = pinchEvent.scale; - this.zoom(focusX, focusY, zoomDelta); + const scaleFactor = pinchEvent.scale * startScaleFactor; + this.zoom(focusX, focusY, scaleFactor); }); } @@ -90,10 +89,10 @@ export default class LdZoom { this.imageElement.style.marginTop = `${marginTop}px`; } - private zoom(focusX: number, focusY: number, zoomDelta: number) { + private zoom(focusX: number, focusY: number, scaleFactor: number) { const ratioX = focusX / this.imageElement.clientWidth; const ratioY = focusY / this.imageElement.clientHeight; - this.setImageScale(Math.min(this.scaleFactor + zoomDelta, this.maxScaleFactor)); + this.setImageScale(Math.min(scaleFactor, this.maxScaleFactor)); this.containerElement.scrollLeft -= focusX - ratioX * this.imageElement.clientWidth; this.containerElement.scrollTop -= focusY - ratioY * this.imageElement.clientHeight; } -- cgit v1.2.3 From bda84785d3d04c0d1d471a1cf9c38363541b64a8 Mon Sep 17 00:00:00 2001 From: pacien Date: Mon, 27 Apr 2020 22:26:56 +0200 Subject: viewer/LdZoom: set image scale on init To prevent some init glitch from happening. --- viewer/src/services/ldzoom.ts | 2 ++ 1 file changed, 2 insertions(+) (limited to 'viewer/src/services') diff --git a/viewer/src/services/ldzoom.ts b/viewer/src/services/ldzoom.ts index a494211..50f006e 100644 --- a/viewer/src/services/ldzoom.ts +++ b/viewer/src/services/ldzoom.ts @@ -60,6 +60,8 @@ export default class LdZoom { const pinchListener = new Hammer(this.containerElement); pinchListener.get('pinch').set({enable: true}); this.installPinchHandler(pinchListener); + + this.setImageScale(this.scaleFactor); } private installPinchHandler(pinchListener: HammerManager) { -- cgit v1.2.3 From b4b698ccbdec98dd902b6290f12207bf5547b140 Mon Sep 17 00:00:00 2001 From: pacien Date: Tue, 28 Apr 2020 01:51:08 +0200 Subject: viewer/LdPicture: fix centering in loading phase --- viewer/src/services/ldzoom.ts | 60 +++++++++++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 19 deletions(-) (limited to 'viewer/src/services') diff --git a/viewer/src/services/ldzoom.ts b/viewer/src/services/ldzoom.ts index 50f006e..c28c2c8 100644 --- a/viewer/src/services/ldzoom.ts +++ b/viewer/src/services/ldzoom.ts @@ -27,28 +27,31 @@ import "hammerjs"; export default class LdZoom { readonly containerElement: HTMLDivElement; readonly imageElement: HTMLImageElement; + readonly pictureProperties: Gallery.PictureProperties; readonly maxScaleFactor: number; readonly scrollZoomSpeed: number; scaleFactor: number = 0.0; constructor( containerElement: HTMLDivElement, imageElement: HTMLImageElement, + pictureProperties: Gallery.PictureProperties, maxScaleFactor: number, scrollZoomSpeed: number ) { this.containerElement = containerElement; this.imageElement = imageElement; + this.pictureProperties = pictureProperties; this.maxScaleFactor = maxScaleFactor; this.scrollZoomSpeed = scrollZoomSpeed; } /** * Register event listeners. - * The dimension of the image should be known before calling this method. */ public install() { + this.updateImageScale(this.scaleFactor); + new ResizeObserver(() => { - this.setImageScale(this.scaleFactor); - this.recenterImageElement(); + this.updateImageScale(this.scaleFactor); }).observe(this.containerElement); this.containerElement.addEventListener('wheel', wheelEvent => { @@ -60,8 +63,6 @@ export default class LdZoom { const pinchListener = new Hammer(this.containerElement); pinchListener.get('pinch').set({enable: true}); this.installPinchHandler(pinchListener); - - this.setImageScale(this.scaleFactor); } private installPinchHandler(pinchListener: HammerManager) { @@ -72,7 +73,6 @@ export default class LdZoom { }); pinchListener.on('pinchmove', (pinchEvent: HammerInput) => { - // FIXME: v-dragscroll interferes with our focus point scroll adjustment const focusX = pinchEvent.center.x + this.containerElement.scrollLeft; const focusY = pinchEvent.center.y + this.containerElement.scrollTop; const scaleFactor = pinchEvent.scale * startScaleFactor; @@ -80,33 +80,55 @@ export default class LdZoom { }); } + /** + * Returns the picture resolution as it should be displayed. + */ + private getDisplayResolution(): Gallery.Resolution { + return { + width: this.pictureProperties.resolution.width * this.scaleFactor, + height: this.pictureProperties.resolution.height * this.scaleFactor, + }; + } + + /** + * Applies scaling to the DOM image element. + * To call after internal intermediate computations because DOM properties aren't stable. + */ + private resizeImageElement() { + const imageDim = this.getDisplayResolution(); + this.imageElement.width = imageDim.width; + this.imageElement.height = imageDim.height; + } + /** * Centers the image element inside its container if it fits, or stick to the top and left borders otherwise. * It's depressingly hard to do in pure CSS… */ private recenterImageElement() { - const marginLeft = Math.max((this.containerElement.clientWidth - this.imageElement.clientWidth) / 2, 0); - const marginTop = Math.max((this.containerElement.clientHeight - this.imageElement.clientHeight) / 2, 0); + const imageDim = this.getDisplayResolution(); + const marginLeft = Math.max((this.containerElement.clientWidth - imageDim.width) / 2, 0); + const marginTop = Math.max((this.containerElement.clientHeight - imageDim.height) / 2, 0); this.imageElement.style.marginLeft = `${marginLeft}px`; this.imageElement.style.marginTop = `${marginTop}px`; } private zoom(focusX: number, focusY: number, scaleFactor: number) { - const ratioX = focusX / this.imageElement.clientWidth; - const ratioY = focusY / this.imageElement.clientHeight; - this.setImageScale(Math.min(scaleFactor, this.maxScaleFactor)); - this.containerElement.scrollLeft -= focusX - ratioX * this.imageElement.clientWidth; - this.containerElement.scrollTop -= focusY - ratioY * this.imageElement.clientHeight; + const imageDim = this.getDisplayResolution(); + const ratioX = focusX / imageDim.width; + const ratioY = focusY / imageDim.height; + this.updateImageScale(Math.min(scaleFactor, this.maxScaleFactor)); + + const newImageDim = this.getDisplayResolution(); + this.containerElement.scrollLeft -= focusX - ratioX * newImageDim.width; + this.containerElement.scrollTop -= focusY - ratioY * newImageDim.height; } - private setImageScale(newScaleFactor: number) { - const horizontalFillRatio = this.containerElement.clientWidth / this.imageElement.naturalWidth; - const verticalFillRatio = this.containerElement.clientHeight / this.imageElement.naturalHeight; + private updateImageScale(newScaleFactor: number) { + const horizontalFillRatio = this.containerElement.clientWidth / this.pictureProperties.resolution.width; + const verticalFillRatio = this.containerElement.clientHeight / this.pictureProperties.resolution.height; const minScaleFactor = Math.min(horizontalFillRatio, verticalFillRatio, 1.0); this.scaleFactor = Math.max(newScaleFactor, minScaleFactor); - - this.imageElement.width = this.scaleFactor * this.imageElement.naturalWidth; - this.imageElement.height = this.scaleFactor * this.imageElement.naturalHeight; + this.resizeImageElement(); this.recenterImageElement(); } } -- 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 +++--- viewer/src/services/indexsearch.ts | 2 +- viewer/src/services/ldzoom.ts | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) (limited to 'viewer/src/services') 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)); diff --git a/viewer/src/services/indexsearch.ts b/viewer/src/services/indexsearch.ts index cd3383a..a55a829 100644 --- a/viewer/src/services/indexsearch.ts +++ b/viewer/src/services/indexsearch.ts @@ -17,7 +17,7 @@ -- along with this program. If not, see . */ -import { Operation } from '@/@types/Operation'; +import { Operation } from "@/@types/Operation"; export default class IndexSearch { diff --git a/viewer/src/services/ldzoom.ts b/viewer/src/services/ldzoom.ts index c28c2c8..ddf57c0 100644 --- a/viewer/src/services/ldzoom.ts +++ b/viewer/src/services/ldzoom.ts @@ -18,7 +18,7 @@ */ // polyfill still required for IE and Safari, see https://caniuse.com/#feat=resizeobserver -import ResizeObserver from 'resize-observer-polyfill'; +import ResizeObserver from "resize-observer-polyfill"; import "hammerjs"; /** @@ -54,25 +54,25 @@ export default class LdZoom { this.updateImageScale(this.scaleFactor); }).observe(this.containerElement); - this.containerElement.addEventListener('wheel', wheelEvent => { + this.containerElement.addEventListener("wheel", wheelEvent => { wheelEvent.preventDefault(); const scaleFactor = this.scaleFactor - Math.sign(wheelEvent.deltaY) * this.scrollZoomSpeed; this.zoom(wheelEvent.offsetX, wheelEvent.offsetY, scaleFactor); }); const pinchListener = new Hammer(this.containerElement); - pinchListener.get('pinch').set({enable: true}); + pinchListener.get("pinch").set({ enable: true }); this.installPinchHandler(pinchListener); } private installPinchHandler(pinchListener: HammerManager) { let startScaleFactor = 0.0; - pinchListener.on('pinchstart', (pinchEvent: HammerInput) => { + pinchListener.on("pinchstart", (pinchEvent: HammerInput) => { startScaleFactor = this.scaleFactor; }); - pinchListener.on('pinchmove', (pinchEvent: HammerInput) => { + pinchListener.on("pinchmove", (pinchEvent: HammerInput) => { const focusX = pinchEvent.center.x + this.containerElement.scrollLeft; const focusY = pinchEvent.center.y + this.containerElement.scrollTop; const scaleFactor = pinchEvent.scale * startScaleFactor; -- cgit v1.2.3