-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(files): Implement files list filters for name, modified time and…
… type Signed-off-by: Ferdinand Thiessen <[email protected]>
- Loading branch information
Showing
5 changed files
with
521 additions
and
0 deletions.
There are no files selected for viewing
59 changes: 59 additions & 0 deletions
59
apps/files/src/components/FilesListFilter/FilesListFilter.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
<template> | ||
<NcActions force-menu | ||
:type="isActive ? 'primary' : 'tertiary'" | ||
:menu-name="filterName"> | ||
<template #icon> | ||
<slot name="icon" /> | ||
</template> | ||
<template v-if="isActive"> | ||
<NcActionButton class="files-list-filter__clear-button" close-after-click @click="$emit('reset-filter')"> | ||
{{ t('files', 'Clear filter') }} | ||
</NcActionButton> | ||
<NcActionSeparator /> | ||
</template> | ||
<slot /> | ||
</NcActions> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { translate as t } from '@nextcloud/l10n' | ||
import { defineComponent } from 'vue' | ||
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js' | ||
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js' | ||
import NcActionSeparator from '@nextcloud/vue/dist/Components/NcActionSeparator.js' | ||
export default defineComponent({ | ||
name: 'FilesListFilter', | ||
components: { | ||
NcActions, | ||
NcActionButton, | ||
NcActionSeparator, | ||
}, | ||
props: { | ||
isActive: { | ||
type: Boolean, | ||
required: true, | ||
}, | ||
filterName: { | ||
type: String, | ||
required: true, | ||
}, | ||
}, | ||
emits: ['reset-filter'], | ||
methods: { | ||
t, | ||
}, | ||
}) | ||
</script> | ||
|
||
<style scoped> | ||
.files-list-filter__clear-button :deep(.action-button__text) { | ||
color: var(--color-error-text); | ||
} | ||
</style> |
98 changes: 98 additions & 0 deletions
98
apps/files/src/components/FilesListFilter/FilesListFilterName.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
<template> | ||
<NcTextField :value.sync="query" | ||
:label="t('files', 'Filename')" | ||
show-trailing-button | ||
:trailing-button-label="t('files', 'Clear filter')" | ||
@trailing-button-click="resetFilter" /> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import type { Node } from '@nextcloud/files' | ||
import { subscribe, unsubscribe } from '@nextcloud/event-bus' | ||
import { translate as t } from '@nextcloud/l10n' | ||
import { defineComponent } from 'vue' | ||
import debounce from 'debounce' | ||
import useFilesFilter from '../../composables/useFilesFilter.ts' | ||
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' | ||
export default defineComponent({ | ||
name: 'FilesListFilterName', | ||
components: { | ||
NcTextField, | ||
}, | ||
props: { | ||
}, | ||
setup() { | ||
return { | ||
...useFilesFilter(), | ||
} | ||
}, | ||
data() { | ||
return { | ||
query: '', | ||
} | ||
}, | ||
computed: { | ||
debouncedSetFilter() { | ||
return debounce(this.setFilter, 200) | ||
}, | ||
}, | ||
watch: { | ||
query(newValue: string) { | ||
if (!newValue) { | ||
// Remove filter if no query is set | ||
this.deleteFilter('files-filter-name') | ||
} else { | ||
this.debouncedSetFilter() | ||
} | ||
}, | ||
}, | ||
mounted() { | ||
subscribe('nextcloud:unified-search.search', this.onSearch) | ||
subscribe('nextcloud:unified-search.reset', this.resetFilter) | ||
}, | ||
beforeDestroy() { | ||
this.deleteFilter('files-filter-name') | ||
unsubscribe('nextcloud:unified-search.search', this.onSearch) | ||
unsubscribe('nextcloud:unified-search.reset', this.resetFilter) | ||
}, | ||
methods: { | ||
t, | ||
setFilter() { | ||
this.addFilter({ | ||
id: 'files-filter-name', | ||
filter: (node: Node) => { | ||
if (!this.query) { | ||
return true | ||
} | ||
const query = this.query.toLowerCase() | ||
return (node.attributes.displayName ?? node.basename).toLowerCase().includes(query) | ||
}, | ||
}) | ||
}, | ||
onSearch({ query }: { query: string }) { | ||
this.query = query ?? '' | ||
}, | ||
resetFilter() { | ||
this.query = '' | ||
}, | ||
}, | ||
}) | ||
</script> |
154 changes: 154 additions & 0 deletions
154
apps/files/src/components/FilesListFilter/FilesListFilterTime.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
<template> | ||
<FilesListFilter :is-active="isActive" | ||
:filter-name="label" | ||
@reset-filter="resetFilter"> | ||
<template #icon> | ||
<NcIconSvgWrapper :path="mdiCalendarRange" /> | ||
</template> | ||
<NcActionButton v-for="preset of timePresets" | ||
:key="preset.id" | ||
type="radio" | ||
close-after-click | ||
:model-value.sync="selectedOption" | ||
:value="preset.id"> | ||
{{ preset.label }} | ||
</NcActionButton> | ||
<!-- TODO: Custom time range --> | ||
</FilesListFilter> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import type { Node } from '@nextcloud/files' | ||
import { mdiCalendarRange } from '@mdi/js' | ||
import { translate as t } from '@nextcloud/l10n' | ||
import { defineComponent } from 'vue' | ||
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js' | ||
import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js' | ||
import FilesListFilter from './FilesListFilter.vue' | ||
import useFilesFilter from '../../composables/useFilesFilter' | ||
const startOfToday = () => (new Date()).setHours(0, 0, 0, 0) | ||
const timePresets = [ | ||
{ | ||
id: 'today', | ||
label: t('files', 'Today'), | ||
filter: (time: number) => time > startOfToday(), | ||
}, | ||
{ | ||
id: 'last-7', | ||
label: t('files', 'Last 7 days'), | ||
filter: (time: number) => time > (startOfToday() - (7 * 24 * 60 * 60 * 1000)), | ||
}, | ||
{ | ||
id: 'last-30', | ||
label: t('files', 'Last 30 days'), | ||
filter: (time: number) => time > (startOfToday() - (30 * 24 * 60 * 60 * 1000)), | ||
}, | ||
{ | ||
id: 'this-year', | ||
label: t('files', 'This year ({year})', { year: (new Date()).getFullYear() }), | ||
filter: (time: number) => time > (new Date(startOfToday())).setMonth(0, 1), | ||
}, | ||
{ | ||
id: 'last-year', | ||
label: t('files', 'Last year ({year})', { year: (new Date()).getFullYear() - 1 }), | ||
filter: (time: number) => (time > (new Date(startOfToday())).setFullYear((new Date()).getFullYear() - 1, 0, 1)) && (time < (new Date(startOfToday())).setMonth(0, 1)), | ||
}, | ||
] as const | ||
export default defineComponent({ | ||
components: { | ||
FilesListFilter, | ||
NcActionButton, | ||
NcIconSvgWrapper, | ||
}, | ||
props: { | ||
}, | ||
setup() { | ||
return { | ||
...useFilesFilter(), | ||
timePresets, | ||
// icons used in template | ||
mdiCalendarRange, | ||
} | ||
}, | ||
data() { | ||
return { | ||
selectedOption: null as (typeof timePresets)[number]['id'] | null, | ||
timeRangeEnd: null as number | null, | ||
timeRangeStart: null as number | null, | ||
} | ||
}, | ||
computed: { | ||
/** | ||
* Is the filter currently active | ||
*/ | ||
isActive() { | ||
return this.selectedOption !== null | ||
}, | ||
currentPreset() { | ||
return timePresets.find(({ id }) => id === this.selectedOption) ?? null | ||
}, | ||
label() { | ||
if (this.currentPreset) { | ||
return this.currentPreset.label | ||
} | ||
return t('files', 'Modified') | ||
}, | ||
}, | ||
watch: { | ||
selectedOption() { | ||
if (this.selectedOption === null) { | ||
this.deleteFilter('files-filter-time') | ||
} else { | ||
const preset = this.currentPreset | ||
this.addFilter({ | ||
id: 'files-filter-time', | ||
filter: (node: Node) => { | ||
if (!node.mtime) { | ||
return false | ||
} | ||
const mtime = node.mtime.getTime() | ||
if (preset) { | ||
return preset.filter(mtime) | ||
} else { | ||
return (!this.timeRangeStart || this.timeRangeStart < mtime) && (!this.timeRangeEnd || this.timeRangeEnd > mtime) | ||
} | ||
}, | ||
}) | ||
} | ||
}, | ||
}, | ||
methods: { | ||
t, | ||
resetFilter() { | ||
this.selectedOption = null | ||
this.timeRangeEnd = null | ||
this.timeRangeStart = null | ||
}, | ||
}, | ||
}) | ||
</script> | ||
|
||
<style scoped lang="scss"> | ||
.files-list-filter-time { | ||
&__clear-button :deep(.action-button__text) { | ||
color: var(--color-error-text); | ||
} | ||
} | ||
</style> |
Oops, something went wrong.