diff --git a/apps/theming/lib/ConfigLexicon.php b/apps/theming/lib/ConfigLexicon.php index 6c91d7dfad30b..f9b03b018aff2 100644 --- a/apps/theming/lib/ConfigLexicon.php +++ b/apps/theming/lib/ConfigLexicon.php @@ -24,6 +24,8 @@ class ConfigLexicon implements ILexicon { /** The cache buster index */ public const CACHE_BUSTER = 'cachebuster'; public const USER_THEMING_DISABLED = 'disable-user-theming'; + public const TOAST_TIMEOUT = 'toast_timeout'; + public const TOAST_TIMEOUT_DEFAULT = 7000; /** Name of the software running on this instance (usually "Nextcloud") */ public const PRODUCT_NAME = 'productName'; @@ -114,6 +116,13 @@ public function getAppConfigs(): array { #[\Override] public function getUserConfigs(): array { - return []; + return [ + new Entry( + self::TOAST_TIMEOUT, + ValueType::INT, + defaultRaw: self::TOAST_TIMEOUT_DEFAULT, + definition: 'How long toast notifications remain visible in milliseconds.', + ), + ]; } } diff --git a/apps/theming/lib/Listener/BeforePreferenceListener.php b/apps/theming/lib/Listener/BeforePreferenceListener.php index d823df1ab33c2..d964a7f884b05 100644 --- a/apps/theming/lib/Listener/BeforePreferenceListener.php +++ b/apps/theming/lib/Listener/BeforePreferenceListener.php @@ -10,6 +10,7 @@ namespace OCA\Theming\Listener; use OCA\Theming\AppInfo\Application; +use OCA\Theming\ConfigLexicon; use OCP\App\IAppManager; use OCP\Config\BeforePreferenceDeletedEvent; use OCP\Config\BeforePreferenceSetEvent; @@ -22,7 +23,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', ConfigLexicon::TOAST_TIMEOUT]; + + /** + * Allowed toast timeout values in milliseconds. + * Default (7000) is represented by deleting the preference. + * + * @var int[] + */ + public const array TOAST_TIMEOUT_VALUES = [15000, 30000, -1]; public function __construct( private IAppManager $appManager, @@ -62,6 +71,10 @@ 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 ConfigLexicon::TOAST_TIMEOUT: + $value = filter_var($event->getConfigValue(), FILTER_VALIDATE_INT); + $event->setValid($value !== false && in_array($value, self::TOAST_TIMEOUT_VALUES, true)); + break; default: $event->setValid(false); } diff --git a/apps/theming/lib/Listener/BeforeTemplateRenderedListener.php b/apps/theming/lib/Listener/BeforeTemplateRenderedListener.php index 3a62d8ebdf32b..02c2b5038dd08 100644 --- a/apps/theming/lib/Listener/BeforeTemplateRenderedListener.php +++ b/apps/theming/lib/Listener/BeforeTemplateRenderedListener.php @@ -10,12 +10,14 @@ namespace OCA\Theming\Listener; use OCA\Theming\AppInfo\Application; +use OCA\Theming\ConfigLexicon; use OCA\Theming\Service\JSDataService; use OCA\Theming\Service\ThemeInjectionService; use OCP\AppFramework\Http\Events\BeforeLoginTemplateRenderedEvent; use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent; use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Services\IInitialState; +use OCP\Config\IUserConfig; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; use OCP\IConfig; @@ -32,6 +34,7 @@ public function __construct( private ThemeInjectionService $themeInjectionService, private IUserSession $userSession, private IConfig $config, + private IUserConfig $userConfig, ) { } @@ -51,6 +54,17 @@ public function handle(Event $event): void { } return false; }); + + $this->initialState->provideLazyInitialState('toastTimeout', function (): int { + if ($this->userSession->getUser()) { + $uid = $this->userSession->getUser()->getUID(); + $value = $this->userConfig->getValueInt($uid, Application::APP_ID, ConfigLexicon::TOAST_TIMEOUT, ConfigLexicon::TOAST_TIMEOUT_DEFAULT); + if (in_array($value, BeforePreferenceListener::TOAST_TIMEOUT_VALUES, true) || $value === ConfigLexicon::TOAST_TIMEOUT_DEFAULT) { + return $value; + } + } + return ConfigLexicon::TOAST_TIMEOUT_DEFAULT; + }); } $this->themeInjectionService->injectHeaders(); diff --git a/apps/theming/src/components/UserSectionToastTimeout.vue b/apps/theming/src/components/UserSectionToastTimeout.vue new file mode 100644 index 0000000000000..81481bc078931 --- /dev/null +++ b/apps/theming/src/components/UserSectionToastTimeout.vue @@ -0,0 +1,110 @@ + + + + + + + diff --git a/apps/theming/src/theming.ts b/apps/theming/src/theming.ts index 4678efe37a319..4187da73a9e56 100644 --- a/apps/theming/src/theming.ts +++ b/apps/theming/src/theming.ts @@ -3,6 +3,12 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +import { setToastTimeout, TOAST_DEFAULT_TIMEOUT } from '@nextcloud/dialogs' import { loadState } from '@nextcloud/initial-state' window.OCA.Theming = loadState('theming', 'data') + +const toastTimeout = loadState('theming', 'toastTimeout', TOAST_DEFAULT_TIMEOUT) +if (typeof toastTimeout === 'number') { + setToastTimeout(toastTimeout) +} diff --git a/apps/theming/src/views/UserTheming.vue b/apps/theming/src/views/UserTheming.vue index 6ecee83202e7a..7bb7128547e0d 100644 --- a/apps/theming/src/views/UserTheming.vue +++ b/apps/theming/src/views/UserTheming.vue @@ -49,6 +49,7 @@ + @@ -68,6 +69,7 @@ import UserSectionAppMenu from '../components/UserSectionAppMenu.vue' import UserSectionBackground from '../components/UserSectionBackground.vue' import UserSectionHotkeys from '../components/UserSectionHotkeys.vue' import UserSectionPrimaryColor from '../components/UserSectionPrimaryColor.vue' +import UserSectionToastTimeout from '../components/UserSectionToastTimeout.vue' import { refreshStyles } from '../utils/refreshStyles.js' const isUserThemingDisabled = loadState('theming', 'isUserThemingDisabled')