diff options
author | Zéro~Informatique | 2022-09-03 04:41:03 +0200 |
---|---|---|
committer | Zéro~Informatique | 2022-09-03 04:41:03 +0200 |
commit | e704198437fb589ec8c954d12700d2a1f911522c (patch) | |
tree | 2413a96880367c55e7f736e902bf2944d90ef0ec /viewer/src/views | |
parent | a8452594c6571e8003baa2aca14747eeeee08152 (diff) | |
download | ldgallery-e704198437fb589ec8c954d12700d2a1f911522c.tar.gz |
viewer: refactoring for tag and sort dropdowns
Diffstat (limited to 'viewer/src/views')
-rw-r--r-- | viewer/src/views/MainLayout.vue | 1 | ||||
-rw-r--r-- | viewer/src/views/layout/left/LayoutTagInput.vue | 93 | ||||
-rw-r--r-- | viewer/src/views/layout/top/LayoutCommandSort.vue | 60 |
3 files changed, 50 insertions, 104 deletions
diff --git a/viewer/src/views/MainLayout.vue b/viewer/src/views/MainLayout.vue index 9c84116..d8b3300 100644 --- a/viewer/src/views/MainLayout.vue +++ b/viewer/src/views/MainLayout.vue | |||
@@ -103,6 +103,7 @@ function validateSpashScreen() { | |||
103 | overflow: hidden; | 103 | overflow: hidden; |
104 | touch-action: none; | 104 | touch-action: none; |
105 | background-color: $content-bgcolor; | 105 | background-color: $content-bgcolor; |
106 | margin: 0; | ||
106 | } | 107 | } |
107 | .layout { | 108 | .layout { |
108 | position: fixed; | 109 | position: fixed; |
diff --git a/viewer/src/views/layout/left/LayoutTagInput.vue b/viewer/src/views/layout/left/LayoutTagInput.vue index 7ad3ed0..a37c546 100644 --- a/viewer/src/views/layout/left/LayoutTagInput.vue +++ b/viewer/src/views/layout/left/LayoutTagInput.vue | |||
@@ -27,42 +27,38 @@ | |||
27 | @keypress.enter="inputEnter" | 27 | @keypress.enter="inputEnter" |
28 | @keydown.backspace="inputBackspace" | 28 | @keydown.backspace="inputBackspace" |
29 | /> | 29 | /> |
30 | <div style="position:relative;"> | 30 | <LdDropdown |
31 | <Transition name="fade"> | 31 | ref="dropdown" |
32 | v-model="showDropdown" | ||
33 | :list="filteredTags" | ||
34 | list-key="tagfiltered" | ||
35 | :tabindex-root="51" | ||
36 | :class="$style.dropdown" | ||
37 | :style="dropdownStyle" | ||
38 | @select="addTag" | ||
39 | @opening="emit('opening')" | ||
40 | @closing="cleanSearch(); emit('closing');" | ||
41 | > | ||
42 | <template #option="{option}:{option:TagSearch}"> | ||
43 | <div v-text="option.display" /> | ||
44 | <div v-text="option.items.length" /> | ||
45 | </template> | ||
46 | <template #empty> | ||
32 | <div | 47 | <div |
33 | v-if="openDropdown" | 48 | :class="$style.nomatch" |
34 | ref="dropdown" | 49 | v-text="t('tagInput.nomatch')" |
35 | class="scrollbar" | 50 | /> |
36 | :class="$style.dropdown" | 51 | </template> |
37 | :style="dropdownStyle" | 52 | </LdDropdown> |
38 | > | ||
39 | <div | ||
40 | v-for="(tag,idx) in filteredTags" | ||
41 | :key="tag.tagfiltered" | ||
42 | :tabindex="51 + idx" | ||
43 | @click="addTag(tag)" | ||
44 | @keypress.enter.space="addTag(tag)" | ||
45 | > | ||
46 | <div v-text="tag.display" /> | ||
47 | <div v-text="tag.items.length" /> | ||
48 | </div> | ||
49 | <div | ||
50 | v-if="!filteredTags.length" | ||
51 | class="disaled" | ||
52 | :class="$style.nomatch" | ||
53 | v-text="t('tagInput.nomatch')" | ||
54 | /> | ||
55 | </div> | ||
56 | </Transition> | ||
57 | </div> | ||
58 | </template> | 53 | </template> |
59 | 54 | ||
60 | <script setup lang="ts"> | 55 | <script setup lang="ts"> |
61 | import { TagSearch } from '@/@types/tag'; | 56 | import { TagSearch } from '@/@types/tag'; |
57 | import LdDropdown from '@/components/LdDropdown.vue'; | ||
62 | import LdInput from '@/components/LdInput.vue'; | 58 | import LdInput from '@/components/LdInput.vue'; |
63 | import { useIndexFactory } from '@/services/indexFactory'; | 59 | import { useIndexFactory } from '@/services/indexFactory'; |
64 | import { useGalleryStore } from '@/store/galleryStore'; | 60 | import { useGalleryStore } from '@/store/galleryStore'; |
65 | import { computedEager, onClickOutside, onKeyStroke, useElementBounding, useFocus, useVModel } from '@vueuse/core'; | 61 | import { computedEager, useElementBounding, useFocus, useVModel } from '@vueuse/core'; |
66 | import { computed, ref, StyleValue, watchEffect } from 'vue'; | 62 | import { computed, ref, StyleValue, watchEffect } from 'vue'; |
67 | import { useI18n } from 'vue-i18n'; | 63 | import { useI18n } from 'vue-i18n'; |
68 | 64 | ||
@@ -77,19 +73,15 @@ const galeryStore = useGalleryStore(); | |||
77 | const indexFactory = useIndexFactory(); | 73 | const indexFactory = useIndexFactory(); |
78 | 74 | ||
79 | const search = ref(''); | 75 | const search = ref(''); |
80 | const openDropdown = computedEager<boolean>(() => !!search.value); | 76 | const showDropdown = ref(false); |
81 | watchEffect(() => { | 77 | |
82 | if (openDropdown.value) emit('opening'); | 78 | watchEffect(() => (showDropdown.value = !!search.value)); |
83 | else emit('closing'); | ||
84 | }); | ||
85 | 79 | ||
86 | // --- | 80 | // --- |
87 | 81 | ||
88 | const dropdown = ref(); | 82 | const dropdown = ref(); |
89 | const { top } = useElementBounding(dropdown); | 83 | const { top } = useElementBounding(dropdown); |
90 | const dropdownStyle = computedEager<StyleValue>(() => ({ height: `calc(100vh - 8px - ${top.value}px)` })); | 84 | const dropdownStyle = computedEager<StyleValue>(() => ({ height: `calc(100vh - 8px - ${top.value}px)` })); |
91 | onClickOutside(dropdown, closeDropdown); | ||
92 | onKeyStroke('Escape', closeDropdown); | ||
93 | 85 | ||
94 | const input = ref(); | 86 | const input = ref(); |
95 | const { focused } = useFocus(input); | 87 | const { focused } = useFocus(input); |
@@ -111,16 +103,16 @@ function addTag(tag?: TagSearch) { | |||
111 | const toPush = tag ?? filteredTags.value[0]; | 103 | const toPush = tag ?? filteredTags.value[0]; |
112 | if (!toPush) return; | 104 | if (!toPush) return; |
113 | model.value.push(toPush); | 105 | model.value.push(toPush); |
114 | closeDropdown(); | 106 | cleanSearch(); |
115 | } | 107 | } |
116 | function inputEnter() { | 108 | function inputEnter() { |
117 | if (search.value) addTag(); | 109 | if (search.value) addTag(); |
118 | else emit('search'); | 110 | else emit('search'); |
119 | } | 111 | } |
120 | function inputBackspace() { | 112 | function inputBackspace() { |
121 | !openDropdown.value && model.value.pop(); | 113 | !showDropdown.value && model.value.pop(); |
122 | } | 114 | } |
123 | function closeDropdown() { | 115 | function cleanSearch() { |
124 | search.value = ''; | 116 | search.value = ''; |
125 | focused.value = true; | 117 | focused.value = true; |
126 | } | 118 | } |
@@ -130,35 +122,20 @@ function closeDropdown() { | |||
130 | @import "~@/assets/scss/theme"; | 122 | @import "~@/assets/scss/theme"; |
131 | 123 | ||
132 | .dropdown { | 124 | .dropdown { |
133 | position: absolute; | ||
134 | left: 0; | ||
135 | z-index: 10; | ||
136 | width: $layout-left; | ||
137 | color: $input-color; | ||
138 | background-color: $dropdown-item-color; | ||
139 | padding: 4px 0px; | ||
140 | > div { | 125 | > div { |
141 | display: flex; | 126 | display: flex; |
142 | justify-content: space-between; | 127 | justify-content: space-between; |
143 | padding: 4px 0; | ||
144 | margin: 2px; // For the focus border | ||
145 | cursor: pointer; | ||
146 | > div { | 128 | > div { |
147 | padding: 0 4px; | 129 | padding: 0 4px; |
148 | } | 130 | } |
149 | > div:last-child { | 131 | > div:last-child { |
150 | color: $text-light; | 132 | color: $text-light; |
151 | } | 133 | } |
152 | &:hover { | 134 | } |
153 | background-color: $dropdown-item-hover-color; | 135 | .nomatch { |
154 | } | 136 | color: $disabled-color; |
155 | &:focus { | 137 | justify-content: center; |
156 | outline: solid 1px $button-active-color; | 138 | cursor: default; |
157 | } | ||
158 | &.nomatch { | ||
159 | color: $text-light; | ||
160 | justify-content: center; | ||
161 | } | ||
162 | } | 139 | } |
163 | } | 140 | } |
164 | </style> | 141 | </style> |
diff --git a/viewer/src/views/layout/top/LayoutCommandSort.vue b/viewer/src/views/layout/top/LayoutCommandSort.vue index bb9744e..8336621 100644 --- a/viewer/src/views/layout/top/LayoutCommandSort.vue +++ b/viewer/src/views/layout/top/LayoutCommandSort.vue | |||
@@ -22,41 +22,35 @@ | |||
22 | <LdLink | 22 | <LdLink |
23 | :title="t('command.sort.title')" | 23 | :title="t('command.sort.title')" |
24 | :tabindex="props.tabindex" | 24 | :tabindex="props.tabindex" |
25 | @click="openDropdown" | 25 | @click="showDropdown=!showDropdown" |
26 | > | 26 | > |
27 | <fa-icon | 27 | <fa-icon |
28 | :icon="faSortAmountDown" | 28 | :icon="faSortAmountDown" |
29 | size="lg" | 29 | size="lg" |
30 | /> | 30 | /> |
31 | <teleport to="body"> | 31 | <teleport to="body"> |
32 | <Transition name="fade"> | 32 | <LdDropdown |
33 | <div | 33 | v-model="showDropdown" |
34 | v-if="showDropdown" | 34 | :list="itemComparator.ITEM_SORTS" |
35 | ref="dropdown" | 35 | :tabindex-root="props.tabindex + 1" |
36 | :class="$style.dropdown" | 36 | :class="$style.dropdown" |
37 | > | 37 | @select="(sort: ItemSort) => selectedSort=sort" |
38 | <div | 38 | > |
39 | v-for="(sort,idx) in itemComparator.ITEM_SORTS" | 39 | <template #option="{option}:{option:ItemSort}"> |
40 | :key="sort.name" | 40 | <fa-icon :icon="option.name == selectedSort.name ? faDotCircle : faCircle" /> |