diff --git a/apps/files/js/app.js b/apps/files/js/app.js
index 8ebd506c1a3..1252bd5796c 100644
--- a/apps/files/js/app.js
+++ b/apps/files/js/app.js
@@ -116,7 +116,9 @@
},
],
sorting: {
- mode: $('#defaultFileSorting').val(),
+ mode: $('#defaultFileSorting').val() === 'basename'
+ ? 'name'
+ : $('#defaultFileSorting').val(),
direction: $('#defaultFileSortingDirection').val()
},
config: this._filesConfig,
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index 2d93ced7100..e3052ea9fe8 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -2181,8 +2181,10 @@
if (persist && OC.getCurrentUser().uid) {
$.post(OC.generateUrl('/apps/files/api/v1/sorting'), {
- mode: sort,
- direction: direction
+ // Compatibility with new files-to-vue API
+ mode: sort === 'name' ? 'basename' : sort,
+ direction: direction,
+ view: 'files'
});
}
},
diff --git a/apps/files/lib/Controller/ApiController.php b/apps/files/lib/Controller/ApiController.php
index c7da9b2c118..808f0d555d0 100644
--- a/apps/files/lib/Controller/ApiController.php
+++ b/apps/files/lib/Controller/ApiController.php
@@ -281,20 +281,29 @@ class ApiController extends Controller {
*
* @param string $mode
* @param string $direction
- * @return Response
+ * @return JSONResponse
* @throws \OCP\PreConditionNotMetException
*/
- public function updateFileSorting($mode, $direction) {
- $allowedMode = ['basename', 'size', 'mtime'];
+ public function updateFileSorting($mode, string $direction = 'asc', string $view = 'files'): JSONResponse {
$allowedDirection = ['asc', 'desc'];
- if (!in_array($mode, $allowedMode) || !in_array($direction, $allowedDirection)) {
- $response = new Response();
- $response->setStatus(Http::STATUS_UNPROCESSABLE_ENTITY);
- return $response;
+ if (!in_array($direction, $allowedDirection)) {
+ return new JSONResponse(['message' => 'Invalid direction parameter'], Http::STATUS_UNPROCESSABLE_ENTITY);
}
- $this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', 'file_sorting', $mode);
- $this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', 'file_sorting_direction', $direction);
- return new Response();
+
+ $userId = $this->userSession->getUser()->getUID();
+
+ $sortingJson = $this->config->getUserValue($userId, 'files', 'files_sorting_configs', '{}');
+ $sortingConfig = json_decode($sortingJson, true) ?: [];
+ $sortingConfig[$view] = [
+ 'mode' => $mode,
+ 'direction' => $direction,
+ ];
+
+ $this->config->setUserValue($userId, 'files', 'files_sorting_configs', json_encode($sortingConfig));
+ return new JSONResponse([
+ 'message' => 'ok',
+ 'data' => $sortingConfig,
+ ]);
}
/**
diff --git a/apps/files/lib/Controller/ViewController.php b/apps/files/lib/Controller/ViewController.php
index 6047ad81808..cb41dfb300b 100644
--- a/apps/files/lib/Controller/ViewController.php
+++ b/apps/files/lib/Controller/ViewController.php
@@ -250,10 +250,8 @@ class ViewController extends Controller {
$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');
+ $filesSortingConfig = json_decode($this->config->getUserValue($userId, 'files', 'files_sorting_configs', '{}'), true);
+ $this->initialState->provideInitialState('filesSortingConfig', $filesSortingConfig);
// render the container content for every navigation item
foreach ($navItems as $item) {
@@ -298,8 +296,8 @@ class ViewController extends Controller {
$params['ownerDisplayName'] = $storageInfo['ownerDisplayName'] ?? '';
$params['isPublic'] = false;
$params['allowShareWithLink'] = $this->shareManager->shareApiAllowLinks() ? 'yes' : 'no';
- $params['defaultFileSorting'] = $this->config->getUserValue($userId, 'files', 'file_sorting', 'name');
- $params['defaultFileSortingDirection'] = $this->config->getUserValue($userId, 'files', 'file_sorting_direction', 'asc');
+ $params['defaultFileSorting'] = $filesSortingConfig['files']['mode'] ?? 'basename';
+ $params['defaultFileSortingDirection'] = $filesSortingConfig['files']['direction'] ?? 'asc';
$params['showgridview'] = $this->config->getUserValue($userId, 'files', 'show_grid', false);
$showHidden = (bool) $this->config->getUserValue($userId, 'files', 'show_hidden', false);
$params['showHiddenFiles'] = $showHidden ? 1 : 0;
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 @@
- {{ displayName }}
+ {{ displayName }}
@@ -89,17 +89,17 @@
-
{{ size }}
- |
+
+ class="files-list__row-column-custom">
|
@@ -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 @@
-->
-
+ |
|
-
+ |
- {{ t('files', 'Name') }}
-
-
-
-
+
|
@@ -44,20 +40,19 @@
- {{ t('files', 'Size') }}
-
-
-
-
+ :class="{'files-list__column--sortable': isSizeAvailable}"
+ class="files-list__column files-list__row-size">
+
|
- {{ column.title }}
+ :class="classForColumn(column)">
+
+
+ {{ column.title }}
+
|
@@ -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({
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 @@
+
+
+
+
+
+
+ {{ name }}
+
+
+
+
+
+
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);
+ }
}
}
}
diff --git a/apps/files/src/mixins/fileslist-row.scss b/apps/files/src/mixins/fileslist-row.scss
index 9ad821eb860..06b6637b6f2 100644
--- a/apps/files/src/mixins/fileslist-row.scss
+++ b/apps/files/src/mixins/fileslist-row.scss
@@ -19,6 +19,11 @@
* along with this program. If not, see .
*
*/
+
+/**
+ * This file is for every column styling that must be
+ * shared between the files list and the list header.
+ */
td, th {
display: flex;
align-items: center;
@@ -31,6 +36,9 @@ td, th {
color: var(--color-text-maxcontrast);
border: none;
+ // Columns should try to add any text
+ // node wrapped in a span. That should help
+ // with the ellipsis on overflow.
span {
overflow: hidden;
white-space: nowrap;
@@ -38,12 +46,6 @@ td, th {
}
}
-.files-list__row {
- &--sortable {
- cursor: pointer;
- }
-}
-
.files-list__row-checkbox {
justify-content: center;
&::v-deep .checkbox-radio-switch {
@@ -122,12 +124,21 @@ td, th {
}
.files-list__row-size {
- justify-content: right;
+ // Right align text
+ justify-content: flex-end;
width: calc(var(--row-height) * 1.5);
// opacity varies with the size
color: var(--color-main-text);
+
+ // Icon is before text since size is right aligned
+ ::v-deep .files-list__column-sort-button {
+ padding: 0 16px 0 4px !important;
+ .button-vue__wrapper {
+ flex-direction: row;
+ }
+ }
}
-.files-list__row-column--custom {
+.files-list__row-column-custom {
width: calc(var(--row-height) * 2);
}
diff --git a/apps/files/src/services/Navigation.ts b/apps/files/src/services/Navigation.ts
index 40881b0e73c..4ca4588dff5 100644
--- a/apps/files/src/services/Navigation.ts
+++ b/apps/files/src/services/Navigation.ts
@@ -74,6 +74,12 @@ export interface Navigation {
/** This view has children and is expanded or not */
expanded?: boolean
+ /**
+ * Will be used as default if the user
+ * haven't customized their sorting column
+ * */
+ defaultSortKey?: string
+
/**
* This view is sticky a legacy view.
* Here until all the views are migrated to Vue.
@@ -195,6 +201,10 @@ const isValidNavigation = function(view: Navigation): boolean {
throw new Error('Navigation expanded must be a boolean')
}
+ if (view.defaultSortKey && typeof view.defaultSortKey !== 'string') {
+ throw new Error('Navigation defaultSortKey must be a string')
+ }
+
return true
}
diff --git a/apps/files/src/store/sorting.ts b/apps/files/src/store/sorting.ts
index b153301b76b..8e7c87b12b3 100644
--- a/apps/files/src/store/sorting.ts
+++ b/apps/files/src/store/sorting.ts
@@ -28,24 +28,34 @@ import axios from '@nextcloud/axios'
type direction = 'asc' | 'desc'
-const saveUserConfig = (key: string, direction: direction) => {
+interface SortingConfig {
+ mode: string
+ direction: direction
+}
+
+interface SortingStore {
+ [key: string]: SortingConfig
+}
+
+const saveUserConfig = (mode: string, direction: direction, view: string) => {
return axios.post(generateUrl('/apps/files/api/v1/sorting'), {
- mode: key,
- direction: direction as string,
+ mode,
+ direction,
+ view,
})
}
-const defaultFileSorting = loadState('files', 'defaultFileSorting', 'basename')
-const defaultFileSortingDirection = loadState('files', 'defaultFileSortingDirection', 'asc') as direction
+const filesSortingConfig = loadState('files', 'filesSortingConfig', {}) as SortingStore
+console.debug('filesSortingConfig', filesSortingConfig)
export const useSortingStore = defineStore('sorting', {
state: () => ({
- defaultFileSorting,
- defaultFileSortingDirection,
+ filesSortingConfig,
}),
getters: {
- isAscSorting: (state) => state.defaultFileSortingDirection === 'asc',
+ isAscSorting: (state) => (view: string = 'files') => state.filesSortingConfig[view]?.direction !== 'desc',
+ getSortingMode: (state) => (view: string = 'files') => state.filesSortingConfig[view]?.mode,
},
actions: {
@@ -54,19 +64,27 @@ export const useSortingStore = defineStore('sorting', {
* 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')
+ setSortingBy(key: string = 'basename', view: string = 'files') {
+ const config = this.filesSortingConfig[view] || {}
+ config.mode = key
+ config.direction = 'asc'
+
+ // Save new config
+ Vue.set(this.filesSortingConfig, view, config)
+ saveUserConfig(config.mode, config.direction, view)
},
/**
* Toggle the sorting direction
*/
- toggleSortingDirection() {
- const newDirection = this.defaultFileSortingDirection === 'asc' ? 'desc' : 'asc'
- Vue.set(this, 'defaultFileSortingDirection', newDirection)
- saveUserConfig(this.defaultFileSorting, newDirection)
+ toggleSortingDirection(view: string = 'files') {
+ const config = this.filesSortingConfig[view] || { 'direction': 'asc' }
+ const newDirection = config.direction === 'asc' ? 'desc' : 'asc'
+ config.direction = newDirection
+
+ // Save new config
+ Vue.set(this.filesSortingConfig, view, config)
+ saveUserConfig(config.mode, config.direction, view)
}
}
})
diff --git a/apps/files/src/views/FilesList.vue b/apps/files/src/views/FilesList.vue
index d09d3c619f2..03b0076a435 100644
--- a/apps/files/src/views/FilesList.vue
+++ b/apps/files/src/views/FilesList.vue
@@ -63,6 +63,7 @@