diff options
author | John Molakvoæ <skjnldsv@protonmail.com> | 2023-03-24 09:41:40 +0100 |
---|---|---|
committer | John Molakvoæ <skjnldsv@protonmail.com> | 2023-04-06 14:49:31 +0200 |
commit | 3c3050c76f86c7a8cc2f217f9305cb1051e0eca0 (patch) | |
tree | d9656a549b1db4c7f3d37549713a6c96da616464 /apps/files/src/components | |
parent | 0b4da6117fff4d999cb492503a8b6fc04eb75f9d (diff) | |
download | nextcloud-server-3c3050c76f86c7a8cc2f217f9305cb1051e0eca0.tar.gz nextcloud-server-3c3050c76f86c7a8cc2f217f9305cb1051e0eca0.zip |
feat(files): implement sorting per view
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
Diffstat (limited to 'apps/files/src/components')
-rw-r--r-- | apps/files/src/components/FileEntry.vue | 10 | ||||
-rw-r--r-- | apps/files/src/components/FilesListHeader.vue | 90 | ||||
-rw-r--r-- | apps/files/src/components/FilesListHeaderButton.vue | 160 | ||||
-rw-r--r-- | apps/files/src/components/FilesListVirtual.vue | 6 |
4 files changed, 235 insertions, 31 deletions
diff --git a/apps/files/src/components/FileEntry.vue b/apps/files/src/components/FileEntry.vue index ea9615af596..29e9895757e 100644 --- a/apps/files/src/components/FileEntry.vue +++ b/apps/files/src/components/FileEntry.vue @@ -50,7 +50,7 @@ </span> <!-- File name --> - {{ displayName }} + <span>{{ displayName }}</span> </a> </td> @@ -89,17 +89,17 @@ </td> <!-- Size --> - <th v-if="isSizeAvailable" + <td v-if="isSizeAvailable" :style="{ opacity: sizeOpacity }" class="files-list__row-size"> <span>{{ size }}</span> - </th> + </td> <!-- View columns --> <td v-for="column in columns" :key="column.id" :class="`files-list__row-${currentView?.id}-${column.id}`" - class="files-list__row-column--custom"> + class="files-list__row-column-custom"> <CustomElementRender :element="column.render(source)" /> </td> </Fragment> @@ -207,7 +207,7 @@ export default Vue.extend({ }, size() { const size = parseInt(this.source.size, 10) || 0 - if (!size || size < 0) { + if (typeof size !== 'number' || size < 0) { return this.t('files', 'Pending') } return formatFileSize(size, true) diff --git a/apps/files/src/components/FilesListHeader.vue b/apps/files/src/components/FilesListHeader.vue index 1fe6d230a20..0ee7298ee95 100644 --- a/apps/files/src/components/FilesListHeader.vue +++ b/apps/files/src/components/FilesListHeader.vue @@ -21,22 +21,18 @@ --> <template> <tr> - <th class="files-list__row-checkbox"> + <th class="files-list__column files-list__row-checkbox"> <NcCheckboxRadioSwitch v-bind="selectAllBind" @update:checked="onToggleAll" /> </th> <!-- Link to file --> - <th class="files-list__row-name files-list__row--sortable" - @click="toggleSortBy('basename')"> + <th class="files-list__column files-list__row-name files-list__column--sortable" + @click.exact.stop="toggleSortBy('basename')"> <!-- Icon or preview --> <span class="files-list__row-icon" /> <!-- Name --> - {{ t('files', 'Name') }} - <template v-if="defaultFileSorting === 'basename'"> - <MenuUp v-if="defaultFileSortingDirection === 'asc'" /> - <MenuDown v-else /> - </template> + <FilesListHeaderButton :name="t('files', 'Name')" mode="basename" /> </th> <!-- Actions --> @@ -44,20 +40,19 @@ <!-- Size --> <th v-if="isSizeAvailable" - class="files-list__row-size" - @click="toggleSortBy('size')"> - {{ t('files', 'Size') }} - <template v-if="defaultFileSorting === 'size'"> - <MenuUp v-if="defaultFileSortingDirection === 'asc'" /> - <MenuDown v-else /> - </template> + :class="{'files-list__column--sortable': isSizeAvailable}" + class="files-list__column files-list__row-size"> + <FilesListHeaderButton :name="t('files', 'Size')" mode="size" /> </th> <!-- Custom views columns --> <th v-for="column in columns" :key="column.id" - :class="`files-list__row-column--custom files-list__row-${currentView.id}-${column.id}`"> - {{ column.title }} + :class="classForColumn(column)"> + <FilesListHeaderButton v-if="!!column.sort" :name="column.title" :mode="column.id" /> + <span v-else> + {{ column.title }} + </span> </th> </tr> </template> @@ -67,6 +62,7 @@ import { mapState } from 'pinia' import { translate } from '@nextcloud/l10n' import MenuDown from 'vue-material-design-icons/MenuDown.vue' import MenuUp from 'vue-material-design-icons/MenuUp.vue' +import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js' import Vue from 'vue' @@ -75,15 +71,13 @@ import { useSelectionStore } from '../store/selection' import { useSortingStore } from '../store/sorting' import logger from '../logger.js' import Navigation from '../services/Navigation' - -Vue.config.performance = true +import FilesListHeaderButton from './FilesListHeaderButton.vue' export default Vue.extend({ name: 'FilesListHeader', components: { - MenuDown, - MenuUp, + FilesListHeaderButton, NcCheckboxRadioSwitch, }, @@ -110,7 +104,7 @@ export default Vue.extend({ }, computed: { - ...mapState(useSortingStore, ['defaultFileSorting', 'defaultFileSortingDirection']), + ...mapState(useSortingStore, ['filesSortingConfig']), /** @return {Navigation} */ currentView() { @@ -153,9 +147,37 @@ export default Vue.extend({ selectedFiles() { return this.selectionStore.selected }, + + sortingMode() { + return this.sortingStore.getSortingMode(this.currentView.id) + || this.currentView.defaultSortKey + || 'basename' + }, + isAscSorting() { + return this.sortingStore.isAscSorting(this.currentView.id) === true + }, }, methods: { + classForColumn(column) { + return { + 'files-list__column': true, + 'files-list__column--sortable': !!column.sort, + 'files-list__row-column-custom': true, + [`files-list__row-${this.currentView.id}-${column.id}`]: true, + } + }, + + sortAriaLabel(column) { + const direction = this.isAscSorting + ? this.t('files', 'ascending') + : this.t('files', 'descending') + return this.t('files', 'Sort list by {column} ({direction})', { + column, + direction, + }) + }, + onToggleAll(selected) { if (selected) { const selection = this.nodes.map(node => node.attributes.fileid.toString()) @@ -169,12 +191,19 @@ export default Vue.extend({ toggleSortBy(key) { // If we're already sorting by this key, flip the direction - if (this.defaultFileSorting === key) { - this.sortingStore.toggleSortingDirection() + if (this.sortingMode === key) { + this.sortingStore.toggleSortingDirection(this.currentView.id) return } // else sort ASC by this new key - this.sortingStore.setFileSorting(key) + this.sortingStore.setSortingBy(key, this.currentView.id) + }, + + toggleSortByCustomColumn(column) { + if (!column.sort) { + return + } + this.toggleSortBy(column.id) }, t: translate, @@ -183,6 +212,15 @@ export default Vue.extend({ </script> <style scoped lang="scss"> -@import '../mixins/fileslist-row.scss' +@import '../mixins/fileslist-row.scss'; +.files-list__column { + user-select: none; + // Make sure the cell colors don't apply to column headers + color: var(--color-text-maxcontrast) !important; + + &--sortable { + cursor: pointer; + } +} </style> diff --git a/apps/files/src/components/FilesListHeaderButton.vue b/apps/files/src/components/FilesListHeaderButton.vue new file mode 100644 index 00000000000..8a07dd71395 --- /dev/null +++ b/apps/files/src/components/FilesListHeaderButton.vue @@ -0,0 +1,160 @@ +<!-- + - @copyright Copyright (c) 2019 Gary Kim <gary@garykim.dev> + - + - @author Gary Kim <gary@garykim.dev> + - + - @license GNU AGPL version 3 or any later version + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU Affero General Public License as + - published by the Free Software Foundation, either version 3 of the + - License, or (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU Affero General Public License for more details. + - + - You should have received a copy of the GNU Affero General Public License + - along with this program. If not, see <http://www.gnu.org/licenses/>. + - + --> +<template> + <NcButton :aria-label="sortAriaLabel(name)" + :class="{'files-list__column-sort-button--active': sortingMode === mode}" + class="files-list__column-sort-button" + type="tertiary" + @click="toggleSortBy(mode)"> + <!-- Sort icon before text as size is align right --> + <MenuUp v-if="sortingMode !== mode || isAscSorting" slot="icon" /> + <MenuDown v-else slot="icon" /> + {{ name }} + </NcButton> +</template> + +<script lang="ts"> +import { mapState } from 'pinia' +import { translate } from '@nextcloud/l10n' +import MenuDown from 'vue-material-design-icons/MenuDown.vue' +import MenuUp from 'vue-material-design-icons/MenuUp.vue' +import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' +import Vue from 'vue' + +import { useSortingStore } from '../store/sorting' + +Vue.config.performance = true + +export default Vue.extend({ + name: 'FilesListHeaderButton', + + components: { + MenuDown, + MenuUp, + NcButton, + }, + + props: { + name: { + type: String, + required: true, + }, + mode: { + type: String, + required: true, + }, + }, + + setup() { + const sortingStore = useSortingStore() + return { + sortingStore, + } + }, + + computed: { + ...mapState(useSortingStore, ['filesSortingConfig']), + + currentView() { + return this.$navigation.active + }, + + sortingMode() { + return this.sortingStore.getSortingMode(this.currentView.id) + || this.currentView.defaultSortKey + || 'basename' + }, + isAscSorting() { + return this.sortingStore.isAscSorting(this.currentView.id) === true + }, + }, + + methods: { + sortAriaLabel(column) { + const direction = this.isAscSorting + ? this.t('files', 'ascending') + : this.t('files', 'descending') + return this.t('files', 'Sort list by {column} ({direction})', { + column, + direction, + }) + }, + + toggleSortBy(key) { + // If we're already sorting by this key, flip the direction + if (this.sortingMode === key) { + this.sortingStore.toggleSortingDirection(this.currentView.id) + return + } + // else sort ASC by this new key + this.sortingStore.setSortingBy(key, this.currentView.id) + }, + + toggleSortByCustomColumn(column) { + if (!column.sort) { + return + } + this.toggleSortBy(column.id) + }, + + t: translate, + }, +}) +</script> + +<style lang="scss"> +.files-list__column-sort-button { + // Compensate for cells margin + margin: 0 calc(var(--cell-margin) * -1); + // Reverse padding + padding: 0 4px 0 16px !important; + + // Icon after text + .button-vue__wrapper { + flex-direction: row-reverse; + // Take max inner width for text overflow ellipsis + width: 100%; + } + + .button-vue__icon { + transition-timing-function: linear; + transition-duration: .1s; + transition-property: opacity; + opacity: 0; + } + + .button-vue__text { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + &--active, + &:hover, + &:focus, + &:active { + .button-vue__icon { + opacity: 1 !important; + } + } +} +</style> diff --git a/apps/files/src/components/FilesListVirtual.vue b/apps/files/src/components/FilesListVirtual.vue index 3f055f8b878..eafac678310 100644 --- a/apps/files/src/components/FilesListVirtual.vue +++ b/apps/files/src/components/FilesListVirtual.vue @@ -151,6 +151,12 @@ export default Vue.extend({ align-items: center; width: 100%; border-bottom: 1px solid var(--color-border); + + &:hover, + &:focus, + &:active { + background-color: var(--color-background-dark); + } } } } |