Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 23 additions & 17 deletions apps/files/lib/Service/UserConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,47 +20,53 @@ class UserConfig {
'allowed' => [true, false],
],
[
// Whether to show the "confirm file extension change" warning
'key' => 'show_dialog_file_extension',
// The view to start the files app in
'key' => 'default_view',
'default' => 'files',
'allowed' => ['files', 'personal'],
],
[
// Whether to show the folder tree
'key' => 'folder_tree',
'default' => true,
'allowed' => [true, false],
],
[
// Whether to show the hidden files or not in the files list
'key' => 'show_hidden',
// Whether to show the files list in grid view or not
'key' => 'grid_view',
'default' => false,
'allowed' => [true, false],
],
[
// Whether to sort favorites first in the list or not
'key' => 'sort_favorites_first',
// Whether to show the "confirm file extension change" warning
'key' => 'show_dialog_file_extension',
'default' => true,
'allowed' => [true, false],
],
[
// Whether to sort folders before files in the list or not
'key' => 'sort_folders_first',
'default' => true,
// Whether to show the hidden files or not in the files list
'key' => 'show_hidden',
'default' => false,
'allowed' => [true, false],
],
[
// Whether to show the files list in grid view or not
'key' => 'grid_view',
// Whether to show the mime column or not
'key' => 'show_mime_column',
'default' => false,
'allowed' => [true, false],
],
[
// Whether to show the folder tree
'key' => 'folder_tree',
// Whether to sort favorites first in the list or not
'key' => 'sort_favorites_first',
'default' => true,
'allowed' => [true, false],
],
[
// Whether to show the mime column or not
'key' => 'show_mime_column',
'default' => false,
// Whether to sort folders before files in the list or not
'key' => 'sort_folders_first',
'default' => true,
'allowed' => [true, false],
]
],
];
protected ?IUser $user = null;

Expand Down
2 changes: 1 addition & 1 deletion apps/files/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { registerTemplateEntries } from './newMenu/newFromTemplate.ts'

import { registerFavoritesView } from './views/favorites.ts'
import registerRecentView from './views/recent'
import registerPersonalFilesView from './views/personal-files'
import { registerPersonalFilesView } from './views/personal-files'
import { registerFilesView } from './views/files'
import { registerFolderTreeView } from './views/folderTree.ts'
import { registerSearchView } from './views/search.ts'
Expand Down
9 changes: 5 additions & 4 deletions apps/files/src/router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import queryString from 'query-string'
import Router, { isNavigationFailure, NavigationFailureType } from 'vue-router'
import Vue from 'vue'

import { useFilesStore } from '../store/files'
import { usePathsStore } from '../store/paths'
import logger from '../logger'
import { useFilesStore } from '../store/files.ts'
import { usePathsStore } from '../store/paths.ts'
import { defaultView } from '../utils/filesViews.ts'
import logger from '../logger.ts'

Vue.use(Router)

