Merge pull request #51937 from nextcloud/perf/filter-propfind

perf(files_sharing): do not emit second propfind for account filter
pull/52005/head
Ferdinand Thiessen 2025-04-07 15:57:21 +07:00 committed by GitHub
commit 39dc22b9dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 84 additions and 72 deletions

@ -36,14 +36,11 @@
</template>
<script setup lang="ts">
import type { IAccountData } from '../filters/AccountFilter.ts'
import type { IAccountData } from '../files_filters/AccountFilter.ts'
import { translate as t } from '@nextcloud/l10n'
import { ShareType } from '@nextcloud/sharing'
import { mdiAccountMultiple } from '@mdi/js'
import { useBrowserLocation } from '@vueuse/core'
import { computed, ref, watch } from 'vue'
import { useNavigation } from '../../../files/src/composables/useNavigation.ts'
import FileListFilter from '../../../files/src/components/FileListFilter/FileListFilter.vue'
import NcActionButton from '@nextcloud/vue/components/NcActionButton'
@ -61,8 +58,6 @@ const emit = defineEmits<{
(event: 'update:accounts', value: IAccountData[]): void
}>()
const { currentView } = useNavigation()
const currentLocation = useBrowserLocation()
const accountFilter = ref('')
const availableAccounts = ref<IUserSelectData[]>([])
const selectedAccounts = ref<IUserSelectData[]>([])
@ -105,53 +100,6 @@ watch(selectedAccounts, () => {
emit('update:accounts', accounts)
})
/**
* Update the accounts owning nodes or have nodes shared to them
* @param path The path inside the current view to load for accounts
*/
async function updateAvailableAccounts(path: string = '/') {
availableAccounts.value = []
if (!currentView.value) {
return
}
const { contents } = await currentView.value.getContents(path)
const available = new Map<string, IUserSelectData>()
for (const node of contents) {
const owner = node.owner
if (owner && !available.has(owner)) {
available.set(owner, {
id: owner,
user: owner,
displayName: node.attributes['owner-display-name'] ?? node.owner,
})
}
const sharees = node.attributes.sharees?.sharee
if (sharees) {
// ensure sharees is an array (if only one share then it is just an object)
for (const sharee of [sharees].flat()) {
// Skip link shares and other without user
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, {
id: sharee.id,
user: sharee.id,
displayName: sharee['display-name'],
})
}
}
}
}
availableAccounts.value = [...available.values()]
}
/**
* Reset this filter
*/
@ -159,18 +107,21 @@ function resetFilter() {
selectedAccounts.value = []
accountFilter.value = ''
}
defineExpose({ resetFilter, toggleAccount })
// When the current view changes or the current directory,
// then we need to rebuild the available accounts
watch([currentView, currentLocation], () => {
if (currentView.value) {
// we have no access to the files router here...
const path = (currentLocation.value.search ?? '?dir=/').match(/(?<=&|\?)dir=([^&#]+)/)?.[1]
resetFilter()
updateAvailableAccounts(decodeURIComponent(path ?? '/'))
}
}, { immediate: true })
/**
* Update list of available accounts in current view.
*
* @param accounts - Accounts to use
*/
function setAvailableAccounts(accounts: IAccountData[]): void {
availableAccounts.value = accounts.map(({ uid, displayName }) => ({ displayName, id: uid, user: uid }))
}
defineExpose({
resetFilter,
setAvailableAccounts,
toggleAccount,
})
</script>
<style scoped lang="scss">

@ -4,8 +4,11 @@
*/
import type { IFileListFilterChip, INode } from '@nextcloud/files'
import { subscribe } from '@nextcloud/event-bus'
import { FileListFilter, registerFileListFilter } from '@nextcloud/files'
import { ShareType } from '@nextcloud/sharing'
import Vue from 'vue'
import FileListFilterAccount from '../components/FileListFilterAccount.vue'
export interface IAccountData {
@ -13,18 +16,28 @@ export interface IAccountData {
displayName: string
}
type CurrentInstance = Vue & { resetFilter: () => void, toggleAccount: (account: string) => void }
type CurrentInstance = Vue & {
resetFilter: () => void
setAvailableAccounts: (accounts: IAccountData[]) => void
toggleAccount: (account: string) => void
}
/**
* File list filter to filter by owner / sharee
*/
class AccountFilter extends FileListFilter {
private availableAccounts: IAccountData[]
private currentInstance?: CurrentInstance
private filterAccounts?: IAccountData[]
constructor() {
super('files_sharing:account', 100)
this.availableAccounts = []
subscribe('files:list:updated', ({ contents }) => {
this.updateAvailableAccounts(contents)
})
}
public mount(el: HTMLElement) {
@ -33,11 +46,11 @@ class AccountFilter extends FileListFilter {
}
const View = Vue.extend(FileListFilterAccount as never)
this.currentInstance = new View({
el,
})
.$on('update:accounts', this.setAccounts.bind(this))
this.currentInstance = new View({ el })
.$on('update:accounts', (accounts?: IAccountData[]) => this.setAccounts(accounts))
.$mount() as CurrentInstance
this.currentInstance
.setAvailableAccounts(this.availableAccounts)
}
public filter(nodes: INode[]): INode[] {
@ -70,6 +83,11 @@ class AccountFilter extends FileListFilter {
this.currentInstance?.resetFilter()
}
/**
* Set accounts that should be filtered.
*
* @param accounts - Account to filter or undefined if inactive.
*/
public setAccounts(accounts?: IAccountData[]) {
this.filterAccounts = accounts
let chips: IFileListFilterChip[] = []
@ -85,6 +103,49 @@ class AccountFilter extends FileListFilter {
this.filterUpdated()
}
/**
* Update the accounts owning nodes or have nodes shared to them.
*
* @param nodes - The current content of the file list.
*/
protected updateAvailableAccounts(nodes: INode[]): void {
const available = new Map<string, IAccountData>()
for (const node of nodes) {
const owner = node.owner
if (owner && !available.has(owner)) {
available.set(owner, {
uid: owner,
displayName: node.attributes['owner-display-name'] ?? node.owner,
})
}
// ensure sharees is an array (if only one share then it is just an object)
const sharees: { id: string, 'display-name': string, type: ShareType }[] = [node.attributes.sharees?.sharee].flat().filter(Boolean)
for (const sharee of [sharees].flat()) {
// Skip link shares and other without user
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, {
uid: sharee.id,
displayName: sharee['display-name'],
})
}
}
}
this.availableAccounts = [...available.values()]
if (this.currentInstance) {
this.currentInstance.setAvailableAccounts(this.availableAccounts)
}
}
}
/**

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long