diff options
author | John Molakvoæ <skjnldsv@protonmail.com> | 2023-03-21 09:53:31 +0100 |
---|---|---|
committer | John Molakvoæ <skjnldsv@protonmail.com> | 2023-04-06 14:49:30 +0200 |
commit | 10010fc532a02958804667e1cb3acee8e9556394 (patch) | |
tree | bbb99c171e6e32b85b5aee9365ad5e9ebe68054e /apps | |
parent | b761039cf1946cb64898b9117d1b15dd89080451 (diff) | |
download | nextcloud-server-10010fc532a02958804667e1cb3acee8e9556394.tar.gz nextcloud-server-10010fc532a02958804667e1cb3acee8e9556394.zip |
feat(files): sorting
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
Diffstat (limited to 'apps')
-rw-r--r-- | apps/files/lib/Controller/ApiController.php | 2 | ||||
-rw-r--r-- | apps/files/lib/Controller/ViewController.php | 6 | ||||
-rw-r--r-- | apps/files/src/components/FilesListHeader.vue | 41 | ||||
-rw-r--r-- | apps/files/src/mixins/fileslist-row.scss | 6 | ||||
-rw-r--r-- | apps/files/src/store/sorting.ts | 73 | ||||
-rw-r--r-- | apps/files/src/views/FilesList.vue | 33 |
6 files changed, 135 insertions, 26 deletions
diff --git a/apps/files/lib/Controller/ApiController.php b/apps/files/lib/Controller/ApiController.php index 85507132edd..c7da9b2c118 100644 --- a/apps/files/lib/Controller/ApiController.php +++ b/apps/files/lib/Controller/ApiController.php @@ -285,7 +285,7 @@ class ApiController extends Controller { * @throws \OCP\PreConditionNotMetException */ public function updateFileSorting($mode, $direction) { - $allowedMode = ['name', 'size', 'mtime']; + $allowedMode = ['basename', 'size', 'mtime']; $allowedDirection = ['asc', 'desc']; if (!in_array($mode, $allowedMode) || !in_array($direction, $allowedDirection)) { $response = new Response(); diff --git a/apps/files/lib/Controller/ViewController.php b/apps/files/lib/Controller/ViewController.php index 5133661d725..6047ad81808 100644 --- a/apps/files/lib/Controller/ViewController.php +++ b/apps/files/lib/Controller/ViewController.php @@ -249,6 +249,12 @@ class ViewController extends Controller { $this->initialState->provideInitialState('navigation', $navItems); $this->initialState->provideInitialState('config', $this->userConfig->getConfigs()); + // File sorting user config + $defaultFileSorting = $this->config->getUserValue($userId, 'files', 'file_sorting', 'basename'); + $defaultFileSortingDirection = $this->config->getUserValue($userId, 'files', 'file_sorting_direction', 'asc'); + $this->initialState->provideInitialState('defaultFileSorting', $defaultFileSorting === 'name' ? 'basename' : $defaultFileSorting); + $this->initialState->provideInitialState('defaultFileSortingDirection', $defaultFileSortingDirection === 'desc' ? 'desc' : 'asc'); + // render the container content for every navigation item foreach ($navItems as $item) { $content = ''; diff --git a/apps/files/src/components/FilesListHeader.vue b/apps/files/src/components/FilesListHeader.vue index 8376a30d55c..b09feae04f2 100644 --- a/apps/files/src/components/FilesListHeader.vue +++ b/apps/files/src/components/FilesListHeader.vue @@ -29,8 +29,13 @@ <th class="files-list__row-icon" /> <!-- Link to file and --> - <th class="files-list__row-name"> + <th class="files-list__row-name files-list__row--sortable" + @click="toggleSortBy('basename')"> {{ t('files', 'Name') }} + <template v-if="defaultFileSorting === 'basename'"> + <MenuUp v-if="defaultFileSortingDirection === 'asc'" /> + <MenuDown v-else /> + </template> </th> <!-- Actions --> @@ -40,18 +45,24 @@ <script lang="ts"> import { File, Folder } from '@nextcloud/files' +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 NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js' import Vue from 'vue' -import logger from '../logger' -import { useSelectionStore } from '../store/selection' import { useFilesStore } from '../store/files' +import { useSelectionStore } from '../store/selection' +import { useSortingStore } from '../store/sorting' +import logger from '../logger.js' export default Vue.extend({ name: 'FilesListHeader', components: { + MenuDown, + MenuUp, NcCheckboxRadioSwitch, }, @@ -65,13 +76,17 @@ export default Vue.extend({ setup() { const filesStore = useFilesStore() const selectionStore = useSelectionStore() + const sortingStore = useSortingStore() return { filesStore, selectionStore, + sortingStore, } }, computed: { + ...mapState(useSortingStore, ['defaultFileSorting', 'defaultFileSortingDirection']), + dir() { // Remove any trailing slash but leave root slash return (this.$route?.query?.dir || '/').replace(/^(.+)\/$/, '$1') @@ -107,16 +122,6 @@ export default Vue.extend({ }, methods: { - /** - * Get a cached note from the store - * - * @param {number} fileId the file id to get - * @return {Folder|File} - */ - getNode(fileId) { - return this.filesStore.getNode(fileId) - }, - onToggleAll(selected) { if (selected) { const selection = this.nodes.map(node => node.attributes.fileid.toString()) @@ -128,6 +133,16 @@ export default Vue.extend({ } }, + toggleSortBy(key) { + // If we're already sorting by this key, flip the direction + if (this.defaultFileSorting === key) { + this.sortingStore.toggleSortingDirection() + return + } + // else sort ASC by this new key + this.sortingStore.setFileSorting(key) + }, + t: translate, }, }) diff --git a/apps/files/src/mixins/fileslist-row.scss b/apps/files/src/mixins/fileslist-row.scss index 9ebd8f00b36..1315a5724f2 100644 --- a/apps/files/src/mixins/fileslist-row.scss +++ b/apps/files/src/mixins/fileslist-row.scss @@ -30,6 +30,12 @@ td, th { border: none; } +.files-list__row { + &--sortable { + cursor: pointer; + } +} + .files-list__row-checkbox { width: var(--row-height); &::v-deep .checkbox-radio-switch { diff --git a/apps/files/src/store/sorting.ts b/apps/files/src/store/sorting.ts new file mode 100644 index 00000000000..b153301b76b --- /dev/null +++ b/apps/files/src/store/sorting.ts @@ -0,0 +1,73 @@ +/** + * @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com> + * + * @author John Molakvoæ <skjnldsv@protonmail.com> + * + * @license AGPL-3.0-or-later + * + * 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/>. + * + */ +/* eslint-disable */ +import { loadState } from '@nextcloud/initial-state' +import { generateUrl } from '@nextcloud/router' +import { defineStore } from 'pinia' +import Vue from 'vue' +import axios from '@nextcloud/axios' + +type direction = 'asc' | 'desc' + +const saveUserConfig = (key: string, direction: direction) => { + return axios.post(generateUrl('/apps/files/api/v1/sorting'), { + mode: key, + direction: direction as string, + }) +} + +const defaultFileSorting = loadState('files', 'defaultFileSorting', 'basename') +const defaultFileSortingDirection = loadState('files', 'defaultFileSortingDirection', 'asc') as direction + +export const useSortingStore = defineStore('sorting', { + state: () => ({ + defaultFileSorting, + defaultFileSortingDirection, + }), + + getters: { + isAscSorting: (state) => state.defaultFileSortingDirection === 'asc', + }, + + actions: { + /** + * Set the sorting key AND sort by ASC + * The key param must be a valid key of a File object + * If not found, will be searched within the File attributes + */ + setSortingBy(key: string) { + Vue.set(this, 'defaultFileSorting', key) + Vue.set(this, 'defaultFileSortingDirection', 'asc') + saveUserConfig(key, 'asc') + }, + + /** + * Toggle the sorting direction + */ + toggleSortingDirection() { + const newDirection = this.defaultFileSortingDirection === 'asc' ? 'desc' : 'asc' + Vue.set(this, 'defaultFileSortingDirection', newDirection) + saveUserConfig(this.defaultFileSorting, newDirection) + } + } +}) + diff --git a/apps/files/src/views/FilesList.vue b/apps/files/src/views/FilesList.vue index 5e9a098c853..e261b375862 100644 --- a/apps/files/src/views/FilesList.vue +++ b/apps/files/src/views/FilesList.vue @@ -62,23 +62,24 @@ <script lang="ts"> import { Folder } from '@nextcloud/files' +import { join } from 'path' import { translate } from '@nextcloud/l10n' import NcAppContent from '@nextcloud/vue/dist/Components/NcAppContent.js' import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js' import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js' import TrashCan from 'vue-material-design-icons/TrashCan.vue' +import Vue from 'vue' -import BreadCrumbs from '../components/BreadCrumbs.vue' -import logger from '../logger.js' -import Navigation from '../services/Navigation' -import FilesListVirtual from '../components/FilesListVirtual.vue' import { ContentsWithRoot } from '../services/Navigation' -import { join } from 'path' -import Vue from 'vue' -import { usePathsStore } from '../store/paths' import { useFilesStore } from '../store/files' +import { usePathsStore } from '../store/paths' import { useSelectionStore } from '../store/selection' +import { useSortingStore } from '../store/sorting' +import BreadCrumbs from '../components/BreadCrumbs.vue' +import FilesListVirtual from '../components/FilesListVirtual.vue' +import logger from '../logger.js' +import Navigation from '../services/Navigation' export default Vue.extend({ name: 'FilesList', @@ -105,10 +106,12 @@ export default Vue.extend({ const pathsStore = usePathsStore() const filesStore = useFilesStore() const selectionStore = useSelectionStore() + const sortingStore = useSortingStore() return { filesStore, pathsStore, selectionStore, + sortingStore, } }, @@ -116,8 +119,6 @@ export default Vue.extend({ return { loading: true, promise: null, - sortKey: 'basename', - sortAsc: true, } }, @@ -162,17 +163,25 @@ export default Vue.extend({ * @return {Node[]} */ dirContents() { + const sortAsc = this.sortingStore.isAscSorting === true + const sortKey = this.sortingStore.defaultFileSorting || 'basename' + return [...(this.currentFolder?.children || []).map(this.getNode)] .sort((a, b) => { + // Sort folders first if (a.type === 'folder' && b.type !== 'folder') { - return this.sortAsc ? -1 : 1 + return sortAsc ? -1 : 1 } if (a.type !== 'folder' && b.type === 'folder') { - return this.sortAsc ? 1 : -1 + return sortAsc ? 1 : -1 + } + + if (typeof a[sortKey] === 'number' && typeof b[sortKey] === 'number') { + return (a[sortKey] - b[sortKey]) * (sortAsc ? 1 : -1) } - return (a[this.sortKey] || a.basename).localeCompare(b[this.sortKey] || b.basename) * (this.sortAsc ? 1 : -1) + return (a[sortKey] || a.basename).localeCompare(b[sortKey] || b.basename) * (sortAsc ? 1 : -1) }) }, |