Expand Down Expand Up @@ -57,7 +58,7 @@ const router = new Router({
{
path: '/',
// Pretending we're using the default view
redirect: { name: 'filelist', params: { view: 'files' } },
redirect: { name: 'filelist', params: { view: defaultView() } },
},
{
path: '/:view/:fileid(\\d+)?',
Expand Down
7 changes: 4 additions & 3 deletions apps/files/src/store/userconfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ import { ref, set } from 'vue'
import axios from '@nextcloud/axios'

const initialUserConfig = loadState<UserConfig>('files', 'config', {
show_hidden: false,
crop_image_previews: true,
sort_favorites_first: true,
sort_folders_first: true,
default_view: 'files',
grid_view: false,
show_hidden: false,
show_mime_column: true,
sort_favorites_first: true,
sort_folders_first: true,

show_dialog_file_extension: true,
})
Expand Down
10 changes: 6 additions & 4 deletions apps/files/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,18 @@ export interface PathOptions {

// User config store
export interface UserConfig {
[key: string]: boolean|undefined
[key: string]: boolean | string | undefined

crop_image_previews: boolean
default_view: 'files' | 'personal'
grid_view: boolean
show_dialog_file_extension: boolean,
show_hidden: boolean
crop_image_previews: boolean
show_mime_column: boolean
sort_favorites_first: boolean
sort_folders_first: boolean
grid_view: boolean
show_mime_column: boolean
}

export interface UserConfigStore {
userConfig: UserConfig
}
Expand Down
75 changes: 75 additions & 0 deletions apps/files/src/utils/filesViews.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { beforeEach, describe, expect, test } from 'vitest'
import { defaultView, hasPersonalFilesView } from './filesViews.ts'

describe('hasPersonalFilesView', () => {
beforeEach(() => removeInitialState())

test('enabled if user has unlimited quota', () => {
mockInitialState('files', 'storageStats', { quota: -1 })
expect(hasPersonalFilesView()).toBe(true)
})

test('enabled if user has limited quota', () => {
mockInitialState('files', 'storageStats', { quota: 1234 })
expect(hasPersonalFilesView()).toBe(true)
})

test('disabled if user has no quota', () => {
mockInitialState('files', 'storageStats', { quota: 0 })
expect(hasPersonalFilesView()).toBe(false)
})
})

describe('defaultView', () => {
beforeEach(() => {
document.querySelectorAll('input[type="hidden"]').forEach((el) => {
el.remove()
})
})

test('Returns files view if set', () => {
mockInitialState('files', 'config', { default_view: 'files' })
expect(defaultView()).toBe('files')
})

test('Returns personal view if set and enabled', () => {
mockInitialState('files', 'config', { default_view: 'personal' })
mockInitialState('files', 'storageStats', { quota: -1 })
expect(defaultView()).toBe('personal')
})

test('Falls back to files if personal view is disabled', () => {
mockInitialState('files', 'config', { default_view: 'personal' })
mockInitialState('files', 'storageStats', { quota: 0 })
expect(defaultView()).toBe('files')
})
})

/**
* Remove the mocked initial state
*/
function removeInitialState(): void {
document.querySelectorAll('input[type="hidden"]').forEach((el) => {
el.remove()
})
}

/**
* Helper to mock an initial state value
* @param app - The app
* @param key - The key
* @param value - The value
*/
function mockInitialState(app: string, key: string, value: unknown): void {
const el = document.createElement('input')
el.value = btoa(JSON.stringify(value))
el.id = `initial-state-${app}-${key}`
el.type = 'hidden'

document.head.appendChild(el)
}
30 changes: 30 additions & 0 deletions apps/files/src/utils/filesViews.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type { UserConfig } from '../types.ts'

import { loadState } from '@nextcloud/initial-state'

/**
* Check whether the personal files view can be shown
*/
export function hasPersonalFilesView(): boolean {
const storageStats = loadState('files', 'storageStats', { quota: -1 })
// Don't show this view if the user has no storage quota
return storageStats.quota !== 0
}

/**
* Get the default files view
*/
export function defaultView() {
const { default_view: defaultView } = loadState<Partial<UserConfig>>('files', 'config', { default_view: 'files' })

// the default view - only use the personal one if it is enabled
if (defaultView !== 'personal' || hasPersonalFilesView()) {
return defaultView
}
return 'files'
}
27 changes: 27 additions & 0 deletions apps/files/src/views/Settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,27 @@
@update:open="onClose">
<!-- Settings API-->
<NcAppSettingsSection id="settings" :name="t('files', 'Files settings')">
<fieldset class="files-settings__default-view"
data-cy-files-settings-setting="default_view">
<legend>
{{ t('files', 'Default view') }}
</legend>
<NcCheckboxRadioSwitch :model-value="userConfig.default_view"
name="default_view"
type="radio"
value="files"
@update:model-value="setConfig('default_view', $event)">
{{ t('files', 'All files') }}
</NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch :model-value="userConfig.default_view"
name="default_view"
type="radio"
value="personal"
@update:model-value="setConfig('default_view', $event)">
{{ t('files', 'Personal files') }}
</NcCheckboxRadioSwitch>
</fieldset>

<NcCheckboxRadioSwitch data-cy-files-settings-setting="sort_favorites_first"
:checked="userConfig.sort_favorites_first"
@update:checked="setConfig('sort_favorites_first', $event)">
Expand Down Expand Up @@ -380,6 +401,12 @@ export default {
</script>

<style lang="scss" scoped>
.files-settings {
&__default-view {
margin-bottom: 0.5rem;
}
}

.setting-link:hover {
text-decoration: underline;
}
Expand Down
11 changes: 7 additions & 4 deletions apps/files/src/views/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { translate as t } from '@nextcloud/l10n'
import FolderSvg from '@mdi/svg/svg/folder.svg?raw'

import { getContents } from '../services/Files'
import { View, getNavigation } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
import { getContents } from '../services/Files.ts'
import { defaultView } from '../utils/filesViews.ts'

import FolderSvg from '@mdi/svg/svg/folder.svg?raw'

export const VIEW_ID = 'files'

Expand All @@ -21,7 +23,8 @@ export function registerFilesView() {
caption: t('files', 'List of your files and folders.'),

icon: FolderSvg,
order: 0,
// if this is the default view we set it at the top of the list - otherwise below it
order: defaultView() === VIEW_ID ? 0 : 5,

getContents,
}))
Expand Down
23 changes: 14 additions & 9 deletions apps/files/src/views/personal-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,36 @@
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { translate as t } from '@nextcloud/l10n'

import { t } from '@nextcloud/l10n'
import { View, getNavigation } from '@nextcloud/files'
import { getContents } from '../services/PersonalFiles.ts'
import { defaultView, hasPersonalFilesView } from '../utils/filesViews.ts'

import { getContents } from '../services/PersonalFiles'
import AccountIcon from '@mdi/svg/svg/account.svg?raw'
import { loadState } from '@nextcloud/initial-state'

export default () => {
// Don't show this view if the user has no storage quota
const storageStats = loadState('files', 'storageStats', { quota: -1 })
if (storageStats.quota === 0) {
export const VIEW_ID = 'personal'

/**
* Register the personal files view if allowed
*/
export function registerPersonalFilesView(): void {
if (!hasPersonalFilesView()) {
return
}

const Navigation = getNavigation()
Navigation.register(new View({
id: 'personal',
id: VIEW_ID,
name: t('files', 'Personal files'),
caption: t('files', 'List of your files and folders that are not shared.'),

emptyTitle: t('files', 'No personal files found'),
emptyCaption: t('files', 'Files that are not shared will show up here.'),

icon: AccountIcon,
order: 5,
// if this is the default view we set it at the top of the list - otherwise default position of fifth
order: defaultView() === VIEW_ID ? 0 : 5,

getContents,
}))
Expand Down
Loading
Loading