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/package-lock.json | 63 ++++++++---------
viewer/package.json | 14 ++--
viewer/src/assets/scss/global.scss | 9 ++-
viewer/src/assets/scss/theme.scss | 15 ++--
viewer/src/components/LdBreadcrumb.vue | 17 +++--
viewer/src/components/LdCommand.vue | 16 ++---
viewer/src/components/LdCommandSearch.vue | 55 +++++++++++++++
viewer/src/components/LdGallery.vue | 47 +++++++++++++
viewer/src/components/LdModeRadio.vue | 41 -----------
viewer/src/components/LdPicture.vue | 2 +-
viewer/src/components/LdProposition.vue | 35 +++++-----
viewer/src/components/LdTagInput.vue | 75 +++-----------------
viewer/src/components/LdThumbnail.vue | 4 +-
viewer/src/dragscrollclickfix.ts | 52 --------------
viewer/src/locales/en.json | 10 +--
viewer/src/plugins/buefy.ts | 4 +-
viewer/src/plugins/fontawesome.ts | 2 +
viewer/src/plugins/router.ts | 17 ++---
viewer/src/services/dragscrollclickfix.ts | 52 ++++++++++++++
viewer/src/services/indexfactory.ts | 101 +++++++++++++++++++++++++++
viewer/src/services/indexsearch.ts | 70 +++++++++++++++++++
viewer/src/services/navigation.ts | 71 +++++++++++++++++++
viewer/src/store/galleryStore.ts | 51 +++-----------
viewer/src/store/uiStore.ts | 22 +-----
viewer/src/tools.ts | 59 ----------------
viewer/src/views/GalleryDirectory.vue | 14 ++--
viewer/src/views/GalleryNavigation.vue | 63 +++++++++++++++++
viewer/src/views/GallerySearch.vue | 36 +++++++---
viewer/src/views/MainGallery.vue | 110 ------------------------------
viewer/src/views/MainLayout.vue | 4 --
viewer/src/views/PanelLeft.vue | 34 ++++++++-
viewer/src/views/PanelTop.vue | 7 +-
viewer/visualstudio.code-workspace | 1 +
33 files changed, 655 insertions(+), 518 deletions(-)
create mode 100644 viewer/src/components/LdCommandSearch.vue
create mode 100644 viewer/src/components/LdGallery.vue
delete mode 100644 viewer/src/components/LdModeRadio.vue
delete mode 100644 viewer/src/dragscrollclickfix.ts
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
delete mode 100644 viewer/src/tools.ts
create mode 100644 viewer/src/views/GalleryNavigation.vue
delete mode 100644 viewer/src/views/MainGallery.vue
diff --git a/viewer/package-lock.json b/viewer/package-lock.json
index 87825c8..955e21e 100644
--- a/viewer/package-lock.json
+++ b/viewer/package-lock.json
@@ -1283,12 +1283,12 @@
}
},
"@typescript-eslint/eslint-plugin": {
- "version": "2.19.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.19.2.tgz",
- "integrity": "sha512-HX2qOq2GOV04HNrmKnTpSIpHjfl7iwdXe3u/Nvt+/cpmdvzYvY0NHSiTkYN257jHnq4OM/yo+OsFgati+7LqJA==",
+ "version": "2.20.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.20.0.tgz",
+ "integrity": "sha512-cimIdVDV3MakiGJqMXw51Xci6oEDEoPkvh8ggJe2IIzcc0fYqAxOXN6Vbeanahz6dLZq64W+40iUEc9g32FLDQ==",
"dev": true,
"requires": {
- "@typescript-eslint/experimental-utils": "2.19.2",
+ "@typescript-eslint/experimental-utils": "2.20.0",
"eslint-utils": "^1.4.3",
"functional-red-black-tree": "^1.0.1",
"regexpp": "^3.0.0",
@@ -1296,32 +1296,32 @@
}
},
"@typescript-eslint/experimental-utils": {
- "version": "2.19.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.19.2.tgz",
- "integrity": "sha512-B88QuwT1wMJR750YvTJBNjMZwmiPpbmKYLm1yI7PCc3x0NariqPwqaPsoJRwU9DmUi0cd9dkhz1IqEnwfD+P1A==",
+ "version": "2.20.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.20.0.tgz",
+ "integrity": "sha512-fEBy9xYrwG9hfBLFEwGW2lKwDRTmYzH3DwTmYbT+SMycmxAoPl0eGretnBFj/s+NfYBG63w/5c3lsvqqz5mYag==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.3",
- "@typescript-eslint/typescript-estree": "2.19.2",
+ "@typescript-eslint/typescript-estree": "2.20.0",
"eslint-scope": "^5.0.0"
}
},
"@typescript-eslint/parser": {
- "version": "2.19.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.19.2.tgz",
- "integrity": "sha512-8uwnYGKqX9wWHGPGdLB9sk9+12sjcdqEEYKGgbS8A0IvYX59h01o8os5qXUHMq2na8vpDRaV0suTLM7S8wraTA==",
+ "version": "2.20.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.20.0.tgz",
+ "integrity": "sha512-o8qsKaosLh2qhMZiHNtaHKTHyCHc3Triq6aMnwnWj7budm3xAY9owSZzV1uon5T9cWmJRJGzTFa90aex4m77Lw==",
"dev": true,
"requires": {
"@types/eslint-visitor-keys": "^1.0.0",
- "@typescript-eslint/experimental-utils": "2.19.2",
- "@typescript-eslint/typescript-estree": "2.19.2",
+ "@typescript-eslint/experimental-utils": "2.20.0",
+ "@typescript-eslint/typescript-estree": "2.20.0",
"eslint-visitor-keys": "^1.1.0"
}
},
"@typescript-eslint/typescript-estree": {
- "version": "2.19.2",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.19.2.tgz",
- "integrity": "sha512-Xu/qa0MDk6upQWqE4Qy2X16Xg8Vi32tQS2PR0AvnT/ZYS4YGDvtn2MStOh5y8Zy2mg4NuL06KUHlvCh95j9C6Q==",
+ "version": "2.20.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.20.0.tgz",
+ "integrity": "sha512-WlFk8QtI8pPaE7JGQGxU7nGcnk1ccKAJkhbVookv94ZcAef3m6oCE/jEDL6dGte3JcD7reKrA0o55XhBRiVT3A==",
"dev": true,
"requires": {
"debug": "^4.1.1",
@@ -2883,9 +2883,9 @@
}
},
"buefy": {
- "version": "0.8.10",
- "resolved": "https://registry.npmjs.org/buefy/-/buefy-0.8.10.tgz",
- "integrity": "sha512-Lw/UP3Ku7o+oqam9TIoRMG5SrytGQwXWAoxAtqt6Wb9eSsMEqp/5o+jZnz8oteR06YWgjdSIfOv2YeEdjEkQCg==",
+ "version": "0.8.12",
+ "resolved": "https://registry.npmjs.org/buefy/-/buefy-0.8.12.tgz",
+ "integrity": "sha512-scKb+0piTAEYk8mopu5HzshlGsT0K9ChlfkGhQgAF7jC9lH3Ta7anYXG+l8uBoSpqgChK0H19jPP55FbeKn1nA==",
"requires": {
"bulma": "0.7.5"
}
@@ -4881,11 +4881,12 @@
}
},
"eslint-plugin-vue": {
- "version": "6.1.2",
- "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-6.1.2.tgz",
- "integrity": "sha512-M75oAB+2a/LNkLKRbeEaS07EjzjIUaV7/hYoHAfRFeeF8ZMmCbahUn8nQLsLP85mkar24+zDU3QW2iT1JRsACw==",
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-6.2.1.tgz",
+ "integrity": "sha512-MiIDOotoWseIfLIfGeDzF6sDvHkVvGd2JgkvjyHtN3q4RoxdAXrAMuI3SXTOKatljgacKwpNAYShmcKZa4yZzw==",
"dev": true,
"requires": {
+ "natural-compare": "^1.4.0",
"semver": "^5.6.0",
"vue-eslint-parser": "^7.0.0"
}
@@ -11950,9 +11951,9 @@
"dev": true
},
"typescript": {
- "version": "3.7.5",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz",
- "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==",
+ "version": "3.8.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.2.tgz",
+ "integrity": "sha512-EgOVgL/4xfVrCMbhYKUQTdF37SQn4Iw73H5BgCrF1Abdun7Kwy/QZsE/ssAy0y4LxBbvua3PIbFsbRczWWnDdQ==",
"dev": true
},
"uglify-js": {
@@ -12248,9 +12249,9 @@
"dev": true
},
"v-lazy-image": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/v-lazy-image/-/v-lazy-image-1.3.2.tgz",
- "integrity": "sha512-yZYMLoy95S4K9mWE/2DMZcwvaWnGiAHGXcKRruyrFvAdFm2fsnfyL0yj2UwXEGliNZO7I4mRy9/RB7J4CT0HAQ=="
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/v-lazy-image/-/v-lazy-image-1.4.0.tgz",
+ "integrity": "sha512-Xp/fM786hdXlP10HatvtNsvvPjW6yOsH17lvVEEuGiNMnyiafT9XVomQRRM4t+IzN21cz3SQcw9cqd486yhpgQ=="
},
"v8-compile-cache": {
"version": "2.1.0",
@@ -12303,9 +12304,9 @@
"integrity": "sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ=="
},
"vue-class-component": {
- "version": "7.2.2",
- "resolved": "https://registry.npmjs.org/vue-class-component/-/vue-class-component-7.2.2.tgz",
- "integrity": "sha512-QjVfjRffux0rUBNtxr1hvUxDrfifDvk9q/OSdB/sKIlfxAudDF2E1YTeiEC+qOYIOOBGWkgSKQSnast6H+S38w=="
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/vue-class-component/-/vue-class-component-7.2.3.tgz",
+ "integrity": "sha512-oEqYpXKaFN+TaXU+mRLEx8dX0ah85aAJEe61mpdoUrq0Bhe/6sWhyZX1JjMQLhVsHAkncyhedhmCdDVSasUtDw=="
},
"vue-cli-plugin-buefy": {
"version": "0.3.7",
diff --git a/viewer/package.json b/viewer/package.json
index 212c2e6..7218cda 100644
--- a/viewer/package.json
+++ b/viewer/package.json
@@ -13,11 +13,11 @@
"@fortawesome/fontawesome-svg-core": "^1.2.27",
"@fortawesome/free-solid-svg-icons": "^5.12.1",
"@fortawesome/vue-fontawesome": "^0.1.5",
- "buefy": "^0.8.10",
+ "buefy": "^0.8.12",
"core-js": "^3.6.4",
- "v-lazy-image": "^1.3.2",
+ "v-lazy-image": "^1.4.0",
"vue": "^2.6.10",
- "vue-class-component": "^7.2.2",
+ "vue-class-component": "^7.2.3",
"vue-dragscroll": "^2.0.1",
"vue-i18n": "^8.0.0",
"vue-property-decorator": "^8.4.0",
@@ -27,8 +27,8 @@
},
"devDependencies": {
"@types/webpack": "^4.41.6",
- "@typescript-eslint/eslint-plugin": "^2.19.2",
- "@typescript-eslint/parser": "^2.19.2",
+ "@typescript-eslint/eslint-plugin": "^2.20.0",
+ "@typescript-eslint/parser": "^2.20.0",
"@vue/cli-plugin-babel": "^4.2.1",
"@vue/cli-plugin-eslint": "^4.2.1",
"@vue/cli-plugin-router": "^4.2.1",
@@ -37,10 +37,10 @@
"@vue/cli-service": "^4.2.1",
"@vue/eslint-config-typescript": "^5.0.1",
"eslint": "^6.8.0",
- "eslint-plugin-vue": "^6.1.2",
+ "eslint-plugin-vue": "^6.2.1",
"node-sass": "^4.13.1",
"sass-loader": "^8.0.2",
- "typescript": "^3.7.5",
+ "typescript": "^3.8.2",
"vue-cli-plugin-buefy": "^0.3.7",
"vue-cli-plugin-fontawesome": "^0.2.0",
"vue-cli-plugin-i18n": "^0.6.1",
diff --git a/viewer/src/assets/scss/global.scss b/viewer/src/assets/scss/global.scss
index ed69841..ea25513 100644
--- a/viewer/src/assets/scss/global.scss
+++ b/viewer/src/assets/scss/global.scss
@@ -27,6 +27,10 @@
color: red;
}
+button svg + span {
+ margin-left: 7px;
+}
+
// === Tools
.nowrap {
@@ -66,11 +70,10 @@
// Disable sticky hover styling on touch devices,
// on which the virtual cursor doesn't leave the element after being tapped.
// The fix can be applied to `a` elements by using the .link class.
-@media (hover:none), (hover:on-demand) {
+@media (hover: none), (hover: on-demand) {
.link:hover {
color: $link !important;
}
-
.disabled:hover {
color: $disabled-color !important;
}
@@ -90,7 +93,7 @@
}
.scrollbar::-webkit-scrollbar-thumb {
box-shadow: inset 0 0 1px black;
- background-color: $toolbar-color;
+ background-color: $scrollbar-color;
}
// === Thumbnail tiles alignment
diff --git a/viewer/src/assets/scss/theme.scss b/viewer/src/assets/scss/theme.scss
index 0a921a9..60504e3 100644
--- a/viewer/src/assets/scss/theme.scss
+++ b/viewer/src/assets/scss/theme.scss
@@ -18,9 +18,8 @@
-- along with this program. If not, see .
*/
-@import '_buefy_variables.scss';
-@import 'palette.scss';
-
+@import "_buefy_variables.scss";
+@import "palette.scss";
// Buefy components
@@ -44,7 +43,12 @@ $loading-background: $palette-800;
$title-color: $palette-200;
$title-size: $size-5;
$tag-background-color: $palette-800;
-
+$button-color: $palette-100;
+$button-background-color: $palette-700;
+$button-border-color: $palette-500;
+$button-focus-color: $button-color;
+$button-focus-border-color: $link;
+$button-focus-box-shadow-size: 0;
// Custom components
@@ -54,14 +58,13 @@ $panel-left-bgcolor: $palette-800;
$panel-left-txtcolor: $primary;
$command-buttons-bgcolor: $palette-700;
$content-bgcolor: $palette-900;
-$toolbar-color: $palette-300; // FIXME: should be named "scrollbar"
+$scrollbar-color: $palette-300;
$loader-color: $palette-800;
$input-tag-delete-background-color: $palette-700;
$breadcrumb-margins: 12px;
$breadcrumb-overflow-mask-size: $breadcrumb-margins + 60px;
$thumbnail-other-size: 120px;
-
// Layout
$layout-top: 45px;
diff --git a/viewer/src/components/LdBreadcrumb.vue b/viewer/src/components/LdBreadcrumb.vue
index 7f7ef7d..643bfb6 100644
--- a/viewer/src/components/LdBreadcrumb.vue
+++ b/viewer/src/components/LdBreadcrumb.vue
@@ -29,24 +29,29 @@
>
diff --git a/viewer/src/components/LdCommand.vue b/viewer/src/components/LdCommand.vue
index 7590ea7..468c241 100644
--- a/viewer/src/components/LdCommand.vue
+++ b/viewer/src/components/LdCommand.vue
@@ -23,14 +23,6 @@
-
-
-
@@ -42,21 +34,23 @@
+
+
diff --git a/viewer/src/components/LdGallery.vue b/viewer/src/components/LdGallery.vue
new file mode 100644
index 0000000..169bc54
--- /dev/null
+++ b/viewer/src/components/LdGallery.vue
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
{{noresult}}
+
+
+
+
+
+
diff --git a/viewer/src/components/LdModeRadio.vue b/viewer/src/components/LdModeRadio.vue
deleted file mode 100644
index c1d5702..0000000
--- a/viewer/src/components/LdModeRadio.vue
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-
-
-
-
- {{$t('mode.navigation')}}
-
-
-
- {{$t('mode.search')}}
-
-
-
-
-
-
-
diff --git a/viewer/src/components/LdPicture.vue b/viewer/src/components/LdPicture.vue
index a5faeb3..1cfcc8b 100644
--- a/viewer/src/components/LdPicture.vue
+++ b/viewer/src/components/LdPicture.vue
@@ -39,7 +39,7 @@
diff --git a/viewer/src/components/LdTagInput.vue b/viewer/src/components/LdTagInput.vue
index eff02e6..982abe4 100644
--- a/viewer/src/components/LdTagInput.vue
+++ b/viewer/src/components/LdTagInput.vue
@@ -19,7 +19,7 @@
{{displayOption(props.option)}}
{{$t('tagInput.nomatch')}}
@@ -39,80 +37,25 @@
diff --git a/viewer/src/dragscrollclickfix.ts b/viewer/src/dragscrollclickfix.ts
deleted file mode 100644
index 38eb106..0000000
--- a/viewer/src/dragscrollclickfix.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-/* 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/locales/en.json b/viewer/src/locales/en.json
index 9440103..878204e 100644
--- a/viewer/src/locales/en.json
+++ b/viewer/src/locales/en.json
@@ -1,8 +1,6 @@
{
"tagInput.placeholder": "Filters",
"tagInput.nomatch": "No match",
- "mode.navigation": "Navigation",
- "mode.search": "Search",
"search.no-results": "No results",
"panelLeft.propositions": "Related filters",
"tag-propositions.substraction": "Exclude items with this tag",
@@ -11,7 +9,9 @@
"tag-propositions.item-count": "Item count",
"gallery.unknowntype": "Unknown item type",
"command.search": "Open/close search panel",
- "command.home": "Go to gallery home",
+ "command.search.clear": "Clear",
+ "command.search.search": "Search",
"command.back": "Go back",
- "command.parent": "Go to parent directory"
-}
\ No newline at end of file
+ "command.parent": "Go to parent directory",
+ "directory.no-results": "Empty directory"
+}
diff --git a/viewer/src/plugins/buefy.ts b/viewer/src/plugins/buefy.ts
index 74b6176..ebdf64e 100644
--- a/viewer/src/plugins/buefy.ts
+++ b/viewer/src/plugins/buefy.ts
@@ -24,7 +24,7 @@ import Taginput from 'buefy/src/components/taginput';
// @ts-ignore
import Loading from 'buefy/src/components/loading';
// @ts-ignore
-import Radio from 'buefy/src/components/radio';
+import Button from 'buefy/src/components/button';
// @ts-ignore
import SnackBar from 'buefy/src/components/snackbar';
@@ -32,7 +32,7 @@ import "@/assets/scss/buefy.scss";
Vue.use(Taginput);
Vue.use(Loading);
-Vue.use(Radio);
+Vue.use(Button);
Vue.use(SnackBar);
declare module 'vue/types/vue' {
diff --git a/viewer/src/plugins/fontawesome.ts b/viewer/src/plugins/fontawesome.ts
index e8848f9..cc8b7ab 100644
--- a/viewer/src/plugins/fontawesome.ts
+++ b/viewer/src/plugins/fontawesome.ts
@@ -23,6 +23,7 @@ import { library, config } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import {
faFolder,
+ faEraser,
faSearch,
faPlus,
faMinus,
@@ -37,6 +38,7 @@ import {
library.add(
faFolder,
+ faEraser,
faSearch,
faPlus,
faMinus,
diff --git a/viewer/src/plugins/router.ts b/viewer/src/plugins/router.ts
index 8b4a8dc..00979c9 100644
--- a/viewer/src/plugins/router.ts
+++ b/viewer/src/plugins/router.ts
@@ -18,19 +18,20 @@
*/
import Vue from "vue";
-import VueRouter from "vue-router";
-import MainGallery from "@/views/MainGallery.vue";
+import VueRouter, { RouteConfig } from "vue-router";
+import GalleryNavigation from "@/views/GalleryNavigation.vue";
Vue.use(VueRouter);
-// async way : component: () => import(/* webpackChunkName: "MainGallery" */ "@/views/MainGallery.vue"),
-
-const routes = [
+const routes: RouteConfig[] = [
{
path: "*",
- name: "MainGallery",
- component: MainGallery,
- props: true
+ name: "GalleryNavigation",
+ component: GalleryNavigation,
+ props: (route) => ({
+ path: route.params.pathMatch,
+ query: Object.keys(route.query),
+ }),
},
];
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";
+ }
+ }
+}
diff --git a/viewer/src/store/galleryStore.ts b/viewer/src/store/galleryStore.ts
index e7d70f8..d2b28dd 100644
--- a/viewer/src/store/galleryStore.ts
+++ b/viewer/src/store/galleryStore.ts
@@ -18,7 +18,8 @@
*/
import { createModule, mutation, action } from "vuex-class-component";
-import Tools from '@/tools';
+import IndexFactory from '@/services/indexfactory';
+import Navigation from '@/services/navigation';
const VuexModule = createModule({
namespaced: "galleryStore",
@@ -29,7 +30,7 @@ export default class GalleryStore extends VuexModule {
config: Gallery.Config | null = null;
galleryItemsRoot: Gallery.Item | null = null;
- tags: Tag.Index = {};
+ tagsIndex: Tag.Index = {};
currentPath: string = "/";
// ---
@@ -42,8 +43,8 @@ export default class GalleryStore extends VuexModule {
this.galleryItemsRoot = galleryItemsRoot;
}
- @mutation private setTags(tags: Tag.Index) {
- this.tags = tags;
+ @mutation private setTagsIndex(tagsIndex: Tag.Index) {
+ this.tagsIndex = tagsIndex;
}
@mutation setCurrentPath(currentPath: string) {
@@ -53,7 +54,7 @@ export default class GalleryStore extends VuexModule {
get currentItemPath(): Gallery.Item[] {
const root = this.galleryItemsRoot;
if (root)
- return GalleryStore.searchCurrentItemPath(root, this.currentPath);
+ return Navigation.searchCurrentItemPath(root, this.currentPath);
return [];
}
@@ -67,7 +68,7 @@ export default class GalleryStore extends VuexModule {
// Fetches the gallery's JSON config
@action async fetchConfig() {
return fetch(`${process.env.VUE_APP_DATA_URL}config.json`, { cache: "no-cache" })
- .then(config => config.json())
+ .then(response => response.json())
.then(this.setConfig);
}
@@ -82,43 +83,7 @@ export default class GalleryStore extends VuexModule {
// Indexes the gallery
@action async indexTags() {
- const root = this.galleryItemsRoot;
- let index = {};
- if (root) GalleryStore.pushTagsForItem(index, root);
- console.log("Index: ", index);
- this.setTags(index);
- }
-
- // ---
-
- // Pushes all tags for a root item (and its children) to the index
- private static pushTagsForItem(index: Tag.Index, item: Gallery.Item) {
- console.log("IndexingTagsFor: ", item.path);
- if (item.properties.type === "directory") {
- item.properties.items.forEach(item => this.pushTagsForItem(index, 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 (!index[part]) index[part] = { tag: part, tagfiltered: Tools.normalize(part), items: [], children: {} };
- if (!index[part].items.includes(item)) index[part].items.push(item);
- if (lastPart) index[lastPart].children[part] = index[part];
- lastPart = part;
- }
- }
+ this.setTagsIndex(IndexFactory.generateTags(this.galleryItemsRoot));
}
- // Searches for an item by path from a root item (navigation)
- private static searchCurrentItemPath(item: Gallery.Item, path: string): Gallery.Item[] {
- if (path === item.path) return [item];
- if (item.properties.type === "directory" && path.startsWith(item.path)) {
- const itemChain = item.properties.items
- .map(item => this.searchCurrentItemPath(item, path))
- .find(itemChain => itemChain.length > 0);
- if (itemChain) return [item, ...itemChain];
- }
- return [];
- }
}
diff --git a/viewer/src/store/uiStore.ts b/viewer/src/store/uiStore.ts
index f7484de..5b6e1ca 100644
--- a/viewer/src/store/uiStore.ts
+++ b/viewer/src/store/uiStore.ts
@@ -28,18 +28,8 @@ export default class UIStore extends VuexModule {
fullscreen: boolean = false;
fullWidth: boolean = true;
- mode: "navigation" | "search" = "navigation";
- currentTags: Tag.Search[] = [];
-
- // ---
-
- get isModeSearch() {
- return this.mode === "search";
- }
-
- get isModeNavigation() {
- return this.mode === "navigation";
- }
+ searchMode: boolean = false;
+ searchFilters: Tag.Search[] = [];
// ---
@@ -50,12 +40,4 @@ export default class UIStore extends VuexModule {
@mutation toggleFullWidth() {
this.fullWidth = !this.fullWidth;
}
-
- @mutation setModeNavigation() {
- this.mode = "navigation";
- }
-
- @mutation setModeSearch() {
- this.mode = "search";
- }
}
diff --git a/viewer/src/tools.ts b/viewer/src/tools.ts
deleted file mode 100644
index 80a7ef0..0000000
--- a/viewer/src/tools.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-/* 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 Tools {
-
- // 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 => Tools.checkType(child, "directory"))
- .sort((a, b) => a.title.localeCompare(b.title)),
-
- ...items
- .filter(child => !Tools.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";
- }
- }
-
-}
diff --git a/viewer/src/views/GalleryDirectory.vue b/viewer/src/views/GalleryDirectory.vue
index 162ef6e..6e68578 100644
--- a/viewer/src/views/GalleryDirectory.vue
+++ b/viewer/src/views/GalleryDirectory.vue
@@ -18,18 +18,12 @@
-->
-
+
diff --git a/viewer/src/views/GalleryNavigation.vue b/viewer/src/views/GalleryNavigation.vue
new file mode 100644
index 0000000..e09b292
--- /dev/null
+++ b/viewer/src/views/GalleryNavigation.vue
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
{{$t("gallery.unknowntype")}}
+
+
+
+
+
+
diff --git a/viewer/src/views/GallerySearch.vue b/viewer/src/views/GallerySearch.vue
index 97c5c66..eacbcdd 100644
--- a/viewer/src/views/GallerySearch.vue
+++ b/viewer/src/views/GallerySearch.vue
@@ -18,25 +18,41 @@
-->
-
-
-
-
-
-
-
{{$t('search.no-results')}}
-
+
diff --git a/viewer/src/views/MainGallery.vue b/viewer/src/views/MainGallery.vue
deleted file mode 100644
index 5767cce..0000000
--- a/viewer/src/views/MainGallery.vue
+++ /dev/null
@@ -1,110 +0,0 @@
-
-
-
-
-
-
-
-
{{$t("gallery.unknowntype")}}
-
-
-
-
-
-
diff --git a/viewer/src/views/MainLayout.vue b/viewer/src/views/MainLayout.vue
index 63a1b83..272c045 100644
--- a/viewer/src/views/MainLayout.vue
+++ b/viewer/src/views/MainLayout.vue
@@ -24,10 +24,6 @@
-
-
-
-
diff --git a/viewer/src/views/PanelLeft.vue b/viewer/src/views/PanelLeft.vue
index a61fe4a..fd117a6 100644
--- a/viewer/src/views/PanelLeft.vue
+++ b/viewer/src/views/PanelLeft.vue
@@ -19,17 +19,45 @@
-
+
+
{{$t('panelLeft.propositions')}}
-
+