diff options
author | Ferdinand Thiessen <opensource@fthiessen.de> | 2024-07-29 18:52:09 +0200 |
---|---|---|
committer | Arthur Schiwon <blizzz@arthur-schiwon.de> | 2024-07-30 16:40:14 +0200 |
commit | 77cffcb04933795b41b73ccb3850c77c5905dbb7 (patch) | |
tree | 8c14460315b614236226b8c2c88aa9b714e3c949 | |
parent | 8b4340a126edd86c82ebe85e1f65fee5e07dbf45 (diff) | |
download | nextcloud-server-77cffcb04933795b41b73ccb3850c77c5905dbb7.tar.gz nextcloud-server-77cffcb04933795b41b73ccb3850c77c5905dbb7.zip |
fix(files_sharing): Make account file filter consistent have design
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
-rw-r--r-- | apps/files/src/components/FileListFilters.vue | 10 | ||||
-rw-r--r-- | apps/files/src/store/filters.ts | 2 | ||||
-rw-r--r-- | apps/files_sharing/src/components/FileListFilterAccount.vue | 101 | ||||
-rw-r--r-- | apps/files_sharing/src/filters/AccountFilter.ts | 18 |
4 files changed, 112 insertions, 19 deletions
diff --git a/apps/files/src/components/FileListFilters.vue b/apps/files/src/components/FileListFilters.vue index 5cdc4e877fd..1bd1dd627af 100644 --- a/apps/files/src/components/FileListFilters.vue +++ b/apps/files/src/components/FileListFilters.vue @@ -14,7 +14,14 @@ <NcChip :aria-label-close="t('files', 'Remove filter')" :icon-svg="chip.icon" :text="chip.text" - @close="chip.onclick" /> + @close="chip.onclick"> + <template v-if="chip.user" #icon> + <NcAvatar disable-menu + :show-user-status="false" + :size="24" + :user="chip.user" /> + </template> + </NcChip> </li> </ul> </div> @@ -25,6 +32,7 @@ import { t } from '@nextcloud/l10n' import { computed, ref, watchEffect } from 'vue' import { useFiltersStore } from '../store/filters.ts' +import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js' import NcChip from '@nextcloud/vue/dist/Components/NcChip.js' const filterStore = useFiltersStore() diff --git a/apps/files/src/store/filters.ts b/apps/files/src/store/filters.ts index abc12732fd4..a5e3421f726 100644 --- a/apps/files/src/store/filters.ts +++ b/apps/files/src/store/filters.ts @@ -65,6 +65,8 @@ export const useFiltersStore = defineStore('keyboard', { onFilterUpdateChips(event: FilterUpdateChipsEvent) { const id = (event.target as IFileListFilter).id this.chips = { ...this.chips, [id]: [...event.detail] } + + logger.debug('File list filter chips updated', { filter: id, chips: event.detail }) }, init() { diff --git a/apps/files_sharing/src/components/FileListFilterAccount.vue b/apps/files_sharing/src/components/FileListFilterAccount.vue index 7a91de6464e..68383735532 100644 --- a/apps/files_sharing/src/components/FileListFilterAccount.vue +++ b/apps/files_sharing/src/components/FileListFilterAccount.vue @@ -3,25 +3,53 @@ - SPDX-License-Identifier: AGPL-3.0-or-later --> <template> - <NcSelect v-model="selectedAccounts" - :aria-label-combobox="t('files_sharing', 'Accounts')" - class="file-list-filter-accounts" - multiple - no-wrap - :options="availableAccounts" - :placeholder="t('files_sharing', 'Accounts')" - user-select /> + <FileListFilter class="file-list-filter-accounts" + :is-active="selectedAccounts.length > 0" + :filter-name="t('files', 'People')" + @reset-filter="resetFilter"> + <template #icon> + <NcIconSvgWrapper :path="mdiAccountMultiple" /> + </template> + <NcActionInput v-if="availableAccounts.length > 1" + :label="t('files_sharing', 'Filter accounts')" + :label-outside="false" + :show-trailing-button="false" + type="search" + :value.sync="accountFilter" /> + <NcActionButton v-for="account of shownAccounts" + :key="account.id" + class="file-list-filter-accounts__item" + type="radio" + :model-value="selectedAccounts.includes(account)" + :value="account.id" + @click="toggleAccount(account.id)"> + <template #icon> + <NcAvatar class="file-list-filter-accounts__avatar" + v-bind="account" + :size="24" + disable-menu + :show-user-status="false" /> + </template> + {{ account.displayName }} + </NcActionButton> + </FileListFilter> </template> <script setup lang="ts"> import type { IAccountData } from '../filters/AccountFilter.ts' import { translate as t } from '@nextcloud/l10n' +import { mdiAccountMultiple } from '@mdi/js' import { useBrowserLocation } from '@vueuse/core' -import { ref, watch, watchEffect } from 'vue' +import { computed, ref, watch } from 'vue' import { useNavigation } from '../../../files/src/composables/useNavigation.ts' -import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js' +import FileListFilter from '../../../files/src/components/FileListFilter/FileListFilter.vue' +import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js' +import NcActionInput from '@nextcloud/vue/dist/Components/NcActionInput.js' +import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js' +import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js' +import { ShareType } from '@nextcloud/sharing' interface IUserSelectData { id: string @@ -35,9 +63,41 @@ const emit = defineEmits<{ const { currentView } = useNavigation() const currentLocation = useBrowserLocation() +const accountFilter = ref('') const availableAccounts = ref<IUserSelectData[]>([]) const selectedAccounts = ref<IUserSelectData[]>([]) +/** + * Currently shown accounts (filtered) + */ +const shownAccounts = computed(() => { + if (!accountFilter.value) { + return availableAccounts.value + } + const queryParts = accountFilter.value.toLocaleLowerCase().trim().split(' ') + return availableAccounts.value.filter((account) => + queryParts.every((part) => + account.user.toLocaleLowerCase().includes(part) + || account.displayName.toLocaleLowerCase().includes(part), + ), + ) +}) + +/** + * Toggle an account as selected + * @param accountId The account to toggle + */ +function toggleAccount(accountId: string) { + const account = availableAccounts.value.find(({ id }) => id === accountId) + if (account && selectedAccounts.value.includes(account)) { + selectedAccounts.value = selectedAccounts.value.filter(({ id }) => id !== accountId) + } else { + if (account) { + selectedAccounts.value = [...selectedAccounts.value, account] + } + } +} + // Watch selected account, on change we emit the new account data to the filter instance watch(selectedAccounts, () => { // Emit selected accounts as account data @@ -75,6 +135,9 @@ async function updateAvailableAccounts(path: string = '/') { if (sharee.id === '') { continue } + if (sharee.type !== ShareType.User && sharee.type !== ShareType.Remote) { + continue + } // Add if not already added if (!available.has(sharee.id)) { available.set(sharee.id, { @@ -94,23 +157,31 @@ async function updateAvailableAccounts(path: string = '/') { */ function resetFilter() { selectedAccounts.value = [] + accountFilter.value = '' } -defineExpose({ resetFilter }) +defineExpose({ resetFilter, toggleAccount }) // When the current view changes or the current directory, // then we need to rebuild the available accounts -watchEffect(() => { +watch([currentView, currentLocation], () => { if (currentView.value) { // we have no access to the files router here... const path = (currentLocation.value.search ?? '?dir=/').match(/(?<=&|\?)dir=([^&#]+)/)?.[1] - selectedAccounts.value = [] + resetFilter() updateAvailableAccounts(decodeURIComponent(path ?? '/')) } -}) +}, { immediate: true }) </script> <style scoped lang="scss"> .file-list-filter-accounts { - max-width: 300px; + &__item { + min-width: 250px; + } + + &__avatar { + // 24px is the avatar size + margin: calc((var(--default-clickable-area) - 24px) / 2) + } } </style> diff --git a/apps/files_sharing/src/filters/AccountFilter.ts b/apps/files_sharing/src/filters/AccountFilter.ts index 408e455e17f..29e8088dc23 100644 --- a/apps/files_sharing/src/filters/AccountFilter.ts +++ b/apps/files_sharing/src/filters/AccountFilter.ts @@ -2,7 +2,7 @@ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -import type { INode } from '@nextcloud/files' +import type { IFileListFilterChip, INode } from '@nextcloud/files' import { FileListFilter, registerFileListFilter } from '@nextcloud/files' import Vue from 'vue' @@ -13,12 +13,14 @@ export interface IAccountData { displayName: string } +type CurrentInstance = Vue & { resetFilter: () => void, toggleAccount: (account: string) => void } + /** * File list filter to filter by owner / sharee */ class AccountFilter extends FileListFilter { - private currentInstance?: Vue + private currentInstance?: CurrentInstance private filterAccounts?: IAccountData[] constructor() { @@ -35,7 +37,7 @@ class AccountFilter extends FileListFilter { el, }) .$on('update:accounts', this.setAccounts.bind(this)) - .$mount() + .$mount() as CurrentInstance } public filter(nodes: INode[]): INode[] { @@ -66,6 +68,16 @@ class AccountFilter extends FileListFilter { public setAccounts(accounts?: IAccountData[]) { this.filterAccounts = accounts + let chips: IFileListFilterChip[] = [] + if (this.filterAccounts && this.filterAccounts.length > 0) { + chips = this.filterAccounts.map(({ displayName, uid }) => ({ + text: displayName, + user: uid, + onclick: () => this.currentInstance?.toggleAccount(uid), + })) + } + + this.updateChips(chips) this.filterUpdated() } |