Signed-off-by: Louis Chemineau <louis@chmn.me>pull/54570/head
parent
9e9f3b9d16
commit
ed02d0df05
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 390 B |
@ -0,0 +1,38 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
namespace OCA\Settings\Sections\Admin; |
||||
|
||||
use OCP\IL10N; |
||||
use OCP\IURLGenerator; |
||||
use OCP\Settings\IIconSection; |
||||
|
||||
class Presets implements IIconSection { |
||||
|
||||
public function __construct( |
||||
private IL10N $l, |
||||
private IURLGenerator $urlGenerator, |
||||
) { |
||||
} |
||||
|
||||
public function getIcon(): string { |
||||
return $this->urlGenerator->imagePath('settings', 'library_add_check.svg'); |
||||
} |
||||
|
||||
public function getID(): string { |
||||
return 'presets'; |
||||
} |
||||
|
||||
public function getName(): string { |
||||
return $this->l->t('Settings presets'); |
||||
} |
||||
|
||||
public function getPriority(): int { |
||||
return 0; |
||||
} |
||||
} |
@ -0,0 +1,54 @@ |
||||
<?php |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
namespace OCA\Settings\Settings\Admin; |
||||
|
||||
use OC\Config\PresetManager; |
||||
use OCP\AppFramework\Http\TemplateResponse; |
||||
use OCP\AppFramework\Services\IInitialState; |
||||
use OCP\IConfig; |
||||
use OCP\IL10N; |
||||
use OCP\ServerVersion; |
||||
use OCP\Settings\ISettings; |
||||
|
||||
class Presets implements ISettings { |
||||
public function __construct( |
||||
private ServerVersion $serverVersion, |
||||
private IConfig $config, |
||||
private IL10N $l, |
||||
private readonly PresetManager $presetManager, |
||||
private IInitialState $initialState, |
||||
) { |
||||
} |
||||
|
||||
public function getForm() { |
||||
$presets = $this->presetManager->retrieveLexiconPreset(); |
||||
$selectedPreset = $this->presetManager->getLexiconPreset(); |
||||
$presetsApps = $this->presetManager->retrieveLexiconPresetApps(); |
||||
|
||||
$this->initialState->provideInitialState('settings-selected-preset', $selectedPreset->name); |
||||
$this->initialState->provideInitialState('settings-presets', $presets); |
||||
$this->initialState->provideInitialState('settings-presets-apps', $presetsApps); |
||||
|
||||
return new TemplateResponse('settings', 'settings/admin/presets', [], ''); |
||||
} |
||||
|
||||
public function getSection() { |
||||
return 'presets'; |
||||
} |
||||
|
||||
public function getPriority() { |
||||
return 0; |
||||
} |
||||
|
||||
public function getName(): ?string { |
||||
return $this->l->t('Settings presets'); |
||||
} |
||||
|
||||
public function getAuthorizedAppConfig(): array { |
||||
return []; |
||||
} |
||||
} |
@ -0,0 +1,160 @@ |
||||
<!-- |
||||
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
- SPDX-License-Identifier: AGPL-3.0-or-later |
||||
--> |
||||
|
||||
<script setup lang="ts"> |
||||
import { computed } from 'vue' |
||||
|
||||
import { t } from '@nextcloud/l10n' |
||||
import { loadState } from '@nextcloud/initial-state' |
||||
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch' |
||||
|
||||
import type { PresetAppConfig, PresetAppConfigs, PresetAppsStates, PresetIds } from './models.ts' |
||||
|
||||
const applicationsStates = loadState('settings', 'settings-presets-apps', {}) as PresetAppsStates |
||||
|
||||
const props = defineProps({ |
||||
presets: { |
||||
type: Object as () => PresetAppConfigs, |
||||
required: true, |
||||
}, |
||||
selectedPreset: { |
||||
type: String as () => PresetIds, |
||||
default: 'NONE', |
||||
}, |
||||
}) |
||||
|
||||
const appsConfigPresets = Object.entries(props.presets) |
||||
.map(([appId, presets]) => [appId, presets.filter(configPreset => configPreset.config === 'app')]) |
||||
.filter(([, presets]) => presets.length > 0) as [string, PresetAppConfig[]][] |
||||
const userConfigPresets = Object.entries(props.presets) |
||||
.map(([appId, presets]) => [appId, presets.filter(configPreset => configPreset.config === 'user')]) |
||||
.filter(([, presets]) => presets.length > 0) as [string, PresetAppConfig[]][] |
||||
|
||||
const hasApplicationsPreset = computed(() => applicationsStates[props.selectedPreset].enabled.length > 0 || applicationsStates[props.selectedPreset].disabled.length > 0) |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="presets"> |
||||
<h3 class="presets__title"> |
||||
{{ t('settings', 'Default config values') }} |
||||
</h3> |
||||
|
||||
<div v-if="appsConfigPresets.length > 0" class="presets__config-list"> |
||||
<h4 class="presets__config-list__subtitle"> |
||||
{{ t('settings', 'Applications config') }} |
||||
</h4> |
||||
<template v-for="[appId, appConfigPresets] in appsConfigPresets"> |
||||
<div v-for="configPreset in appConfigPresets" |
||||
:key="appId + '-' + configPreset.entry.key" |
||||
class="presets__config-list__item"> |
||||
<span> |
||||
<div>{{ configPreset.entry.definition }}</div> |
||||
<code class="presets__config-list__item__key">{{ configPreset.entry.key }}</code> |
||||
</span> |
||||
<span> |
||||
<NcCheckboxRadioSwitch v-if="configPreset.entry.type === 'BOOL'" |
||||
:model-value="configPreset.defaults[selectedPreset] === '1'" |
||||
:disabled="true" /> |
||||
<code v-else>{{ configPreset.defaults[selectedPreset] }}</code> |
||||
</span> |
||||
</div> |
||||
</template> |
||||
</div> |
||||
|
||||
<div v-if="userConfigPresets.length > 0" class="presets__config-list"> |
||||
<h4 class="presets__config-list__subtitle"> |
||||
{{ t('settings', 'User config') }} |
||||
</h4> |
||||
<template v-for="[appId, userPresets] in userConfigPresets"> |
||||
<div v-for="configPreset in userPresets" |
||||
:key="appId + '-' + configPreset.entry.key" |
||||
class="presets__config-list__item"> |
||||
<span> |
||||
<div>{{ configPreset.entry.definition }}</div> |
||||
<code class="presets__config-list__item__key">{{ configPreset.entry.key }}</code> |
||||
</span> |
||||
<span> |
||||
<NcCheckboxRadioSwitch v-if="configPreset.entry.type === 'BOOL'" |
||||
:model-value="configPreset.defaults[selectedPreset] === '1'" |
||||
:disabled="true" /> |
||||
<code v-else>{{ configPreset.defaults[selectedPreset] }}</code> |
||||
</span> |
||||
</div> |
||||
</template> |
||||
</div> |
||||
|
||||
<template v-if="hasApplicationsPreset"> |
||||
<h3 class="presets__title"> |
||||
{{ t('settings', 'Bundled applications') }} |
||||
</h3> |
||||
|
||||
<div class="presets__app-list"> |
||||
<div class="presets__app-list__enabled"> |
||||
<h4 class="presets__app-list__title"> |
||||
{{ t('settings', 'Enabled applications') }} |
||||
</h4> |
||||
<ul> |
||||
<li v-for="applicationId in applicationsStates[selectedPreset].enabled" |
||||
:key="applicationId"> |
||||
{{ applicationId }} |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
<div class="presets__app-list__disabled"> |
||||
<h4 class="presets__app-list__title"> |
||||
{{ t('settings', 'Disabled applications') }} |
||||
</h4> |
||||
<ul> |
||||
<li v-for="applicationId in applicationsStates[selectedPreset].disabled" |
||||
:key="applicationId"> |
||||
{{ applicationId }} |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
</div> |
||||
</template> |
||||
|
||||
<style lang="scss" scoped> |
||||
.presets { |
||||
margin-top: 16px; |
||||
|
||||
&__title { |
||||
font-size: 16px; |
||||
margin-bottom: 0; |
||||
} |
||||
|
||||
&__config-list { |
||||
margin-top: 8px; |
||||
width: 55%; |
||||
|
||||
&__subtitle { |
||||
font-size: 14px; |
||||
} |
||||
|
||||
&__item { |
||||
display: flex; |
||||
justify-content: space-between; |
||||
align-items: center; |
||||
padding: 2px 0; |
||||
|
||||
&__key { |
||||
font-size: 12px; |
||||
color: var(--color-text-maxcontrast); |
||||
} |
||||
} |
||||
} |
||||
|
||||
&__app-list { |
||||
display: flex; |
||||
gap: 32px; |
||||
|
||||
&__title { |
||||
font-size: 14px; |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,118 @@ |
||||
<!-- |
||||
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
- SPDX-License-Identifier: AGPL-3.0-or-later |
||||
--> |
||||
|
||||
<script setup lang="ts"> |
||||
import Domain from 'vue-material-design-icons/Domain.vue' |
||||
import CloudCircleOutline from 'vue-material-design-icons/CloudCircleOutline.vue' |
||||
import SchoolOutline from 'vue-material-design-icons/SchoolOutline.vue' |
||||
import Crowd from 'vue-material-design-icons/Crowd.vue' |
||||
import AccountGroupOutline from 'vue-material-design-icons/AccountGroupOutline.vue' |
||||
import AccountOutline from 'vue-material-design-icons/AccountOutline.vue' |
||||
import MinusCircleOutline from 'vue-material-design-icons/MinusCircleOutline.vue' |
||||
|
||||
import { t } from '@nextcloud/l10n' |
||||
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch' |
||||
|
||||
import { type PresetAppConfigs, type PresetIds } from './models.ts' |
||||
|
||||
const PresetNames = { |
||||
LARGE: t('settings', 'Large organization'), |
||||
MEDIUM: t('settings', 'Big organization'), |
||||
SMALL: t('settings', 'Small organization'), |
||||
SHARED: t('settings', 'Hosting company'), |
||||
UNIVERSITY: t('settings', 'University'), |
||||
SCHOOL: t('settings', 'School'), |
||||
CLUB: t('settings', 'Club or association'), |
||||
FAMILY: t('settings', 'Family'), |
||||
PRIVATE: t('settings', 'Personal use'), |
||||
NONE: t('settings', 'Default'), |
||||
} |
||||
|
||||
const PresetsIcons = { |
||||
LARGE: Domain, |
||||
MEDIUM: Domain, |
||||
SMALL: Domain, |
||||
SHARED: CloudCircleOutline, |
||||
UNIVERSITY: SchoolOutline, |
||||
SCHOOL: SchoolOutline, |
||||
CLUB: AccountGroupOutline, |
||||
FAMILY: Crowd, |
||||
PRIVATE: AccountOutline, |
||||
NONE: MinusCircleOutline, |
||||
} |
||||
|
||||
defineProps({ |
||||
presets: { |
||||
type: Object as () => PresetAppConfigs, |
||||
required: true, |
||||
}, |
||||
value: { |
||||
type: String as () => PresetIds, |
||||
default: '', |
||||
}, |
||||
}) |
||||
|
||||
const emit = defineEmits<{ |
||||
(e: 'input', option: string): void |
||||
}>() |
||||
</script> |
||||
|
||||
<template> |
||||
<form class="presets-form"> |
||||
<label v-for="(presetName, presetId) in PresetNames" |
||||
:key="presetId" |
||||
class="presets-form__option"> |
||||
|
||||
<components :is="PresetsIcons[presetId]" :size="32" /> |
||||
|
||||
<NcCheckboxRadioSwitch type="radio" |
||||
:model-value="value" |
||||
:value="presetId" |
||||
name="preset" |
||||
@update:modelValue="emit('input', presetId)" /> |
||||
|
||||
<span class="presets-form__option__name">{{ presetName }}</span> |
||||
</label> |
||||
</form> |
||||
</template> |
||||
|
||||
<style lang="scss" scoped> |
||||
.presets-form { |
||||
display: flex; |
||||
flex-wrap: wrap; |
||||
gap: 24px; |
||||
margin-top: 32px; |
||||
|
||||
&__option { |
||||
display: flex; |
||||
flex-wrap: wrap; |
||||
justify-content: space-between; |
||||
width: 250px; |
||||
min-height: 100px; |
||||
padding: 16px; |
||||
border-radius: var(--border-radius-large); |
||||
background-color: var(--color-background-dark); |
||||
font-size: 20px; |
||||
|
||||
&:hover { |
||||
background-color: var(--color-background-darker); |
||||
} |
||||
|
||||
&:has(input[type=radio]:checked) { |
||||
border: 2px solid var(--color-main-text); |
||||
padding: 14px; |
||||
} |
||||
|
||||
&__name { |
||||
flex-basis: 250px; |
||||
margin-top: 8px; |
||||
} |
||||
|
||||
&__name, .material-design-icon { |
||||
cursor: pointer; |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,31 @@ |
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
type PresetAppConfigEntry = { |
||||
key: string |
||||
type: 'ARRAY' | 'BOOL' | 'FLOAT' | 'INT' | 'MIXED' | 'STRING' |
||||
definition: string |
||||
note: string |
||||
lazy: boolean |
||||
deprecated: boolean |
||||
} |
||||
|
||||
export type PresetIds = 'LARGE' | 'MEDIUM' | 'SMALL' | 'SHARED' | 'UNIVERSITY' | 'SCHOOL' | 'CLUB' | 'FAMILY' | 'PRIVATE' | 'NONE' |
||||
|
||||
export type PresetAppConfig = { |
||||
config: 'app' | 'user' |
||||
entry: PresetAppConfigEntry |
||||
defaults: Record<PresetIds, string> |
||||
value?: unknown |
||||
} |
||||
|
||||
export type PresetAppConfigs = Record<string, PresetAppConfig[]> |
||||
|
||||
type PresetAppsState = { |
||||
enabled: string[] |
||||
disabled: string[] |
||||
} |
||||
|
||||
export type PresetAppsStates = Record<PresetIds, PresetAppsState> |
@ -0,0 +1,19 @@ |
||||
/** |
||||
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
import Vue from 'vue' |
||||
|
||||
import SettingsPresets from './views/SettingsPresets.vue' |
||||
import { getCSPNonce } from '@nextcloud/auth' |
||||
|
||||
// CSP config for webpack dynamic chunk loading
|
||||
// eslint-disable-next-line camelcase
|
||||
__webpack_nonce__ = getCSPNonce() |
||||
|
||||
export default new Vue({ |
||||
render: h => h(SettingsPresets), |
||||
el: '#settings-presets', |
||||
name: 'SettingsPresets', |
||||
}) |
@ -0,0 +1,68 @@ |
||||
<!-- |
||||
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
- SPDX-License-Identifier: AGPL-3.0-or-later |
||||
--> |
||||
|
||||
<script setup lang="ts"> |
||||
import { ref } from 'vue' |
||||
import { t } from '@nextcloud/l10n' |
||||
import { generateUrl } from '@nextcloud/router' |
||||
import axios from '@nextcloud/axios' |
||||
import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection' |
||||
import NcButton from '@nextcloud/vue/components/NcButton' |
||||
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon' |
||||
import { loadState } from '@nextcloud/initial-state' |
||||
import { showError } from '@nextcloud/dialogs' |
||||
|
||||
import PresetsSelectionForm from '../components/SettingsPresets/PresetsSelectionForm.vue' |
||||
import PresetVisualisation from '../components/SettingsPresets/PresetVisualisation.vue' |
||||
import type { PresetAppConfigs, PresetIds } from '../components/SettingsPresets/models' |
||||
import logger from '../logger' |
||||
|
||||
const presets = loadState('settings', 'settings-presets', {}) as PresetAppConfigs |
||||
const currentPreset = ref(loadState('settings', 'settings-selected-preset', 'NONE') as PresetIds) |
||||
const selectedPreset = ref(currentPreset.value) |
||||
const savingPreset = ref(false) |
||||
|
||||
async function saveSelectedPreset() { |
||||
try { |
||||
savingPreset.value = true |
||||
await axios.post(generateUrl('/settings/preset/current'), { |
||||
presetName: selectedPreset.value, |
||||
}) |
||||
currentPreset.value = selectedPreset.value |
||||
} catch (error) { |
||||
showError(t('settings', 'Failed to save selected preset.')) |
||||
logger.error('Error saving selected preset:', { error }) |
||||
selectedPreset.value = currentPreset.value |
||||
} finally { |
||||
savingPreset.value = false |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<NcSettingsSection :name="t('settings', 'Settings presets')" |
||||
:description="t('settings', 'Select a configuration preset for easy setup.')"> |
||||
<PresetsSelectionForm v-model="selectedPreset" :presets="presets" /> |
||||
|
||||
<PresetVisualisation :presets="presets" :selected-preset="selectedPreset" /> |
||||
|
||||
<NcButton class="save-button" |
||||
variant="primary" |
||||
:disabled="selectedPreset === currentPreset || savingPreset" |
||||
@click="saveSelectedPreset()"> |
||||
{{ t('settings', 'Apply') }} |
||||
|
||||
<template v-if="savingPreset" #icon> |
||||
<NcLoadingIcon /> |
||||
</template> |
||||
</NcButton> |
||||
</NcSettingsSection> |
||||
</template> |
||||
|
||||
<style lang="scss" scoped> |
||||
.save-button { |
||||
margin-top: 16px; |
||||
} |
||||
</style> |
@ -0,0 +1,12 @@ |
||||
<?php |
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
\OCP\Util::addScript('settings', 'vue-settings-admin-settings-presets'); |
||||
|
||||
?> |
||||
|
||||
<div id="settings-presets"> |
||||
</div> |
Loading…
Reference in new issue