Skip to content
Closed
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
feat(files): refactor settings dialog with new form components
Reorganize settings into logical sections using NcFormBox,
NcFormBoxSwitch, NcRadioGroup, and NcFormBoxButton components.

Signed-off-by: nfebe <[email protected]>
  • Loading branch information
nfebe committed Nov 14, 2025
commit 222e7adaa16fc03ddec19125493c22bd84fa45f1
11 changes: 6 additions & 5 deletions apps/files/src/store/userconfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { ref, set } from 'vue'
const initialUserConfig = loadState<UserConfig>('files', 'config', {
crop_image_previews: true,
default_view: 'files',
folder_tree: false,
grid_view: false,
show_files_extensions: true,
show_hidden: false,
Expand All @@ -36,18 +37,19 @@ export const useUserConfigStore = defineStore('userconfig', () => {
* @param key The config key
* @param value The new value
*/
function onUpdate(key: string, value: boolean): void {
function onUpdate(key: string, value: boolean | string): void {
set(userConfig.value, key, value)
}

/**
* Update the user config local store AND on server side
*
* @param key The config key
* @param value The new value
* @param value The new value (boolean for switches, string for default_view)
*/
async function update(key: string, value: boolean): Promise<void> {
// only update if a user is logged in (not the case for public shares)
async function update(key: string, value: boolean | string): Promise<void> {
onUpdate(key, value)

if (getCurrentUser() !== null) {
await axios.put(generateUrl('/apps/files/api/v1/config/{key}', { key }), {
value,
Expand All @@ -56,7 +58,6 @@ export const useUserConfigStore = defineStore('userconfig', () => {
emit('files:config:updated', { key, value })
}

// Register the event listener
subscribe('files:config:updated', ({ key, value }) => onUpdate(key, value))

return {
Expand Down
1 change: 1 addition & 0 deletions apps/files/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export interface UserConfig {

crop_image_previews: boolean
default_view: 'files' | 'personal'
folder_tree: boolean
grid_view: boolean
show_files_extensions: boolean
show_hidden: boolean
Expand Down
217 changes: 108 additions & 109 deletions apps/files/src/views/FilesAppSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,75 +10,63 @@
@update:open="onClose">
<!-- Settings API-->
<NcAppSettingsSection id="settings" :name="t('files', 'General')">
<fieldset
<NcFormBox>
<NcFormBoxSwitch
v-model="userConfig.sort_favorites_first"
:label="t('files', 'Sort favorites first')"
data-cy-files-settings-setting="sort_favorites_first"
@update:modelValue="setConfig('sort_favorites_first', $event)" />
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@update:modelValue="setConfig('sort_favorites_first', $event)" />
@update:model-value="setConfig('sort_favorites_first', $event)" />

Even though I get warnings like 18:6 warning v-on event '@update:modelValue' must be hyphenated vue/v-on-event-hyphenation

The hyphenated version does not work.

<NcFormBoxSwitch
v-model="userConfig.sort_folders_first"
:label="t('files', 'Sort folders before files')"
data-cy-files-settings-setting="sort_folders_first"
@update:modelValue="setConfig('sort_folders_first', $event)" />
<NcFormBoxSwitch
v-model="userConfig.folder_tree"
:label="t('files', 'Folder tree')"
data-cy-files-settings-setting="folder_tree"
@update:modelValue="setConfig('folder_tree', $event)" />
</NcFormBox>

<NcRadioGroup
v-model="userConfig.default_view"
:label="t('files', 'Default view')"
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"
data-cy-files-settings-setting="default_view"
@update:modelValue="setConfig('default_view', $event)">
<NcRadioGroupButton
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"
:label="t('files', 'All files')" />
<NcRadioGroupButton
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)">
{{ t('files', 'Sort favorites first') }}
</NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch
data-cy-files-settings-setting="sort_folders_first"
:checked="userConfig.sort_folders_first"
@update:checked="setConfig('sort_folders_first', $event)">
{{ t('files', 'Sort folders before files') }}
</NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch
data-cy-files-settings-setting="folder_tree"
:checked="userConfig.folder_tree"
@update:checked="setConfig('folder_tree', $event)">
{{ t('files', 'Folder tree') }}
</NcCheckboxRadioSwitch>
:label="t('files', 'Personal files')" />
</NcRadioGroup>
</NcAppSettingsSection>

<!-- Appearance -->
<NcAppSettingsSection id="appearance" :name="t('files', 'Appearance')">
<NcCheckboxRadioSwitch
data-cy-files-settings-setting="show_hidden"
:checked="userConfig.show_hidden"
@update:checked="setConfig('show_hidden', $event)">
{{ t('files', 'Show hidden files') }}
</NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch
data-cy-files-settings-setting="show_mime_column"
:checked="userConfig.show_mime_column"
@update:checked="setConfig('show_mime_column', $event)">
{{ t('files', 'Show file type column') }}
</NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch
data-cy-files-settings-setting="show_files_extensions"
:checked="userConfig.show_files_extensions"
@update:checked="setConfig('show_files_extensions', $event)">
{{ t('files', 'Show file extensions') }}
</NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch
data-cy-files-settings-setting="crop_image_previews"
:checked="userConfig.crop_image_previews"
@update:checked="setConfig('crop_image_previews', $event)">
{{ t('files', 'Crop image previews') }}
</NcCheckboxRadioSwitch>
<NcFormBox>
<NcFormBoxSwitch
v-model="userConfig.show_hidden"
:label="t('files', 'Show hidden files')"
data-cy-files-settings-setting="show_hidden"
@update:modelValue="setConfig('show_hidden', $event)" />
<NcFormBoxSwitch
v-model="userConfig.show_mime_column"
:label="t('files', 'Show file type column')"
data-cy-files-settings-setting="show_mime_column"
@update:modelValue="setConfig('show_mime_column', $event)" />
<NcFormBoxSwitch
v-model="userConfig.show_files_extensions"
:label="t('files', 'Show file extensions')"
data-cy-files-settings-setting="show_files_extensions"
@update:modelValue="setConfig('show_files_extensions', $event)" />
<NcFormBoxSwitch
v-model="userConfig.crop_image_previews"
:label="t('files', 'Crop image previews')"
data-cy-files-settings-setting="crop_image_previews"
@update:modelValue="setConfig('crop_image_previews', $event)" />
</NcFormBox>
</NcAppSettingsSection>

<!-- Settings API-->
Expand Down Expand Up @@ -107,36 +95,39 @@
<Clipboard :size="20" />
</template>
</NcInputField>
<em>
<a
class="setting-link"
:href="webdavDocs"
target="_blank"
rel="noreferrer noopener">
{{ t('files', 'How to access files using WebDAV') }} ↗
</a>
</em>
<br>
<em v-if="isTwoFactorEnabled">
<a class="setting-link" :href="appPasswordUrl">
{{ t('files', 'Two-Factor Authentication is enabled for your account, and therefore you need to use an app password to connect an external WebDAV client.') }} ↗
</a>
</em>

<NcFormBoxButton
v-if="isTwoFactorEnabled"
:label="t('files', 'Create an app password')"
:description="t('files', 'Required for WebDAV because Two-Factor Authentication is enabled for this account')"
:href="appPasswordUrl"
target="_blank">
<template #icon>
<OpenInNew :size="20" />
</template>
</NcFormBoxButton>

<NcFormBoxButton
:label="t('files', 'How to access files using WebDAV')"
:href="webdavDocs"
target="_blank">
<template #icon>
<OpenInNew :size="20" />
</template>
</NcFormBoxButton>
</NcAppSettingsSection>

<NcAppSettingsSection id="warning" :name="t('files', 'Warnings')">
<NcCheckboxRadioSwitch
type="switch"
:checked="userConfig.show_dialog_file_extension"
@update:checked="setConfig('show_dialog_file_extension', $event)">
{{ t('files', 'Warn before changing a file extension') }}
</NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch
type="switch"
:checked="userConfig.show_dialog_deletion"
@update:checked="setConfig('show_dialog_deletion', $event)">
{{ t('files', 'Warn before deleting files') }}
</NcCheckboxRadioSwitch>
<NcFormBox>
<NcFormBoxSwitch
v-model="userConfig.show_dialog_file_extension"
:label="t('files', 'Warn before changing a file extension')"
@update:modelValue="setConfig('show_dialog_file_extension', $event)" />
<NcFormBoxSwitch
v-model="userConfig.show_dialog_deletion"
:label="t('files', 'Warn before deleting files')"
@update:modelValue="setConfig('show_dialog_deletion', $event)" />
</NcFormBox>
</NcAppSettingsSection>

<FilesAppSettingsShortcuts />
Expand All @@ -153,9 +144,14 @@ import { generateRemoteUrl, generateUrl } from '@nextcloud/router'
import { useHotKey } from '@nextcloud/vue/composables/useHotKey'
import NcAppSettingsDialog from '@nextcloud/vue/components/NcAppSettingsDialog'
import NcAppSettingsSection from '@nextcloud/vue/components/NcAppSettingsSection'
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
import NcFormBox from '@nextcloud/vue/components/NcFormBox'
import NcFormBoxButton from '@nextcloud/vue/components/NcFormBoxButton'
import NcFormBoxSwitch from '@nextcloud/vue/components/NcFormBoxSwitch'
import NcInputField from '@nextcloud/vue/components/NcInputField'
import NcRadioGroup from '@nextcloud/vue/components/NcRadioGroup'
import NcRadioGroupButton from '@nextcloud/vue/components/NcRadioGroupButton'
import Clipboard from 'vue-material-design-icons/ContentCopy.vue'
import OpenInNew from 'vue-material-design-icons/OpenInNew.vue'
import FilesAppSettingsEntry from '../components/FilesAppSettings/FilesAppSettingsEntry.vue'
import FilesAppSettingsShortcuts from '../components/FilesAppSettings/FilesAppSettingsShortcuts.vue'
import { useUserConfigStore } from '../store/userconfig.ts'
Expand All @@ -168,8 +164,13 @@ export default {
FilesAppSettingsShortcuts,
NcAppSettingsDialog,
NcAppSettingsSection,
NcCheckboxRadioSwitch,
NcFormBox,
NcFormBoxButton,
NcFormBoxSwitch,
NcInputField,
NcRadioGroup,
NcRadioGroupButton,
OpenInNew,
},

props: {
Expand All @@ -179,6 +180,8 @@ export default {
},
},

emits: ['close', 'update:open'],

setup() {
const userConfigStore = useUserConfigStore()
const isSystemtagsEnabled = getCapabilities()?.systemtags?.enabled === true
Expand Down Expand Up @@ -221,7 +224,6 @@ export default {
},

created() {
// ? opens the settings dialog on the keyboard shortcuts section
useHotKey('?', this.showKeyboardShortcuts, {
stop: true,
prevent: true,
Expand All @@ -243,8 +245,13 @@ export default {
this.$emit('close')
},

setConfig(key, value) {
this.userConfigStore.update(key, value)
async setConfig(key, value) {
try {
await this.userConfigStore.update(key, value)
showSuccess(t('files', 'Setting saved'))
} catch {
showError(t('files', 'Failed to save setting'))
}
},

async copyCloudId() {
Expand Down Expand Up @@ -278,23 +285,15 @@ export default {
</script>

<style lang="scss" scoped>
.files-settings {
&__default-view {
margin-bottom: 0.5rem;
}
}
.files-settings__default-view {
margin-block-start: calc(var(--default-grid-baseline) * 4);

.setting-link:hover {
text-decoration: underline;
}
:deep(.radio-group__button-group) {
font-size: 1.05em;

.shortcut-key {
width: 160px;
// some shortcuts are too long to fit in one line
white-space: normal;
span {
// force portion of a shortcut on a new line for nicer display
white-space: nowrap;
button {
padding-block: calc(var(--default-grid-baseline) * 2);
}
}
}

Expand Down
24 changes: 6 additions & 18 deletions apps/files/src/views/SettingsAdmin.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { loadState } from '@nextcloud/initial-state'
import { t } from '@nextcloud/l10n'
import { generateOcsUrl } from '@nextcloud/router'
import { ref } from 'vue'
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
import NcFormBoxSwitch from '@nextcloud/vue/components/NcFormBoxSwitch'
import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection'
import SettingsSanitizeFilenames from '../components/Settings/SettingsSanitizeFilenames.vue'
import logger from '../logger.ts'
Expand Down Expand Up @@ -55,26 +55,14 @@ async function toggleWindowsFilenameSupport(enabled: boolean) {
:doc-url="docUrl"
:name="t('files', 'Files compatibility')"
:description="description">
<NcCheckboxRadioSwitch
:model-value="hasWindowsSupport"
<NcFormBoxSwitch
v-model="hasWindowsSupport"
:label="t('files', 'Enforce Windows compatibility')"
:description="t('files', 'This will block filenames not valid on Windows systems, like using reserved names or special characters. But this will not enforce compatibility of case sensitivity.')"
:disabled="isRunningSanitization"
:loading="loading"
type="switch"
@update:model-value="toggleWindowsFilenameSupport">
{{ t('files', 'Enforce Windows compatibility') }}
</NcCheckboxRadioSwitch>
<p class="hint">
{{ t('files', 'This will block filenames not valid on Windows systems, like using reserved names or special characters. But this will not enforce compatibility of case sensitivity.') }}
</p>
@update:model-value="toggleWindowsFilenameSupport" />

<SettingsSanitizeFilenames v-if="hasWindowsSupport" />
</NcSettingsSection>
</template>

<style scoped>
.hint {
color: var(--color-text-maxcontrast);
margin-inline-start: var(--border-radius-element);
margin-block-end: 1em;
}
</style>
Loading