Skip to content

Commit 82e6814

Browse files
committed
fix(files): Properly reset all file list filters on view change
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
1 parent 935f0d2 commit 82e6814

File tree

6 files changed

+139
-71
lines changed

6 files changed

+139
-71
lines changed

apps/files/src/filters/FilenameFilter.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
*/
55

66
import type { IFileListFilterChip, INode } from '@nextcloud/files'
7-
import { subscribe } from '@nextcloud/event-bus'
87
import { FileListFilter } from '@nextcloud/files'
98

109
/**
@@ -16,7 +15,6 @@ export class FilenameFilter extends FileListFilter {
1615

1716
constructor() {
1817
super('files:filename', 5)
19-
subscribe('files:navigation:changed', () => this.updateQuery(''))
2018
}
2119

2220
public filter(nodes: INode[]): INode[] {
@@ -27,6 +25,10 @@ export class FilenameFilter extends FileListFilter {
2725
})
2826
}
2927

28+
public reset(): void {
29+
this.updateQuery('')
30+
}
31+
3032
public updateQuery(query: string) {
3133
query = (query || '').trim()
3234

apps/files/src/filters/ModifiedFilter.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
*/
55
import type { IFileListFilterChip, INode } from '@nextcloud/files'
66

7-
import { subscribe } from '@nextcloud/event-bus'
87
import { FileListFilter, registerFileListFilter } from '@nextcloud/files'
98
import { t } from '@nextcloud/l10n'
109
import Vue from 'vue'
@@ -58,7 +57,6 @@ class ModifiedFilter extends FileListFilter {
5857

5958
constructor() {
6059
super('files:modified', 50)
61-
subscribe('files:navigation:changed', () => this.setPreset())
6260
}
6361

6462
public mount(el: HTMLElement) {
@@ -85,6 +83,10 @@ class ModifiedFilter extends FileListFilter {
8583
return nodes.filter((node) => node.mtime === undefined || this.currentPreset!.filter(node.mtime.getTime()))
8684
}
8785

86+
public reset(): void {
87+
this.setPreset()
88+
}
89+
8890
public setPreset(preset?: ITimePreset) {
8991
this.currentPreset = preset
9092
this.filterUpdated()

apps/files/src/filters/TypeFilter.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
*/
55
import type { IFileListFilterChip, INode } from '@nextcloud/files'
66

7-
import { subscribe } from '@nextcloud/event-bus'
87
import { FileListFilter, registerFileListFilter } from '@nextcloud/files'
98
import { t } from '@nextcloud/l10n'
109
import Vue from 'vue'
@@ -94,7 +93,6 @@ class TypeFilter extends FileListFilter {
9493

9594
constructor() {
9695
super('files:type', 10)
97-
subscribe('files:navigation:changed', () => this.setPreset())
9896
}
9997

10098
public async mount(el: HTMLElement) {
@@ -141,6 +139,10 @@ class TypeFilter extends FileListFilter {
141139
})
142140
}
143141

142+
public reset(): void {
143+
this.setPreset()
144+
}
145+
144146
public setPreset(presets?: ITypePreset[]) {
145147
this.currentPresets = presets
146148
this.filterUpdated()

apps/files/src/store/filters.ts

Lines changed: 123 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -7,74 +7,133 @@ import { subscribe } from '@nextcloud/event-bus'
77
import { getFileListFilters } from '@nextcloud/files'
88
import { defineStore } from 'pinia'
99
import logger from '../logger'
10+
import { computed, ref } from 'vue'
1011

11-
export const useFiltersStore = defineStore('filters', {
12-
state: () => ({
13-
chips: {} as Record<string, IFileListFilterChip[]>,
14-
filters: [] as IFileListFilter[],
15-
filtersChanged: false,
16-
}),
17-
18-
getters: {
19-
/**
20-
* Currently active filter chips
21-
* @param state Internal state
22-
*/
23-
activeChips(state): IFileListFilterChip[] {
24-
return Object.values(state.chips).flat()
25-
},
26-
27-
/**
28-
* Filters sorted by order
29-
* @param state Internal state
30-
*/
31-
sortedFilters(state): IFileListFilter[] {
32-
return state.filters.sort((a, b) => a.order - b.order)
33-
},
34-
35-
/**
36-
* All filters that provide a UI for visual controlling the filter state
37-
*/
38-
filtersWithUI(): Required<IFileListFilter>[] {
39-
return this.sortedFilters.filter((filter) => 'mount' in filter) as Required<IFileListFilter>[]
40-
},
41-
},
42-
43-
actions: {
44-
addFilter(filter: IFileListFilter) {
45-
filter.addEventListener('update:chips', this.onFilterUpdateChips)
46-
filter.addEventListener('update:filter', this.onFilterUpdate)
47-
this.filters.push(filter)
48-
logger.debug('New file list filter registered', { id: filter.id })
49-
},
50-
51-
removeFilter(filterId: string) {
52-
const index = this.filters.findIndex(({ id }) => id === filterId)
53-
if (index > -1) {
54-
const [filter] = this.filters.splice(index, 1)
55-
filter.removeEventListener('update:chips', this.onFilterUpdateChips)
56-
filter.removeEventListener('update:filter', this.onFilterUpdate)
57-
logger.debug('Files list filter unregistered', { id: filterId })
58-
}
59-
},
12+
/**
13+
* Check if the given value is an instance file list filter with mount function
14+
* @param value The filter to check
15+
*/
16+
function isFileListFilterWithUi(value: IFileListFilter): value is Required<IFileListFilter> {
17+
return 'mount' in value
18+
}
19+
20+
export const useFiltersStore = defineStore('filters', () => {
21+
const chips = ref<Record<string, IFileListFilterChip[]>>({})
22+
const filters = ref<IFileListFilter[]>([])
23+
const filtersChanged = ref(false)
24+
25+
26+
/**
27+
* Currently active filter chips
28+
*/
29+
const activeChips = computed<IFileListFilterChip[]>(
30+
() => Object.values(chips.value).flat(),
31+
)
32+
33+
/**
34+
* Filters sorted by order
35+
*/
36+
const sortedFilters = computed<IFileListFilter[]>(
37+
() => filters.value.sort((a, b) => a.order - b.order),
38+
)
39+
40+
/**
41+
* All filters that provide a UI for visual controlling the filter state
42+
*/
43+
const filtersWithUI = computed<Required<IFileListFilter>[]>(
44+
() => sortedFilters.value.filter(isFileListFilterWithUi)
45+
)
46+
47+
/**
48+
* Register a new filter on the store.
49+
* This will subscribe the store to the filters events.
50+
*
51+
* @param filter The filter to add
52+
*/
53+
function addFilter(filter: IFileListFilter) {
54+
filter.addEventListener('update:chips', onFilterUpdateChips)
55+
filter.addEventListener('update:filter', onFilterUpdate)
6056

61-
onFilterUpdate() {
62-
this.filtersChanged = true
63-
},
57+
filters.value.push(filter)
58+
logger.debug('New file list filter registered', { id: filter.id })
59+
}
6460

65-
onFilterUpdateChips(event: FilterUpdateChipsEvent) {
66-
const id = (event.target as IFileListFilter).id
67-
this.chips = { ...this.chips, [id]: [...event.detail] }
61+
/**
62+
* Unregister a filter from the store.
63+
* This will remove the filter from the store and unsubscribe the store from the filer events.
64+
* @param filterId Id of the filter to remove
65+
*/
66+
function removeFilter(filterId: string) {
67+
const index = filters.value.findIndex(({ id }) => id === filterId)
68+
if (index > -1) {
69+
const [filter] = filters.value.splice(index, 1)
70+
filter.removeEventListener('update:chips', onFilterUpdateChips)
71+
filter.removeEventListener('update:filter', onFilterUpdate)
72+
logger.debug('Files list filter unregistered', { id: filterId })
73+
}
74+
}
6875

69-
logger.debug('File list filter chips updated', { filter: id, chips: event.detail })
70-
},
76+
/**
77+
* Event handler for filter update events
78+
* @private
79+
*/
80+
function onFilterUpdate() {
81+
filtersChanged.value = true
82+
}
7183

72-
init() {
73-
subscribe('files:filter:added', this.addFilter)
74-
subscribe('files:filter:removed', this.removeFilter)
75-
for (const filter of getFileListFilters()) {
76-
this.addFilter(filter)
84+
/**
85+
* Event handler for filter chips updates
86+
* @param event The update event
87+
* @private
88+
*/
89+
function onFilterUpdateChips(event: FilterUpdateChipsEvent) {
90+
const id = (event.target as IFileListFilter).id
91+
chips.value = {
92+
...chips.value,
93+
[id]: [...event.detail],
94+
}
95+
96+
logger.debug('File list filter chips updated', { filter: id, chips: event.detail })
97+
}
98+
99+
/**
100+
* Event handler that resets all filters if the file list view was changed.
101+
* @private
102+
*/
103+
function onViewChanged() {
104+
logger.debug('Reset all file list filters - view changed')
105+
106+
for (const filter of filters.value) {
107+
if (filter.reset !== undefined) {
108+
filter.reset()
77109
}
78-
},
79-
},
110+
}
111+
}
112+
113+
// Initialize the store
114+
{
115+
subscribe('files:filter:added', addFilter)
116+
subscribe('files:filter:removed', removeFilter)
117+
for (const filter of getFileListFilters()) {
118+
addFilter(filter)
119+
}
120+
121+
subscribe('files:navigation:changed', onViewChanged)
122+
}
123+
124+
return {
125+
// state
126+
chips,
127+
filters,
128+
filtersWithUI,
129+
filtersChanged,
130+
131+
// getters / computed
132+
activeChips,
133+
sortedFilters,
134+
135+
// actions / methods
136+
addFilter,
137+
removeFilter,
138+
}
80139
})

apps/files/src/views/FilesList.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,6 @@ export default defineComponent({
522522
},
523523
524524
mounted() {
525-
this.filtersStore.init()
526525
this.fetchContent()
527526
528527
subscribe('files:node:deleted', this.onNodeDeleted)

apps/files_sharing/src/files_filters/AccountFilter.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ class AccountFilter extends FileListFilter {
6666
})
6767
}
6868

69+
public reset(): void {
70+
this.currentInstance?.resetFilter()
71+
}
72+
6973
public setAccounts(accounts?: IAccountData[]) {
7074
this.filterAccounts = accounts
7175
let chips: IFileListFilterChip[] = []

0 commit comments

Comments
 (0)