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