aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFerdinand Thiessen <opensource@fthiessen.de>2024-07-29 18:52:09 +0200
committerArthur Schiwon <blizzz@arthur-schiwon.de>2024-07-30 16:40:14 +0200
commit77cffcb04933795b41b73ccb3850c77c5905dbb7 (patch)
tree8c14460315b614236226b8c2c88aa9b714e3c949
parent8b4340a126edd86c82ebe85e1f65fee5e07dbf45 (diff)
downloadnextcloud-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.vue10
-rw-r--r--apps/files/src/store/filters.ts2
-rw-r--r--apps/files_sharing/src/components/FileListFilterAccount.vue101
-rw-r--r--apps/files_sharing/src/filters/AccountFilter.ts18
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()
}