aboutsummaryrefslogtreecommitdiff
path: root/viewer/src/views/layout/left/LayoutLeft.vue
diff options
context:
space:
mode:
Diffstat (limited to 'viewer/src/views/layout/left/LayoutLeft.vue')
-rw-r--r--viewer/src/views/layout/left/LayoutLeft.vue158
1 files changed, 158 insertions, 0 deletions
diff --git a/viewer/src/views/layout/left/LayoutLeft.vue b/viewer/src/views/layout/left/LayoutLeft.vue
new file mode 100644
index 0000000..bb3e747
--- /dev/null
+++ b/viewer/src/views/layout/left/LayoutLeft.vue
@@ -0,0 +1,158 @@
1<!-- ldgallery - A static generator which turns a collection of tagged
2-- pictures into a searchable web gallery.
3--
4-- Copyright (C) 2019-2022 Guillaume FOUET
5--
6-- This program is free software: you can redistribute it and/or modify
7-- it under the terms of the GNU Affero General Public License as
8-- published by the Free Software Foundation, either version 3 of the
9-- License, or (at your option) any later version.
10--
11-- This program is distributed in the hope that it will be useful,
12-- but WITHOUT ANY WARRANTY; without even the implied warranty of
13-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14-- GNU Affero General Public License for more details.
15--
16-- You should have received a copy of the GNU Affero General Public License
17-- along with this program. If not, see <https://www.gnu.org/licenses/>.
18-->
19
20<template>
21 <div
22 class="flex-column"
23 :class="$style.sidebar"
24 >
25 <LayoutTagList v-model="searchFilters" />
26 <LayoutTagInput
27 v-model="searchFilters"
28 @search="search"
29 @opening="isOpenSearch=true"
30 @closing="isOpenSearch=false"
31 />
32 <!-- We disable the components beneath the Dropdown for accessibility purposes -->
33 <LayoutCommandSearch
34 v-show="!isOpenSearch"
35 @clear="clear"
36 @search="search"
37 />
38 <h1
39 v-show="!isOpenSearch"
40 :class="$style.title"
41 >
42 {{ t("panelLeft.propositions.related") }}
43 </h1>
44 <div
45 v-show="!isOpenSearch"
46 class="scrollbar no-scroll-x"
47 :class="$style.flexShrinkFully"
48 >
49 <LayoutProposition
50 v-for="category in galleryStore.tagsCategories"
51 :key="category.tag"
52 v-model:search-filters="searchFilters"
53 :category="galleryStore.tagsIndex[category.tag]"
54 :show-category="galleryStore.tagsCategories.length > 1"
55 :tags-index="category.index"
56 :current-tags="currentTags"
57 />
58 </div>
59
60 <template v-if="galleryStore.currentItem">
61 <div class="flex-grow-1" />
62 <h1
63 :class="[$style.infoPanelTitleBar, $style.title]"
64 class="flex"
65 @click="infoOpen = !infoOpen"
66 >
67 {{ t("panelLeft.information.title") }}
68 <fa-icon :icon="infoOpen ? faCaretDown : faCaretUp" />
69 </h1>
70 <transition name="slide">
71 <LayoutInformation
72 v-show="infoOpen"
73 :item="galleryStore.currentItem"
74 class="scrollbar no-scroll-x"
75 />
76 </transition>
77 </template>
78 </div>
79</template>
80
81<script setup lang="ts">
82import { TagSearch } from '@/@types/tag';
83import { useNavigation } from '@/services/navigation';
84import { useGalleryStore } from '@/store/galleryStore';
85import { faCaretDown, faCaretUp } from '@fortawesome/free-solid-svg-icons';
86import { computed, ref, watch } from 'vue';
87import { useI18n } from 'vue-i18n';
88import { useRoute, useRouter } from 'vue-router';
89import LayoutCommandSearch from './LayoutCommandSearch.vue';
90import LayoutInformation from './LayoutInformation.vue';
91import LayoutProposition from './LayoutProposition.vue';
92import LayoutTagInput from './LayoutTagInput.vue';
93import LayoutTagList from './LayoutTagList.vue';
94
95const { t } = useI18n();
96const route = useRoute();
97const router = useRouter();
98const navigation = useNavigation();
99const galleryStore = useGalleryStore();
100
101const searchFilters = ref<TagSearch[]>([]);
102const infoOpen = ref(true);
103const isOpenSearch = ref(false);
104
105function clear() {
106 searchFilters.value = [];
107 search();
108}
109
110function search() {
111 const lastDirectory = navigation.getLastDirectory(galleryStore.currentItemPath);
112 router.push({ path: lastDirectory.path, query: serializeSearch() }).catch(err => {
113 if (err.name !== 'NavigationDuplicated') throw err;
114 });
115}
116
117function serializeSearch() {
118 const query: Record<string, null> = {};
119 searchFilters.value.forEach(filter => (query[filter.display] = null));
120 return query;
121}
122
123const currentTags = computed(() => galleryStore.currentItem?.tags ?? []);
124
125watch(() => route.query, (query) => {
126 const filters = Object.keys(query);
127 if (filters.length > 0) galleryStore.search(filters).then(search => (searchFilters.value = [...search]));
128}, { immediate: true });
129</script>
130
131<style lang="scss" module>
132@import "~@/assets/scss/theme";
133
134.sidebar {
135 .title {
136 background-color: $proposed-category-bgcolor;
137 color: $title-color;
138 padding: 0.2em 0.5em;
139 margin: 0 0 1px 0;
140 font-variant: small-caps;
141 justify-content: space-between;
142 user-select: none;
143
144 >svg {
145 color: $link;
146 margin-top: 2px; // Fixes a vertical centering issue with the carret
147 }
148 }
149
150 .infoPanelTitleBar {
151 cursor: pointer;
152 }
153}
154
155.flexShrinkFully {
156 flex-shrink: 1000;
157}
158</style>