summaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorJohn Molakvoæ <skjnldsv@protonmail.com>2023-03-21 09:53:31 +0100
committerJohn Molakvoæ <skjnldsv@protonmail.com>2023-04-06 14:49:30 +0200
commit10010fc532a02958804667e1cb3acee8e9556394 (patch)
treebbb99c171e6e32b85b5aee9365ad5e9ebe68054e /apps
parentb761039cf1946cb64898b9117d1b15dd89080451 (diff)
downloadnextcloud-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.php2
-rw-r--r--apps/files/lib/Controller/ViewController.php6
-rw-r--r--apps/files/src/components/FilesListHeader.vue41
-rw-r--r--apps/files/src/mixins/fileslist-row.scss6
-rw-r--r--apps/files/src/store/sorting.ts73
-rw-r--r--apps/files/src/views/FilesList.vue33
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)
})
},