refactor(files): migrate to new NcForm* components for app settings

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
Ferdinand Thiessen 2025-11-12 19:02:41 +01:00
parent d1b00335d6
commit 82237a8bc9
No known key found for this signature in database
GPG key ID: 45FAE7268762B400
9 changed files with 255 additions and 315 deletions

View file

@ -0,0 +1,37 @@
<!--
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<script setup lang="ts">
import { t } from '@nextcloud/l10n'
import NcAppSettingsSection from '@nextcloud/vue/components/NcAppSettingsSection'
import NcFormBox from '@nextcloud/vue/components/NcFormBox'
import NcFormBoxSwitch from '@nextcloud/vue/components/NcFormBoxSwitch'
import { useUserConfigStore } from '../../store/userconfig.ts'
const store = useUserConfigStore()
</script>
<template>
<NcAppSettingsSection id="appearance" :name="t('files', 'Appearance')">
<NcFormBox>
<NcFormBoxSwitch
v-model="store.userConfig.show_hidden"
:label="t('files', 'Show hidden files')"
@update:modelValue="store.update('show_hidden', $event)" />
<NcFormBoxSwitch
v-model="store.userConfig.show_mime_column"
:label="t('files', 'Show file type column')"
@update:modelValue="store.update('show_mime_column', $event)" />
<NcFormBoxSwitch
v-model="store.userConfig.show_files_extensions"
:label="t('files', 'Show file extensions')"
@update:modelValue="store.update('show_files_extensions', $event)" />
<NcFormBoxSwitch
v-model="store.userConfig.crop_image_previews"
:label="t('files', 'Crop image previews')"
@update:modelValue="store.update('crop_image_previews', $event)" />
</NcFormBox>
</NcAppSettingsSection>
</template>

View file

@ -1,24 +0,0 @@
<!--
- SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
<div />
</template>
<script>
export default {
name: 'FilesAppSettingsEntry',
props: {
el: {
type: Function,
required: true,
},
},
mounted() {
this.$el.appendChild(this.el())
},
}
</script>

View file

@ -0,0 +1,44 @@
<!--
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<script setup lang="ts">
import { t } from '@nextcloud/l10n'
import NcAppSettingsSection from '@nextcloud/vue/components/NcAppSettingsSection'
import NcFormBox from '@nextcloud/vue/components/NcFormBox'
import NcFormBoxSwitch from '@nextcloud/vue/components/NcFormBoxSwitch'
import NcRadioGroup from '@nextcloud/vue/components/NcRadioGroup'
import NcRadioGroupButton from '@nextcloud/vue/components/NcRadioGroupButton'
import { useUserConfigStore } from '../../store/userconfig.ts'
const store = useUserConfigStore()
</script>
<template>
<NcAppSettingsSection
id="settings"
:name="t('files', 'General')">
<NcFormBox>
<NcFormBoxSwitch
v-model="store.userConfig.sort_favorites_first"
:label="t('files', 'Sort favorites first')"
@update:modelValue="store.update('sort_favorites_first', $event)" />
<NcFormBoxSwitch
v-model="store.userConfig.sort_folders_first"
:label="t('files', 'Sort folders before files')"
@update:modelValue="store.update('sort_folders_first', $event)" />
<NcFormBoxSwitch
v-model="store.userConfig.folder_tree"
:label="t('files', 'Enable folder tree view')"
@update:modelValue="store.update('folder_tree', $event)" />
</NcFormBox>
<NcRadioGroup
v-model="store.userConfig.default_view"
:label="t('files', 'Default view')"
@update:modelValue="store.update('default_view', $event)">
<NcRadioGroupButton :label="t('files', 'All files')" value="files" />
<NcRadioGroupButton :label="t('files', 'Personal files')" value="personal" />
</NcRadioGroup>
</NcAppSettingsSection>
</template>

View file

