From cb5c499faf331d224c1dd3e057f59445d51e3900 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Wed, 13 Nov 2024 17:09:33 +0100 Subject: [PATCH] fix(files): Properly reset all file list filters on view change Signed-off-by: Ferdinand Thiessen --- apps/files/src/filters/FilenameFilter.ts | 6 +- apps/files/src/filters/ModifiedFilter.ts | 6 +- apps/files/src/filters/TypeFilter.ts | 6 +- apps/files/src/store/filters.ts | 187 ++++++++++++------ apps/files/src/views/FilesList.vue | 1 - .../src/files_filters/AccountFilter.ts | 4 + 6 files changed, 139 insertions(+), 71 deletions(-) diff --git a/apps/files/src/filters/FilenameFilter.ts b/apps/files/src/filters/FilenameFilter.ts index 32df078a00681..5019ca42d834a 100644 --- a/apps/files/src/filters/FilenameFilter.ts +++ b/apps/files/src/filters/FilenameFilter.ts @@ -4,7 +4,6 @@ */ import type { IFileListFilterChip, INode } from '@nextcloud/files' -import { subscribe } from '@nextcloud/event-bus' import { FileListFilter } from '@nextcloud/files' /** @@ -16,7 +15,6 @@ export class FilenameFilter extends FileListFilter { constructor() { super('files:filename', 5) - subscribe('files:navigation:changed', () => this.updateQuery('')) } public filter(nodes: INode[]): INode[] { @@ -27,6 +25,10 @@ export class FilenameFilter extends FileListFilter { }) } + public reset(): void { + this.updateQuery('') + } + public updateQuery(query: string) { query = (query || '').trim() diff --git a/apps/files/src/filters/ModifiedFilter.ts b/apps/files/src/filters/ModifiedFilter.ts index 63563f2451060..e7d7c2f26a7dc 100644 --- a/apps/files/src/filters/ModifiedFilter.ts +++ b/apps/files/src/filters/ModifiedFilter.ts @@ -4,7 +4,6 @@ */ import type { IFileListFilterChip, INode } from '@nextcloud/files' -import { subscribe } from '@nextcloud/event-bus' import { FileListFilter, registerFileListFilter } from '@nextcloud/files' import { t } from '@nextcloud/l10n' import Vue from 'vue' @@ -58,7 +57,6 @@ class ModifiedFilter extends FileListFilter { constructor() { super('files:modified', 50) - subscribe('files:navigation:changed', () => this.setPreset()) } public mount(el: HTMLElement) { @@ -85,6 +83,10 @@ class ModifiedFilter extends FileListFilter { return nodes.filter((node) => node.mtime === undefined || this.currentPreset!.filter(node.mtime.getTime())) } + public reset(): void { + this.setPreset() + } + public setPreset(preset?: ITimePreset) { this.currentPreset = preset this.filterUpdated() diff --git a/apps/files/src/filters/TypeFilter.ts b/apps/files/src/filters/TypeFilter.ts index cf8660220476c..23125bf307486 100644 --- a/apps/files/src/filters/TypeFilter.ts +++ b/apps/files/src/filters/TypeFilter.ts @@ -4,7 +4,6 @@ */ import type { IFileListFilterChip, INode } from '@nextcloud/files' -import { subscribe } from '@nextcloud/event-bus' import { FileListFilter, registerFileListFilter } from '@nextcloud/files' import { t } from '@nextcloud/l10n' import Vue from 'vue' @@ -94,7 +93,6 @@ class TypeFilter extends FileListFilter { constructor() { super('files:type', 10) - subscribe('files:navigation:changed', () => this.setPreset()) } public async mount(el: HTMLElement) { @@ -141,6 +139,10 @@ class TypeFilter extends FileListFilter { }) } + public reset(): void { + this.setPreset() + } + public setPreset(presets?: ITypePreset[]) { this.currentPresets = presets this.filterUpdated() diff --git a/apps/files/src/store/filters.ts b/apps/files/src/store/filters.ts index ea8ff736b999c..f3a7424138647 100644 --- a/apps/files/src/store/filters.ts +++ b/apps/files/src/store/filters.ts @@ -7,74 +7,133 @@ import { subscribe } from '@nextcloud/event-bus' import { getFileListFilters } from '@nextcloud/files' import { defineStore } from 'pinia' import logger from '../logger' +import { computed, ref } from 'vue' -export const useFiltersStore = defineStore('filters', { - state: () => ({ - chips: {} as Record, - filters: [] as IFileListFilter[], - filtersChanged: false, - }), - - getters: { - /** - * Currently active filter chips - * @param state Internal state - */ - activeChips(state): IFileListFilterChip[] { - return Object.values(state.chips).flat() - }, - - /** - * Filters sorted by order - * @param state Internal state - */ - sortedFilters(state): IFileListFilter[] { - return state.filters.sort((a, b) => a.order - b.order) - }, - - /** - * All filters that provide a UI for visual controlling the filter state - */ - filtersWithUI(): Required[] { - return this.sortedFilters.filter((filter) => 'mount' in filter) as Required[] - }, - }, - - actions: { - addFilter(filter: IFileListFilter) { - filter.addEventListener('update:chips', this.onFilterUpdateChips) - filter.addEventListener('update:filter', this.onFilterUpdate) - this.filters.push(filter) - logger.debug('New file list filter registered', { id: filter.id }) - }, - - removeFilter(filterId: string) { - const index = this.filters.findIndex(({ id }) => id === filterId) - if (index > -1) { - const [filter] = this.filters.splice(index, 1) - filter.removeEventListener('update:chips', this.onFilterUpdateChips) - filter.removeEventListener('update:filter', this.onFilterUpdate) - logger.debug('Files list filter unregistered', { id: filterId }) - } - }, +/** + * Check if the given value is an instance file list filter with mount function + * @param value The filter to check + */ +function isFileListFilterWithUi(value: IFileListFilter): value is Required { + return 'mount' in value +} + +export const useFiltersStore = defineStore('filters', () => { + const chips = ref>({}) + const filters = ref([]) + const filtersChanged = ref(false) + + + /** + * Currently active filter chips + */ + const activeChips = computed( + () => Object.values(chips.value).flat(), + ) + + /** + * Filters sorted by order + */ + const sortedFilters = computed( + () => filters.value.sort((a, b) => a.order - b.order), + ) + + /** + * All filters that provide a UI for visual controlling the filter state + */ + const filtersWithUI = computed[]>( + () => sortedFilters.value.filter(isFileListFilterWithUi) + ) + + /** + * Register a new filter on the store. + * This will subscribe the store to the filters events. + * + * @param filter The filter to add + */ + function addFilter(filter: IFileListFilter) { + filter.addEventListener('update:chips', onFilterUpdateChips) + filter.addEventListener('update:filter', onFilterUpdate) - onFilterUpdate() { - this.filtersChanged = true - }, + filters.value.push(filter) + logger.debug('New file list filter registered', { id: filter.id }) + } - onFilterUpdateChips(event: FilterUpdateChipsEvent) { - const id = (event.target as IFileListFilter).id - this.chips = { ...this.chips, [id]: [...event.detail] } + /** + * Unregister a filter from the store. + * This will remove the filter from the store and unsubscribe the store from the filer events. + * @param filterId Id of the filter to remove + */ + function removeFilter(filterId: string) { + const index = filters.value.findIndex(({ id }) => id === filterId) + if (index > -1) { + const [filter] = filters.value.splice(index, 1) + filter.removeEventListener('update:chips', onFilterUpdateChips) + filter.removeEventListener('update:filter', onFilterUpdate) + logger.debug('Files list filter unregistered', { id: filterId }) + } + } - logger.debug('File list filter chips updated', { filter: id, chips: event.detail }) - }, + /** + * Event handler for filter update events + * @private + */ + function onFilterUpdate() { + filtersChanged.value = true + } - init() { - subscribe('files:filter:added', this.addFilter) - subscribe('files:filter:removed', this.removeFilter) - for (const filter of getFileListFilters()) { - this.addFilter(filter) + /** + * Event handler for filter chips updates + * @param event The update event + * @private + */ + function onFilterUpdateChips(event: FilterUpdateChipsEvent) { + const id = (event.target as IFileListFilter).id + chips.value = { + ...chips.value, + [id]: [...event.detail], + } + + logger.debug('File list filter chips updated', { filter: id, chips: event.detail }) + } + + /** + * Event handler that resets all filters if the file list view was changed. + * @private + */ + function onViewChanged() { + logger.debug('Reset all file list filters - view changed') + + for (const filter of filters.value) { + if (filter.reset !== undefined) { + filter.reset() } - }, - }, + } + } + + // Initialize the store + { + subscribe('files:filter:added', addFilter) + subscribe('files:filter:removed', removeFilter) + for (const filter of getFileListFilters()) { + addFilter(filter) + } + + subscribe('files:navigation:changed', onViewChanged) + } + + return { + // state + chips, + filters, + filtersWithUI, + filtersChanged, + + // getters / computed + activeChips, + sortedFilters, + + // actions / methods + addFilter, + removeFilter, + } }) diff --git a/apps/files/src/views/FilesList.vue b/apps/files/src/views/FilesList.vue index fdfbed6a4e31a..6727ed6efbcfd 100644 --- a/apps/files/src/views/FilesList.vue +++ b/apps/files/src/views/FilesList.vue @@ -489,7 +489,6 @@ export default defineComponent({ }, mounted() { - this.filtersStore.init() this.fetchContent() subscribe('files:node:deleted', this.onNodeDeleted) diff --git a/apps/files_sharing/src/files_filters/AccountFilter.ts b/apps/files_sharing/src/files_filters/AccountFilter.ts index 29e8088dc2307..8da4d85d67cd9 100644 --- a/apps/files_sharing/src/files_filters/AccountFilter.ts +++ b/apps/files_sharing/src/files_filters/AccountFilter.ts @@ -66,6 +66,10 @@ class AccountFilter extends FileListFilter { }) } + public reset(): void { + this.currentInstance?.resetFilter() + } + public setAccounts(accounts?: IAccountData[]) { this.filterAccounts = accounts let chips: IFileListFilterChip[] = []