mirror of
https://github.com/nextcloud/server.git
synced 2026-06-13 18:50:47 -04:00
fix(files_sharing): Make account file filter consistent have design
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
parent
8b4340a126
commit
77cffcb049
4 changed files with 112 additions and 19 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue