-
-
Notifications
You must be signed in to change notification settings - Fork 5.1k
feat: configure toast timeout #62689
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,7 +22,15 @@ class BeforePreferenceListener implements IEventListener { | |
| /** | ||
| * @var string[] | ||
| */ | ||
| private const ALLOWED_KEYS = ['force_enable_blur_filter', 'shortcuts_disabled', 'primary_color']; | ||
| private const ALLOWED_KEYS = ['force_enable_blur_filter', 'shortcuts_disabled', 'primary_color', 'toast_timeout']; | ||
|
|
||
| /** | ||
| * Allowed toast timeout values in milliseconds. | ||
| * Default (7000) is represented by deleting the preference. | ||
| * | ||
| * @var string[] | ||
| */ | ||
| public const TOAST_TIMEOUT_VALUES = ['15000', '30000', '-1']; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be integers instead |
||
|
|
||
| public function __construct( | ||
| private IAppManager $appManager, | ||
|
|
@@ -62,6 +70,9 @@ private function handleThemingValues(BeforePreferenceSetEvent|BeforePreferenceDe | |
| case 'primary_color': | ||
| $event->setValid(preg_match('/^\#([0-9a-f]{3}|[0-9a-f]{6})$/i', $event->getConfigValue()) === 1); | ||
| break; | ||
| case 'toast_timeout': | ||
| $event->setValid(in_array($event->getConfigValue(), self::TOAST_TIMEOUT_VALUES, true)); | ||
| break; | ||
| default: | ||
| $event->setValid(false); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -51,6 +51,17 @@ | |||||
| } | ||||||
| return false; | ||||||
| }); | ||||||
|
|
||||||
| $this->initialState->provideLazyInitialState('toastTimeout', function () { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| if ($this->userSession->getUser()) { | ||||||
| $uid = $this->userSession->getUser()->getUID(); | ||||||
| $value = $this->config->getUserValue($uid, Application::APP_ID, 'toast_timeout', '7000'); | ||||||
|
Check failure on line 58 in apps/theming/lib/Listener/BeforeTemplateRenderedListener.php
|
||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please use IUserConfig in new code
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes and ideally config lexicon
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here please use the correct type, there is no reason for string |
||||||
| if (in_array($value, BeforePreferenceListener::TOAST_TIMEOUT_VALUES, true) || $value === '7000') { | ||||||
| return (int)$value; | ||||||
| } | ||||||
| } | ||||||
| return 7000; | ||||||
| }); | ||||||
| } | ||||||
|
|
||||||
| $this->themeInjectionService->injectHeaders(); | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| <!-- | ||
| - SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors | ||
| - SPDX-License-Identifier: AGPL-3.0-or-later | ||
| --> | ||
|
|
||
| <script setup lang="ts"> | ||
| import axios from '@nextcloud/axios' | ||
| import { | ||
| setToastTimeout, | ||
| showError, | ||
| TOAST_DEFAULT_TIMEOUT, | ||
| TOAST_PERMANENT_TIMEOUT, | ||
| } from '@nextcloud/dialogs' | ||
| import { loadState } from '@nextcloud/initial-state' | ||
| import { t } from '@nextcloud/l10n' | ||
| import { generateOcsUrl } from '@nextcloud/router' | ||
| import { computed, ref } from 'vue' | ||
| import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch' | ||
| import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection' | ||
| import { logger } from '../utils/logger.ts' | ||
|
|
||
| const TOAST_TIMEOUT_DEFAULT = String(TOAST_DEFAULT_TIMEOUT) | ||
| const TOAST_TIMEOUT_15S = '15000' | ||
| const TOAST_TIMEOUT_30S = '30000' | ||
| const TOAST_TIMEOUT_NEVER = String(TOAST_PERMANENT_TIMEOUT) | ||
|
|
||
| const toastTimeout = ref(String(loadState('theming', 'toastTimeout', TOAST_DEFAULT_TIMEOUT))) | ||
|
Comment on lines
+22
to
+27
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As mentioned above there is no reason for string type, this is an int so also pass it as integer |
||
|
|
||
| const options = computed(() => [ | ||
| { | ||
| value: TOAST_TIMEOUT_DEFAULT, | ||
| label: t('theming', 'Default ({time} seconds)', { time: TOAST_DEFAULT_TIMEOUT / 1000 }), | ||
| }, | ||
| { | ||
| value: TOAST_TIMEOUT_15S, | ||
| label: t('theming', '15 seconds'), | ||
| }, | ||
| { | ||
| value: TOAST_TIMEOUT_30S, | ||
| label: t('theming', '30 seconds'), | ||
| }, | ||
| { | ||
| value: TOAST_TIMEOUT_NEVER, | ||
| label: t('theming', 'Never dismiss'), | ||
| }, | ||
| ]) | ||
|
|
||
| /** | ||
| * Persist and apply the selected toast timeout | ||
| * | ||
| * @param value - Selected timeout preference value | ||
| */ | ||
| async function updateToastTimeout(value: string | number | boolean) { | ||
| const nextValue = String(value) | ||
| const previous = toastTimeout.value | ||
| toastTimeout.value = nextValue | ||
| setToastTimeout(Number(nextValue)) | ||
|
|
||
| const url = generateOcsUrl('apps/provisioning_api/api/v1/config/users/{appId}/{configKey}', { | ||
| appId: 'theming', | ||
| configKey: 'toast_timeout', | ||
| }) | ||
|
|
||
| try { | ||
| if (nextValue === TOAST_TIMEOUT_DEFAULT) { | ||
| await axios.delete(url) | ||
| } else { | ||
| await axios.post(url, { | ||
| configValue: nextValue, | ||
| }) | ||
| } | ||
| } catch (error) { | ||
| toastTimeout.value = previous | ||
| setToastTimeout(Number(previous)) | ||
| logger.error('Could not update toast timeout', { error }) | ||
| showError(t('theming', 'Could not update toast timeout')) | ||
| } | ||
| } | ||
| </script> | ||
|
|
||
| <template> | ||
| <NcSettingsSection | ||
| :name="t('theming', 'Toast notifications')" | ||
| :description="t('theming', 'Set how long toast messages stay visible. Choose a longer duration if you need more time to read them.')"> | ||
| <fieldset class="toast-timeout"> | ||
| <legend class="hidden-visually"> | ||
| {{ t('theming', 'Toast timeout') }} | ||
| </legend> | ||
| <NcCheckboxRadioSwitch | ||
| v-for="option in options" | ||
| :key="option.value" | ||
| :modelValue="toastTimeout" | ||
| type="radio" | ||
| name="toast_timeout" | ||
| :value="option.value" | ||
| @update:modelValue="updateToastTimeout"> | ||
| {{ option.label }} | ||
| </NcCheckboxRadioSwitch> | ||
| </fieldset> | ||
| </NcSettingsSection> | ||
| </template> | ||
|
|
||
| <style scoped lang="scss"> | ||
| .toast-timeout { | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 4px; | ||
| border: 0; | ||
| margin: 0; | ||
| padding: 0; | ||
| } | ||
| </style> | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.