@ -0,0 +1,29 @@
<!--
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<script setup lang="ts">
import type Setting from '../../models/Setting.ts'
import { t } from '@nextcloud/l10n'
import NcAppSettingsSection from '@nextcloud/vue/components/NcAppSettingsSection'
import FilesAppSettingsLegacyApiEntry from './FilesAppSettingsLegacyApiEntry.vue'
const apiSettings = ((window.OCA?.Files?.Settings?.settings || []) as Setting[])
.sort((a, b) => {
if (a.order && b.order) {
return a.order - b.order
}
return a.name.localeCompare(b.name)
})
</script>
<template>
<NcAppSettingsSection
v-if="apiSettings.length !== 0"
id="api-settings"
:name="t('files', 'Additional settings')">
<FilesAppSettingsLegacyApiEntry v-for="setting in apiSettings" :key="setting.name" :setting="setting" />
</NcAppSettingsSection>
</template>

View file

@ -0,0 +1,26 @@
<!--
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<script setup lang="ts">
import type Setting from '../../models/Setting.ts'
import { onBeforeMount, onBeforeUnmount, onMounted, ref } from 'vue'
const props = defineProps<{
setting: Setting
}>()
const el = ref<HTMLElement>()
onBeforeMount(() => props.setting.open())
onBeforeUnmount(() => props.setting.close())
onMounted(() => {
el.value!.appendChild(props.setting.el())
})
</script>
<template>
<div ref="el" />
</template>

View file

@ -0,0 +1,29 @@
<!--
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<script lang="ts" setup>
import { t } from '@nextcloud/l10n'
import NcAppSettingsSection from '@nextcloud/vue/components/NcAppSettingsSection'
import NcFormBox from '@nextcloud/vue/components/NcFormBox'
import NcFormBoxSwitch from '@nextcloud/vue/components/NcFormBoxSwitch'
import { useUserConfigStore } from '../../store/userconfig.ts'
const store = useUserConfigStore()
</script>
<template>
<NcAppSettingsSection id="warning" :name="t('files', 'Warnings')">
<NcFormBox>
<NcFormBoxSwitch
v-model="store.userConfig.show_dialog_file_extension"
:label="t('files', 'Warn before changing a file extension')"
@update:modelValue="store.update('show_dialog_file_extension', $event)" />
<NcFormBoxSwitch
v-model="store.userConfig.show_dialog_deletion"
:label="t('files', 'Warn before deleting a file')"
@update:modelValue="store.update('show_dialog_deletion', $event)" />
</NcFormBox>
</NcAppSettingsSection>
</template>

View file

@ -0,0 +1,38 @@
<!--
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<script lang="ts" setup>
import { getCurrentUser } from '@nextcloud/auth'
import { loadState } from '@nextcloud/initial-state'
import { t } from '@nextcloud/l10n'
import { generateRemoteUrl, generateUrl } from '@nextcloud/router'
import NcAppSettingsSection from '@nextcloud/vue/components/NcAppSettingsSection'
import NcFormBox from '@nextcloud/vue/components/NcFormBox'
import NcFormBoxButton from '@nextcloud/vue/components/NcFormBoxButton'
import NcFormBoxCopyButton from '@nextcloud/vue/components/NcFormBoxCopyButton'
const webDavUrl = generateRemoteUrl('dav/files/' + encodeURIComponent(getCurrentUser()!.uid))
const webDavDocsUrl = 'https://docs.nextcloud.com/server/stable/go.php?to=user-webdav'
const appPasswordUrl = generateUrl('/settings/user/security#generate-app-token-section')
const isTwoFactorEnabled = loadState('files', 'isTwoFactorEnabled', false)
</script>
<template>
<NcAppSettingsSection id="webdav" name="WebDAV">
<NcFormBox>
<NcFormBoxCopyButton :label="t('files', 'WebDAV URL')" :value="webDavUrl" />
<NcFormBoxButton
v-if="isTwoFactorEnabled"
:label="t('files', 'Create an app password')"
:description="t('files', 'Required for WebDAV authentication because Two-Factor Authentication is enabled for this account.')"
:href="appPasswordUrl"
target="_blank" />
<NcFormBoxButton
:label="t('files', 'How to access files using WebDAV')"
:href="webDavDocsUrl"
target="_blank" />
</NcFormBox>
</NcAppSettingsSection>
</template>

View file

@ -2,303 +2,61 @@
- SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
<NcAppSettingsDialog
:open="open"
:show-navigation="true"
:name="t('files', 'Files settings')"
@update:open="onClose">
<!-- Settings API-->
<NcAppSettingsSection id="settings" :name="t('files', 'General')">
<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)">
{{ 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>
</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>
</NcAppSettingsSection>
<!-- Settings API-->
<NcAppSettingsSection
v-if="settings.length !== 0"
id="more-settings"
:name="t('files', 'Additional settings')">
<FilesAppSettingsEntry v-for="setting in settings" :key="setting.name" :el="setting.el" />
</NcAppSettingsSection>
<!-- Webdav URL-->
<NcAppSettingsSection id="webdav" :name="t('files', 'WebDAV')">
<NcInputField
id="webdav-url-input"
:label="t('files', 'WebDAV URL')"
:show-trailing-button="true"
:success="webdavUrlCopied"
:trailing-button-label="t('files', 'Copy')"
:value="webdavUrl"
class="webdav-url-input"
readonly="readonly"
type="url"
@focus="$event.target.select()"
@trailing-button-click="copyCloudId">
<template #trailing-button-icon>
<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>
</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>
</NcAppSettingsSection>
<FilesAppSettingsShortcuts />
</NcAppSettingsDialog>
</template>
<script>
import { getCurrentUser } from '@nextcloud/auth'
import { getCapabilities } from '@nextcloud/capabilities'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { loadState } from '@nextcloud/initial-state'
<script setup lang="ts">
import { t } from '@nextcloud/l10n'
import { generateRemoteUrl, generateUrl } from '@nextcloud/router'
import { useHotKey } from '@nextcloud/vue/composables/useHotKey'
import { nextTick } from 'vue'
import NcAppSettingsDialog from '@nextcloud/vue/components/NcAppSettingsDialog'
import NcAppSettingsSection from '@nextcloud/vue/components/NcAppSettingsSection'
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
import NcInputField from '@nextcloud/vue/components/NcInputField'
import Clipboard from 'vue-material-design-icons/ContentCopy.vue'
import FilesAppSettingsEntry from '../components/FilesAppSettings/FilesAppSettingsEntry.vue'
import FilesAppSettingsAppearance from '../components/FilesAppSettings/FilesAppSettingsAppearance.vue'
import FilesAppSettingsGeneral from '../components/FilesAppSettings/FilesAppSettingsGeneral.vue'
import FilesAppSettingsLegacyApi from '../components/FilesAppSettings/FilesAppSettingsLegacyApi.vue'
import FilesAppSettingsShortcuts from '../components/FilesAppSettings/FilesAppSettingsShortcuts.vue'
import { useUserConfigStore } from '../store/userconfig.ts'
import FilesAppSettingsWarnings from '../components/FilesAppSettings/FilesAppSettingsWarnings.vue'
import FilesAppSettingsWebDav from '../components/FilesAppSettings/FilesAppSettingsWebDav.vue'
export default {
name: 'FilesAppSettings',
components: {
Clipboard,
FilesAppSettingsEntry,
FilesAppSettingsShortcuts,
NcAppSettingsDialog,
NcAppSettingsSection,
NcCheckboxRadioSwitch,
NcInputField,
},
defineProps<{
open: boolean
}>()
props: {
open: {
type: Boolean,
default: false,
},
},
const emit = defineEmits<{
(e: 'close'): void
(e: 'update:open', open: boolean): void
}>()
setup() {
const userConfigStore = useUserConfigStore()
const isSystemtagsEnabled = getCapabilities()?.systemtags?.enabled === true
return {
isSystemtagsEnabled,
userConfigStore,
t,
}
},
// ? opens the settings dialog on the keyboard shortcuts section
useHotKey('?', showKeyboardShortcuts, {
stop: true,
prevent: true,
})
data() {
return {
// Settings API
settings: window.OCA?.Files?.Settings?.settings || [],
/**
* Opens the settings dialog and scrolls to the keyboard shortcuts section
*/
async function showKeyboardShortcuts() {
emit('update:open', true)
// Webdav infos
webdavUrl: generateRemoteUrl('dav/files/' + encodeURIComponent(getCurrentUser()?.uid)),
webdavDocs: 'https://docs.nextcloud.com/server/stable/go.php?to=user-webdav',
appPasswordUrl: generateUrl('/settings/user/security#generate-app-token-section'),
webdavUrlCopied: false,
enableGridView: (loadState('core', 'config', [])['enable_non-accessible_features'] ?? true),
isTwoFactorEnabled: (loadState('files', 'isTwoFactorEnabled', false)),
}
},
computed: {
userConfig() {
return this.userConfigStore.userConfig
},
sortedSettings() {
// Sort settings by name
return [...this.settings].sort((a, b) => {
if (a.order && b.order) {
return a.order - b.order
}
return a.name.localeCompare(b.name)
})
},
},
created() {
// ? opens the settings dialog on the keyboard shortcuts section
useHotKey('?', this.showKeyboardShortcuts, {
stop: true,
prevent: true,
})
},
beforeMount() {
// Update the settings API entries state
this.settings.forEach((setting) => setting.open())
},
beforeUnmount() {
// Update the settings API entries state
this.settings.forEach((setting) => setting.close())
},
methods: {
onClose() {
this.$emit('close')
},
setConfig(key, value) {
this.userConfigStore.update(key, value)
},
async copyCloudId() {
document.querySelector('input#webdav-url-input').select()
if (!navigator.clipboard) {
// Clipboard API not available
showError(t('files', 'Clipboard is not available'))
return
}
await navigator.clipboard.writeText(this.webdavUrl)
this.webdavUrlCopied = true
showSuccess(t('files', 'WebDAV URL copied'))
setTimeout(() => {
this.webdavUrlCopied = false
}, 5000)
},
async showKeyboardShortcuts() {
this.$emit('update:open', true)
await this.$nextTick()
document.getElementById('settings-section_shortcuts').scrollIntoView({
behavior: 'smooth',
inline: 'nearest',
})
},
},
await nextTick()
document.getElementById('settings-section_keyboard-shortcuts')!.scrollIntoView({
behavior: 'smooth',
inline: 'nearest',
})
}
</script>
<style lang="scss" scoped>
.files-settings {
&__default-view {
margin-bottom: 0.5rem;
}
}
.setting-link:hover {
text-decoration: underline;
}
.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;
}
}
.webdav-url-input {
margin-block-end: 0.5rem;
}
</style>
<template>
<NcAppSettingsDialog
:legacy="false"
:name="t('files', 'Files settings')"
no-version
:open="open"
show-navigation
@update:open="emit('close')">
<FilesAppSettingsGeneral />
<FilesAppSettingsAppearance />
<FilesAppSettingsLegacyApi />
<FilesAppSettingsWarnings />
<FilesAppSettingsWebDav />
<FilesAppSettingsShortcuts />
</NcAppSettingsDialog>
</template>

View file

@ -149,10 +149,13 @@ function showHiddenFiles() {
// Open the files settings
cy.get('[data-cy-files-navigation-settings-button] a').click({ force: true })
// Toggle the hidden files setting
cy.get('[data-cy-files-settings-setting="show_hidden"]').within(() => {
cy.get('input').should('not.be.checked')
cy.get('input').check({ force: true })
})
cy.findByRole('switch', { name: /show hidden files/i })
.as('hiddenFiles')
.scrollIntoView()
cy.get('@hiddenFiles')
.should('not.be.checked')
.check({ force: true })
// Close the dialog
cy.get('[data-cy-files-navigation-settings] button[aria-label="Close"]').click()
}