]> source.dussan.org Git - nextcloud-server.git/commitdiff
Start theming providers
authorJoas Schilling <coding@schilljs.com>
Tue, 29 Mar 2022 20:18:40 +0000 (22:18 +0200)
committerJohn Molakvoæ <skjnldsv@protonmail.com>
Thu, 21 Apr 2022 07:29:33 +0000 (09:29 +0200)
Signed-off-by: Joas Schilling <coding@schilljs.com>
21 files changed:
apps/theming/appinfo/routes.php
apps/theming/css/settings-admin.scss
apps/theming/js/settings-admin.js
apps/theming/lib/Controller/ThemingController.php
apps/theming/lib/ITheme.php [new file with mode: 0644]
apps/theming/lib/Listener/BeforeTemplateRenderedListener.php
apps/theming/lib/Service/ThemeInjectionService.php [new file with mode: 0644]
apps/theming/lib/Service/ThemesService.php [new file with mode: 0644]
apps/theming/lib/Themes/DarkHighContrastTheme.php [new file with mode: 0644]
apps/theming/lib/Themes/DarkTheme.php [new file with mode: 0644]
apps/theming/lib/Themes/DefaultTheme.php [new file with mode: 0644]
apps/theming/lib/Themes/HighContrastTheme.php [new file with mode: 0644]
apps/theming/lib/Util.php
composer.json
composer.lock
core/css/apps.scss
core/css/header.scss
core/css/styles.scss
core/templates/layout.user.php
lib/private/TemplateLayout.php
lib/private/legacy/OC_Template.php

index 0628ade8032eaa115192b7890082f4fd14b061c2..358f6a39ad4a517c85d228f5d34915ad44ce881f 100644 (file)
@@ -44,8 +44,8 @@ return ['routes' => [
                'verb' => 'POST'
        ],
        [
-               'name' => 'Theming#getStylesheet',
-               'url' => '/styles',
+               'name' => 'Theming#getThemeVariables',
+               'url' => '/theme/{themeId}.css',
                'verb' => 'GET',
        ],
        [
index 504760d4596188c576952244f55a7b8dc2adfbe7..c4d67917506e51c7da23e413f54997abf1d07726 100644 (file)
         margin-top: 10px;
         margin-bottom: 20px;
         cursor: pointer;
+        background-image: var(--image-login);
 
         #theming-preview-logo {
             cursor: pointer;
             background-position: center;
             background-repeat: no-repeat;
             background-size: contain;
+            background-image: var(--image-logo);
         }
     }
 
index 335492fdae2eb6521030512f08fb301148cffd52..7efdab6dda47cdc2714449c32776b3253c5df85b 100644 (file)
@@ -28,9 +28,9 @@ function setThemingValue(setting, value) {
        startLoading();
        $.post(
                OC.generateUrl('/apps/theming/ajax/updateStylesheet'), {'setting' : setting, 'value' : value}
-       ).done(function(response) {
+       ).done(function() {
                hideUndoButton(setting, value);
-               preview(setting, value, response.data.serverCssUrl);
+               preview(setting, value);
        }).fail(function(response) {
                OC.msg.finishedSaving('#theming_settings_msg', response.responseJSON);
                $('#theming_settings_loading').hide();
@@ -39,41 +39,31 @@ function setThemingValue(setting, value) {
 
 function preview(setting, value, serverCssUrl) {
        OC.msg.startAction('#theming_settings_msg', t('theming', 'Loading preview…'));
-       var stylesheetsLoaded = 1;
-       var reloadStylesheets = function(cssFile) {
-               var queryString = '?reload=' + new Date().getTime();
-               var url = cssFile + queryString;
-               var old = $('link[href*="' + cssFile + '"]');
-               var stylesheet = $("<link/>", {
-                       rel: "stylesheet",
-                       type: "text/css",
-                       href: url
-               });
-               stylesheet.load(function () {
-                       $(old).remove();
-                       stylesheetsLoaded--;
-                       if(stylesheetsLoaded === 0) {
-                               $('#theming_settings_loading').hide();
-                               var response = { status: 'success', data: {message: t('theming', 'Saved')}};
-                               OC.msg.finishedSaving('#theming_settings_msg', response);
-                       }
-               });
-               stylesheet.appendTo("head");
-       };
-
-       if (serverCssUrl !== undefined) {
-               stylesheetsLoaded++;
 
-               reloadStylesheets(serverCssUrl);
-       }
-       reloadStylesheets(OC.generateUrl('/apps/theming/styles'));
+       // Get all theming themes css links and force reload them
+       [...document.querySelectorAll('link.theme')]
+               .forEach(theme => {
+                       // Only edit the clone to not remove applied one
+                       var clone = theme.cloneNode()
+                       var url = new URL(clone.href)
+                       // Set current timestamp as cache buster
+                       url.searchParams.set('v', Date.now())
+                       clone.href = url.toString()
+                       clone.onload = function() {
+                               theme.remove()
+                       }
+                       document.head.append(clone)
+               })
 
        if (setting === 'name') {
                window.document.title = t('core', 'Admin') + " - " + value;
        }
-
+       
+       // Finish
+       $('#theming_settings_loading').hide();
+       var response = { status: 'success', data: {message: t('theming', 'Saved')}};
+       OC.msg.finishedSaving('#theming_settings_msg', response);
        hideUndoButton(setting, value);
-
 }
 
 function hideUndoButton(setting, value) {
index a735dfafc2ce88e3cabd4fa344db2c99e2571119..e8f6bd430d3e264195b40fd46858a9a41e45de2d 100644 (file)
  */
 namespace OCA\Theming\Controller;
 
-use OC\Template\SCSSCacher;
 use OCA\Theming\ImageManager;
+use OCA\Theming\Service\ThemesService;
 use OCA\Theming\ThemingDefaults;
 use OCP\App\IAppManager;
 use OCP\AppFramework\Controller;
 use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\DataDisplayResponse;
 use OCP\AppFramework\Http\DataResponse;
 use OCP\AppFramework\Http\FileDisplayResponse;
 use OCP\AppFramework\Http\NotFoundResponse;
@@ -63,40 +64,16 @@ use OCP\IURLGenerator;
  * @package OCA\Theming\Controller
  */
 class ThemingController extends Controller {
-       /** @var ThemingDefaults */
-       private $themingDefaults;
-       /** @var IL10N */
-       private $l10n;
-       /** @var IConfig */
-       private $config;
-       /** @var ITempManager */
-       private $tempManager;
-       /** @var IAppData */
-       private $appData;
-       /** @var SCSSCacher */
-       private $scssCacher;
-       /** @var IURLGenerator */
-       private $urlGenerator;
-       /** @var IAppManager */
-       private $appManager;
-       /** @var ImageManager */
-       private $imageManager;
+       private ThemingDefaults $themingDefaults;
+       private IL10N $l10n;
+       private IConfig $config;
+       private ITempManager $tempManager;
+       private IAppData $appData;
+       private IURLGenerator $urlGenerator;
+       private IAppManager $appManager;
+       private ImageManager $imageManager;
+       private ThemesService $themesService;
 
-       /**
-        * ThemingController constructor.
-        *
-        * @param string $appName
-        * @param IRequest $request
-        * @param IConfig $config
-        * @param ThemingDefaults $themingDefaults
-        * @param IL10N $l
-        * @param ITempManager $tempManager
-        * @param IAppData $appData
-        * @param SCSSCacher $scssCacher
-        * @param IURLGenerator $urlGenerator
-        * @param IAppManager $appManager
-        * @param ImageManager $imageManager
-        */
        public function __construct(
                $appName,
                IRequest $request,
@@ -105,10 +82,10 @@ class ThemingController extends Controller {
                IL10N $l,
                ITempManager $tempManager,
                IAppData $appData,
-               SCSSCacher $scssCacher,
                IURLGenerator $urlGenerator,
                IAppManager $appManager,
-               ImageManager $imageManager
+               ImageManager $imageManager,
+               ThemesService $themesService
        ) {
                parent::__construct($appName, $request);
 
@@ -117,10 +94,10 @@ class ThemingController extends Controller {
                $this->config = $config;
                $this->tempManager = $tempManager;
                $this->appData = $appData;
-               $this->scssCacher = $scssCacher;
                $this->urlGenerator = $urlGenerator;
                $this->appManager = $appManager;
                $this->imageManager = $imageManager;
+               $this->themesService = $themesService;
        }
 
        /**
@@ -185,19 +162,12 @@ class ThemingController extends Controller {
 
                $this->themingDefaults->set($setting, $value);
 
-               // reprocess server scss for preview
-               $cssCached = $this->scssCacher->process(\OC::$SERVERROOT, 'core/css/css-variables.scss', 'core');
-
-               return new DataResponse(
-                       [
-                               'data' =>
-                                       [
-                                               'message' => $this->l10n->t('Saved'),
-                                               'serverCssUrl' => $this->urlGenerator->linkTo('', $this->scssCacher->getCachedSCSS('core', '/core/css/css-variables.scss'))
-                                       ],
-                               'status' => 'success'
-                       ]
-               );
+               return new DataResponse([
+                       'data' => [
+                               'message' => $this->l10n->t('Saved'),
+                       ],
+                       'status' => 'success'
+               ]);
        }
 
        /**
@@ -262,7 +232,6 @@ class ThemingController extends Controller {
                }
 
                $name = $image['name'];
-               $cssCached = $this->scssCacher->process(\OC::$SERVERROOT, 'core/css/css-variables.scss', 'core');
 
                return new DataResponse(
                        [
@@ -271,7 +240,6 @@ class ThemingController extends Controller {
                                                'name' => $name,
                                                'url' => $this->imageManager->getImageUrl($key),
                                                'message' => $this->l10n->t('Saved'),
-                                               'serverCssUrl' => $this->urlGenerator->linkTo('', $this->scssCacher->getCachedSCSS('core', '/core/css/css-variables.scss'))
                                        ],
                                'status' => 'success'
                        ]
@@ -288,8 +256,6 @@ class ThemingController extends Controller {
         */
        public function undo(string $setting): DataResponse {
                $value = $this->themingDefaults->undo($setting);
-               // reprocess server scss for preview
-               $cssCached = $this->scssCacher->process(\OC::$SERVERROOT, 'core/css/css-variables.scss', 'core');
 
                return new DataResponse(
                        [
@@ -297,7 +263,6 @@ class ThemingController extends Controller {
                                        [
                                                'value' => $value,
                                                'message' => $this->l10n->t('Saved'),
-                                               'serverCssUrl' => $this->urlGenerator->linkTo('', $this->scssCacher->getCachedSCSS('core', '/core/css/css-variables.scss'))
                                        ],
                                'status' => 'success'
                        ]
@@ -341,25 +306,31 @@ class ThemingController extends Controller {
         * @NoSameSiteCookieRequired
         *
         * @return FileDisplayResponse|NotFoundResponse
-        * @throws NotPermittedException
-        * @throws \Exception
-        * @throws \OCP\App\AppPathNotFoundException
         */
-       public function getStylesheet() {
-               $appPath = $this->appManager->getAppPath('theming');
-
-               /* SCSSCacher is required here
-                * We cannot rely on automatic caching done by \OC_Util::addStyle,
-                * since we need to add the cacheBuster value to the url
-                */
-               $cssCached = $this->scssCacher->process($appPath, 'css/theming.scss', 'theming');
-               if (!$cssCached) {
+       public function getThemeVariables(string $themeId, bool $plain = false) {
+               $themes = $this->themesService->getThemes();
+               if (!in_array($themeId, array_keys($themes))) {
                        return new NotFoundResponse();
                }
 
+               $theme = $themes[$themeId];
+
+               // Generate variables
+               $variables = '';
+               foreach ($theme->getCSSVariables() as $variable => $value) {
+                       $variables .= "$variable:$value; ";
+               };
+
+               // If plain is set, the browser decides of the css priority
+               if ($plain) {
+                       $css = ":root { $variables }";
+               } else { 
+                       // If not set, we'll rely on the body class
+                       $css = "body[data-theme-$themeId] { $variables }";
+               }
+
                try {
-                       $cssFile = $this->scssCacher->getCachedCSS('theming', 'theming.css');
-                       $response = new FileDisplayResponse($cssFile, Http::STATUS_OK, ['Content-Type' => 'text/css']);
+                       $response = new DataDisplayResponse($css, Http::STATUS_OK, ['Content-Type' => 'text/css']);
                        $response->cacheFor(86400);
                        return $response;
                } catch (NotFoundException $e) {
diff --git a/apps/theming/lib/ITheme.php b/apps/theming/lib/ITheme.php
new file mode 100644 (file)
index 0000000..7f3e490
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\Theming;
+
+/**
+ * Interface ITheme
+ *
+ * @since 25.0.0
+ */
+interface ITheme {
+
+       /**
+        * Unique theme id
+        * @since 25.0.0
+        */
+       public function getId(): string;
+
+       /**
+        * Get the media query triggering this theme
+        * Optional, ignored if falsy
+        *
+        * @return string
+        * @since 25.0.0
+        */
+       public function getMediaQuery(): string;
+
+       /**
+        * Return the list of changed css variables
+        *
+        * @return array
+        * @since 25.0.0
+        */
+       public function getCSSVariables(): array;
+}
index 10a9434835c2098eed1ddb8aa481d3fd6543bc21..6842a731b5f5dd58bee905f522e127ac25b3a5dd 100644 (file)
@@ -27,6 +27,8 @@ namespace OCA\Theming\Listener;
 
 use OCA\Theming\AppInfo\Application;
 use OCA\Theming\Service\JSDataService;
+use OCA\Theming\Service\ThemeInjectionService;
+use OCA\Theming\Service\ThemesService;
 use OCP\EventDispatcher\Event;
 use OCP\EventDispatcher\IEventListener;
 use OCP\IConfig;
@@ -36,25 +38,18 @@ use OCP\IURLGenerator;
 
 class BeforeTemplateRenderedListener implements IEventListener {
 
-       /** @var IInitialStateService */
-       private $initialStateService;
-       /** @var IURLGenerator */
-       private $urlGenerator;
-       /** @var IConfig */
-       private $config;
-       /** @var IServerContainer */
-       private $serverContainer;
+       private IInitialStateService $initialStateService;
+       private IServerContainer $serverContainer;
+       private ThemeInjectionService $themeInjectionService;
 
        public function __construct(
                IInitialStateService $initialStateService,
-               IURLGenerator $urlGenerator,
-               IConfig $config,
-               IServerContainer $serverContainer
+               IServerContainer $serverContainer,
+               ThemeInjectionService $themeInjectionService
        ) {
                $this->initialStateService = $initialStateService;
-               $this->urlGenerator = $urlGenerator;
-               $this->config = $config;
                $this->serverContainer = $serverContainer;
+               $this->themeInjectionService = $themeInjectionService;
        }
 
        public function handle(Event $event): void {
@@ -63,19 +58,21 @@ class BeforeTemplateRenderedListener implements IEventListener {
                        return $serverContainer->query(JSDataService::class);
                });
 
-               $linkToCSS = $this->urlGenerator->linkToRoute(
-                       'theming.Theming.getStylesheet',
-                       [
-                               'v' => $this->config->getAppValue('theming', 'cachebuster', '0'),
-                       ]
-               );
-               \OCP\Util::addHeader(
-                       'link',
-                       [
-                               'rel' => 'stylesheet',
-                               'href' => $linkToCSS,
-                       ]
-               );
+               // $linkToCSS = $this->urlGenerator->linkToRoute(
+               //      'theming.Theming.getStylesheet',
+               //      [
+               //              'v' => $this->config->getAppValue('theming', 'cachebuster', '0'),
+               //      ]
+               // );
+               // \OCP\Util::addHeader(
+               //      'link',
+               //      [
+               //              'rel' => 'stylesheet',
+               //              'href' => $linkToCSS,
+               //      ]
+               // );
+
+               $this->themeInjectionService->injectHeaders();
 
                // Making sure to inject just after core
                \OCP\Util::addScript('theming', 'theming', 'core');
diff --git a/apps/theming/lib/Service/ThemeInjectionService.php b/apps/theming/lib/Service/ThemeInjectionService.php
new file mode 100644 (file)
index 0000000..0b4890c
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+/**
+ * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCA\Theming\Service;
+
+use OCA\Theming\Themes\DefaultTheme;
+use OCP\IURLGenerator;
+use OCP\Util;
+
+class ThemeInjectionService {
+
+       private IURLGenerator $urlGenerator;
+       private ThemesService $themesService;
+       private DefaultTheme $defaultTheme;
+
+       public function __construct(IURLGenerator $urlGenerator,
+                                                               ThemesService $themesService,
+                                                               DefaultTheme $defaultTheme) {
+               $this->urlGenerator = $urlGenerator;
+               $this->themesService = $themesService;
+               $this->defaultTheme = $defaultTheme;
+       }
+
+       public function injectHeaders() {
+               $themes = $this->themesService->getThemes();
+               $defaultTheme = $themes[$this->defaultTheme->getId()];
+               $mediaThemes = array_filter($themes, function($theme) {
+                       // Check if the theme provides a media query
+                       return (bool)$theme->getMediaQuery();
+               });
+
+               // Default theme fallback
+               $this->addThemeHeader($defaultTheme->getId());
+               
+               // Themes applied by media queries
+               foreach($mediaThemes as $theme) {
+                       $this->addThemeHeader($theme->getId(), true, $theme->getMediaQuery());
+               }
+
+               // Themes 
+               foreach($this->themesService->getThemes() as $theme) {
+                       // Ignore default theme as already processed first
+                       if ($theme->getId() === $this->defaultTheme->getId()) {
+                               continue;
+                       }
+                       $this->addThemeHeader($theme->getId(), false);
+               }
+       }
+
+       /**
+        * Inject theme header into rendered page
+        * 
+        * @param string $themeId the theme ID
+        * @param bool $plain request the :root syntax
+        * @param string $media media query to use in the <link> element
+        */
+       private function addThemeHeader(string $themeId, bool $plain = true, string $media = null) {
+               $linkToCSS = $this->urlGenerator->linkToRoute('theming.Theming.getThemeVariables', [
+                       'themeId' => $themeId,
+                       'plain' => $plain,
+               ]);
+               Util::addHeader('link', [
+                       'rel' => 'stylesheet',
+                       'media' => $media,
+                       'href' => $linkToCSS,
+                       'class' => 'theme'
+               ]);
+       }
+}
diff --git a/apps/theming/lib/Service/ThemesService.php b/apps/theming/lib/Service/ThemesService.php
new file mode 100644 (file)
index 0000000..3092b3b
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+/**
+ * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCA\Theming\Service;
+
+use OCA\Theming\Themes\DefaultTheme;
+use OCA\Theming\Themes\DarkTheme;
+use OCA\Theming\Themes\DarkHighContrastTheme;
+use OCA\Theming\Themes\HighContrastTheme;
+use OCA\Theming\ITheme;
+
+class ThemesService {
+
+       /** @var ITheme[] */
+       private array $themesProviders;
+
+       public function __construct(DefaultTheme $defaultTheme,
+                                                               DarkTheme $darkTheme,
+                                                               DarkHighContrastTheme $darkHighContrastTheme,
+                                                               HighContrastTheme $highContrastTheme) {
+               // Register themes
+               $this->themesProviders = [
+                       $defaultTheme->getId()                  => $defaultTheme,
+                       $darkTheme->getId()                             => $darkTheme,
+                       $darkHighContrastTheme->getId() => $darkHighContrastTheme,
+                       $highContrastTheme->getId()             => $highContrastTheme,
+               ];
+       }
+
+       public function getThemes() {
+               return $this->themesProviders;
+       }
+
+       public function getThemeVariables(string $id) {
+               return $this->themesProviders[$id]->getCSSVariables();
+       }
+}
diff --git a/apps/theming/lib/Themes/DarkHighContrastTheme.php b/apps/theming/lib/Themes/DarkHighContrastTheme.php
new file mode 100644 (file)
index 0000000..1f00990
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2022 Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCA\Theming\Themes;
+
+use OCA\Theming\ITheme;
+
+class DarkHighContrastTheme extends HighContrastTheme implements ITheme {
+
+       public function getId(): string {
+               return 'dark-highcontrast';
+       }
+
+       public function getMediaQuery(): string {
+               return '(prefers-color-scheme: dark) and (prefers-contrast: more)';
+       }
+
+       public function getCSSVariables(): array {
+               $variables = parent::getCSSVariables();
+
+               // FIXME …
+               $variables = $variables;
+
+               return $variables;
+       }
+}
diff --git a/apps/theming/lib/Themes/DarkTheme.php b/apps/theming/lib/Themes/DarkTheme.php
new file mode 100644 (file)
index 0000000..b7ec16a
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2022 Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCA\Theming\Themes;
+
+use OCA\Theming\ITheme;
+
+class DarkTheme extends DefaultTheme implements ITheme {
+
+       public function getId(): string {
+               return 'dark';
+       }
+
+       public function getMediaQuery(): string {
+               return '(prefers-color-scheme: dark)';
+       }
+
+       public function getCSSVariables(): array {
+               $defaultVariables = parent::getCSSVariables();
+
+               $colorMainText = '#D8D8D8';
+               $colorMainBackground = '#171717';
+               $colorMainBackgroundRGB = join(',', $this->util->hexToRGB($colorMainBackground));
+               $colorBoxShadow = $this->util->darken($colorMainBackground, 70);
+               $colorBoxShadowRGB = join(',', $this->util->hexToRGB($colorBoxShadow));
+
+               return array_merge($defaultVariables, [ 
+                       '--color-main-text' => $colorMainText,
+                       '--color-main-background' => $colorMainBackground,
+                       '--color-main-background-rgb' => $colorMainBackgroundRGB,
+
+                       '--color-background-hover' => $this->util->lighten($colorMainBackground, 4),
+                       '--color-background-dark' => $this->util->lighten($colorMainBackground, 7),
+                       '--color-background-darker' => $this->util->lighten($colorMainBackground, 14),
+
+                       '--color-placeholder-light' => $this->util->lighten($colorMainBackground, 10),
+                       '--color-placeholder-dark' => $this->util->lighten($colorMainBackground, 20),
+
+                       '--color-text-maxcontrast' => $this->util->darken($colorMainText, 30),
+                       '--color-text-light' => $this->util->darken($colorMainText, 10),
+                       '--color-text-lighter' => $this->util->darken($colorMainText, 20),
+
+                       '--color-loading-light' => '#777',
+                       '--color-loading-dark' => '#CCC',
+
+                       '--color-box-shadow-rgb' => $colorBoxShadowRGB,
+
+                       '--color-border' => $this->util->lighten($colorMainBackground, 7),
+                       '--color-border-dark' => $this->util->lighten($colorMainBackground, 14),
+
+                       '--background-invert-if-bright' => 'invert(100%)',
+               ]);
+       }
+}
diff --git a/apps/theming/lib/Themes/DefaultTheme.php b/apps/theming/lib/Themes/DefaultTheme.php
new file mode 100644 (file)
index 0000000..97650bf
--- /dev/null
@@ -0,0 +1,160 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2022 Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCA\Theming\Themes;
+
+use OCA\Theming\ThemingDefaults;
+use OCA\Theming\Util;
+use OCA\Theming\ITheme;
+use OCP\IURLGenerator;
+
+class DefaultTheme implements ITheme {
+       public Util $util;
+       public ThemingDefaults $themingDefaults;
+       public IURLGenerator $urlGenerator;
+       public string $primaryColor;
+
+       public function __construct(Util $util, ThemingDefaults $themingDefaults, IURLGenerator $urlGenerator) {
+               $this->util = $util;
+               $this->themingDefaults = $themingDefaults;
+               $this->urlGenerator = $urlGenerator;
+
+               $this->primaryColor = $this->themingDefaults->getColorPrimary();
+       }
+
+       public function getId(): string {
+               return 'default';
+       }
+
+       public function getMediaQuery(): string {
+               return '';
+       }
+
+       public function getCSSVariables(): array {
+               $colorMainText = '#222222';
+               $colorMainBackground = '#ffffff';
+               $colorMainBackgroundRGB = join(',', $this->util->hexToRGB($colorMainBackground));
+               $colorBoxShadow = $this->util->darken($colorMainBackground, 70);
+               $colorBoxShadowRGB = join(',', $this->util->hexToRGB($colorBoxShadow));
+
+               // Logo variables
+               $logoSvgPath = $this->urlGenerator->getAbsoluteURL($this->themingDefaults->getLogo());
+               $backgroundSvgPath = $this->urlGenerator->getAbsoluteURL($this->themingDefaults->getBackground());
+
+               return [
+                       '--color-main-background' => $colorMainBackground,
+                       '--color-main-background-rgb' => $colorMainBackgroundRGB,
+                       '--color-main-background-translucent' => 'rgba(var(--color-main-background-rgb), .97)',
+
+                       // to use like this: background-image: linear-gradient(0, var('--gradient-main-background));
+                       '--gradient-main-background' => 'var(--color-main-background) 0%, var(--color-main-background-translucent) 85%, transparent 100%',
+
+                       // used for different active/hover/focus/disabled states
+                       '--color-background-hover' => $this->util->darken($colorMainBackground, 4),
+                       '--color-background-dark' => $this->util->darken($colorMainBackground, 7),
+                       '--color-background-darker' => $this->util->darken($colorMainBackground, 14),
+
+                       '--color-placeholder-light' => $this->util->darken($colorMainBackground, 10),
+                       '--color-placeholder-dark' => $this->util->darken($colorMainBackground, 20),
+
+                       // primary related colours
+                       '--color-primary' => $this->primaryColor,
+                       '--color-primary-text' => $this->util->invertTextColor($this->primaryColor) ? '#000000' : '#ffffff',
+                       '--color-primary-hover' => $this->util->mix($this->primaryColor, $colorMainBackground, 80),
+                       '--color-primary-light' => $this->util->mix($this->primaryColor, $colorMainBackground, 10),
+                       '--color-primary-light-text' => $this->primaryColor,
+                       '--color-primary-light-hover' => $this->util->mix($this->primaryColor, $colorMainText, 10),
+                       '--color-primary-text-dark' => $this->util->darken($this->util->invertTextColor($this->primaryColor) ? '#000000' : '#ffffff', 7),
+                       // used for buttons, inputs...
+                       '--color-primary-element' => $this->util->elementColor($this->primaryColor),
+                       '--color-primary-element-hover' => $this->util->mix($this->util->elementColor($this->primaryColor), $colorMainBackground, 80),
+                       '--color-primary-element-light' => $this->util->lighten($this->util->elementColor($this->primaryColor), 15),
+                       '--color-primary-element-lighter' => $this->util->mix($this->util->elementColor($this->primaryColor), $colorMainBackground, 15),
+
+                       // max contrast for WCAG compliance
+                       '--color-main-text' => $colorMainText,
+                       '--color-text-maxcontrast' => $this->util->lighten($colorMainText, 33),
+                       '--color-text-light' => $colorMainText,
+                       '--color-text-lighter' => $this->util->lighten($colorMainText, 33),
+
+                       // info/warning/success feedback colours
+                       '--color-error' => '#e9322d',
+                       '--color-error-hover' => $this->util->mix('#e9322d', $colorMainBackground, 80),
+                       '--color-warning' => '#eca700',
+                       '--color-warning-hover' => $this->util->mix('#eca700', $colorMainBackground, 80),
+                       '--color-success' => '#46ba61',
+                       '--color-success-hover' => $this->util->mix('#46ba61', $colorMainBackground, 80),
+
+                       // used for the icon loading animation
+                       '--color-loading-light' => '#cccccc',
+                       '--color-loading-dark' => '#444444',
+
+                       '--color-box-shadow-rgb' => $colorBoxShadowRGB,
+                       '--color-box-shadow' => "rgba(var(--color-box-shadow-rgb), 0.5)",
+
+                       '--color-border' => $this->util->darken($colorMainBackground, 7),
+                       '--color-border-dark' => $this->util->darken($colorMainBackground, 14),
+
+                       '--font-face' => "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Cantarell, Ubuntu, 'Helvetica Neue', Arial, sans-serif, 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'",
+                       '--default-font-size' => '15px',
+
+                       // TODO: support "(prefers-reduced-motion)"
+                       '--animation-quick' => '100ms',
+                       '--animation-slow' => '300ms',
+
+                       // Default variables --------------------------------------------
+                       '--image-logo' => "url('$logoSvgPath')",
+                       '--image-login' => "url('$backgroundSvgPath')",
+                       '--image-logoheader' => "url('$logoSvgPath')",
+                       '--image-favicon' => "url('$logoSvgPath')",
+
+                       '--border-radius' => '3px',
+                       '--border-radius-large' => '10px',
+                       // pill-style button, value is large so big buttons also have correct roundness
+                       '--border-radius-pill' => '100px',
+
+                       '--default-line-height' => '24px',
+
+                       // various structure data
+                       '--header-height' => '50px',
+                       '--navigation-width' => '300px',
+                       '--sidebar-min-width' => '300px',
+                       '--sidebar-max-width' => '500px',
+                       '--list-min-width' => '200px',
+                       '--list-max-width' => '300px',
+                       '--header-menu-item-height' => '44px',
+                       '--header-menu-profile-item-height' => '66px',
+
+                       // mobile. Keep in sync with core/js/js.js
+                       '--breakpoint-mobile' => '1024px',
+
+                       // invert filter if primary is too bright
+                       // to be used for legacy reasons only. Use inline
+                       // svg with proper css variable instead or material
+                       // design icons.
+                       '--primary-invert-if-bright' => $this->util->invertTextColor($this->primaryColor) ? 'invert(100%)' : 'unset',
+                       '--background-invert-if-bright' => 'unset',
+               ];
+       }
+}
diff --git a/apps/theming/lib/Themes/HighContrastTheme.php b/apps/theming/lib/Themes/HighContrastTheme.php
new file mode 100644 (file)
index 0000000..cae7cc5
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2022 Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCA\Theming\Themes;
+
+use OCA\Theming\ITheme;
+
+class HighContrastTheme extends DefaultTheme implements ITheme {
+
+       public function getId(): string {
+               return 'highcontrast';
+       }
+
+       public function getMediaQuery(): string {
+               return '(prefers-contrast: more)';
+       }
+
+       public function getCSSVariables(): array {
+               $variables = parent::getCSSVariables();
+
+               // FIXME …
+               $variables = $variables;
+
+               return $variables;
+       }
+}
index 05b954d50599bee5ed68cf0e311585f144037f8f..beaca679149129aa560b3b1df963ce5f0ade65fe 100644 (file)
@@ -34,17 +34,13 @@ use OCP\Files\IAppData;
 use OCP\Files\NotFoundException;
 use OCP\Files\SimpleFS\ISimpleFile;
 use OCP\IConfig;
+use Mexitek\PHPColors\Color;
 
 class Util {
 
-       /** @var IConfig */
-       private $config;
-
-       /** @var IAppManager */
-       private $appManager;
-
-       /** @var IAppData */
-       private $appData;
+       private IConfig $config;
+       private IAppManager $appManager;
+       private IAppData $appData;
 
        /**
         * Util constructor.
@@ -95,6 +91,21 @@ class Util {
                return $color;
        }
 
+       public function mix(string $color1, string $color2, int $factor): string {
+               $color = new Color($color1);
+               return '#' . $color->mix($color2, $factor);
+       }
+
+       public function lighten(string $color, int $factor): string {
+               $color = new Color($color);
+               return '#' . $color->lighten($factor);
+       }
+
+       public function darken(string $color, int $factor): string {
+               $color = new Color($color);
+               return '#' . $color->darken($factor);
+       }
+
        /**
         * Convert RGB to HSL
         *
@@ -106,38 +117,16 @@ class Util {
         *
         * @return array
         */
-       public function toHSL($red, $green, $blue) {
-               $min = min($red, $green, $blue);
-               $max = max($red, $green, $blue);
-               $l = $min + $max;
-               $d = $max - $min;
-
-               if ((int) $d === 0) {
-                       $h = $s = 0;
-               } else {
-                       if ($l < 255) {
-                               $s = $d / $l;
-                       } else {
-                               $s = $d / (510 - $l);
-                       }
-
-                       if ($red == $max) {
-                               $h = 60 * ($green - $blue) / $d;
-                       } elseif ($green == $max) {
-                               $h = 60 * ($blue - $red) / $d + 120;
-                       } else {
-                               $h = 60 * ($red - $green) / $d + 240;
-                       }
-               }
-
-               return [fmod($h, 360), $s * 100, $l / 5.1];
+       public function toHSL(string $red, string $green, string $blue): array {
+               $color = new Color(Color::rgbToHex(['R' => $red, 'G' => $green, 'B' => $blue]));
+               return array_values($color->getHsl());
        }
 
        /**
         * @param string $color rgb color value
         * @return float
         */
-       public function calculateLuminance($color) {
+       public function calculateLuminance(string $color): float {
                [$red, $green, $blue] = $this->hexToRGB($color);
                $hsl = $this->toHSL($red, $green, $blue);
                return $hsl[2] / 100;
@@ -147,7 +136,7 @@ class Util {
         * @param string $color rgb color value
         * @return float
         */
-       public function calculateLuma($color) {
+       public function calculateLuma(string $color): float {
                [$red, $green, $blue] = $this->hexToRGB($color);
                return (0.2126 * $red + 0.7152 * $green + 0.0722 * $blue) / 255;
        }
@@ -157,19 +146,9 @@ class Util {
         * @return int[]
         * @psalm-return array{0: int, 1: int, 2: int}
         */
-       public function hexToRGB($color) {
-               $hex = preg_replace("/[^0-9A-Fa-f]/", '', $color);
-               if (strlen($hex) === 3) {
-                       $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
-               }
-               if (strlen($hex) !== 6) {
-                       return [0, 0, 0];
-               }
-               return [
-                       hexdec(substr($hex, 0, 2)),
-                       hexdec(substr($hex, 2, 2)),
-                       hexdec(substr($hex, 4, 2))
-               ];
+       public function hexToRGB(string $color): array {
+               $color = new Color($color);
+               return array_values($color->getRgb());
        }
 
        /**
index 69ac5821c92ac614f8e4ba0f3122d4d1a39b3073..929ded70796366bbeeb6d7d03a989c945020522b 100644 (file)
@@ -25,7 +25,8 @@
                "ext-pdo": "*",
                "ext-simplexml": "*",
                "ext-xmlreader": "*",
-               "ext-zip": "*"
+               "ext-zip": "*",
+               "mexitek/phpcolors": "^1.0"
        },
        "require-dev": {
                "bamarni/composer-bin-plugin": "^1.4"
index 0d00ed2efcfa3d9961b2b1b5d8f4ad3893975439..69042da683cd6e73c37fd8122d9bc32da00ab57f 100644 (file)
@@ -4,8 +4,62 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "8333c8a239fe5ccec285dfbccc17cca4",
-    "packages": [],
+    "content-hash": "8f8b099365b3839a80b19e266c4ba5e4",
+    "packages": [
+        {
+            "name": "mexitek/phpcolors",
+            "version": "v1.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/mexitek/phpColors.git",
+                "reference": "4043974240ca7dc3c2bec3c158588148b605b206"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/mexitek/phpColors/zipball/4043974240ca7dc3c2bec3c158588148b605b206",
+                "reference": "4043974240ca7dc3c2bec3c158588148b605b206",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2|^8.0"
+            },
+            "require-dev": {
+                "nette/tester": "^2.3",
+                "squizlabs/php_codesniffer": "^3.5"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Arlo Carreon",
+                    "homepage": "http://arlocarreon.com",
+                    "role": "creator"
+                }
+            ],
+            "description": "A series of methods that let you manipulate colors. Just incase you ever need different shades of one color on the fly.",
+            "homepage": "http://mexitek.github.com/phpColors/",
+            "keywords": [
+                "color",
+                "css",
+                "design",
+                "frontend",
+                "ui"
+            ],
+            "support": {
+                "issues": "https://github.com/mexitek/phpColors/issues",
+                "source": "https://github.com/mexitek/phpColors"
+            },
+            "time": "2021-11-26T13:19:08+00:00"
+        }
+    ],
     "packages-dev": [
         {
             "name": "bamarni/composer-bin-plugin",
index 15742786b0eaafbb4f212e18f77d4d31e6362a8a..b683ffae0ef087700932c692cfde8b8acd6d67c6 100644 (file)
@@ -285,6 +285,8 @@ kbd {
                                    margin-right: 11px;
                                    width: 16px;
                                    height: 16px;
+                                       // Legacy invert if bright background
+                                       filter: var(--background-invert-if-bright);
                                }
 
                                /* counter can also be inside the link */
index 27a8fe289fa134d3f5a9f622b9ee6583ba73c9ee..2c1dc647189150a50b4fd71c88cce8b5ef69eb3d 100644 (file)
@@ -449,6 +449,11 @@ nav[role='navigation'] {
                        // Make sure most app names don’t ellipsize
                        letter-spacing: -0.5px;
                        font-size: 12px;
+
+                       // If the primary is too bright, invert the app icons
+                       svg image {
+                               filter: var(--primary-invert-if-bright);
+                       }
                }
 
                /* focused app visual feedback */
index 8a15cfa19d836affe322985c0e4477124d8c34cb..27e5675b53a995ecbb88ffc5f2c728cfce5841be 100644 (file)
@@ -975,6 +975,7 @@ span.ui-icon {
                background-size: 20px 20px;
                padding: 14px;
                cursor: pointer;
+               filter: var(--primary-invert-if-bright);
 
                &:hover,
                &:focus,
index 2b84c89fca6016a16fe8c413e41944bef7057d63..f5ac783b3402fae7b425fc4716370361dfb75f91 100644 (file)
@@ -64,7 +64,7 @@ $getUserAvatar = static function (int $size) use ($_): string {
                                        </div>
                                </a>
 
-                               <ul id="appmenu" <?php if ($_['themingInvertMenu']) { ?>class="inverted"<?php } ?>>
+                               <ul id="appmenu">
                                        <?php foreach ($_['navigation'] as $entry): ?>
                                                <li data-id="<?php p($entry['id']); ?>" class="hidden" tabindex="-1">
                                                        <a href="<?php print_unescaped($entry['href']); ?>"
@@ -73,13 +73,12 @@ $getUserAvatar = static function (int $size) use ($_): string {
                                                                aria-label="<?php p($entry['name']); ?>">
                                                                        <svg width="24" height="20" viewBox="0 0 24 20" alt=""<?php if ($entry['unread'] !== 0) { ?> class="has-unread"<?php } ?>>
                                                                                <defs>
-                                                                                       <?php if ($_['themingInvertMenu']) { ?><filter id="invertMenuMain-<?php p($entry['id']); ?>"><feColorMatrix in="SourceGraphic" type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0" /></filter><?php } ?>
                                                                                        <mask id="hole">
                                                                                                <rect width="100%" height="100%" fill="white"/>
                                                                                                <circle r="4.5" cx="21" cy="3" fill="black"/>
                                                                                        </mask>
                                                                                </defs>
-                                                                               <image x="2" y="0" width="20" height="20" preserveAspectRatio="xMinYMin meet"<?php if ($_['themingInvertMenu']) { ?> filter="url(#invertMenuMain-<?php p($entry['id']); ?>)"<?php } ?> xlink:href="<?php print_unescaped($entry['icon'] . '?v=' . $_['versionHash']); ?>" style="<?php if ($entry['unread'] !== 0) { ?>mask: url("#hole");<?php } ?>" class="app-icon"></image>
+                                                                               <image x="2" y="0" width="20" height="20" preserveAspectRatio="xMinYMin meet" xlink:href="<?php print_unescaped($entry['icon'] . '?v=' . $_['versionHash']); ?>" style="<?php if ($entry['unread'] !== 0) { ?>mask: url("#hole");<?php } ?>" class="app-icon"></image>
                                                                                <circle class="app-icon-notification" r="3" cx="21" cy="3" fill="red"/>
                                                                        </svg>
                                                                <div class="unread-counter" aria-hidden="true"><?php p($entry['unread']); ?></div>
index 4e633bf8b06cbc1a3f9038ae169cb41d223f7285..a379f23a1ef6099e1971338b20b7f36b611d4b68 100644 (file)
@@ -141,17 +141,6 @@ class TemplateLayout extends \OC_Template {
                                $this->assign('userAvatarSet', true);
                                $this->assign('userAvatarVersion', $this->config->getUserValue(\OC_User::getUser(), 'avatar', 'version', 0));
                        }
-
-                       // check if app menu icons should be inverted
-                       try {
-                               /** @var \OCA\Theming\Util $util */
-                               $util = \OC::$server->query(\OCA\Theming\Util::class);
-                               $this->assign('themingInvertMenu', $util->invertTextColor(\OC::$server->getThemingDefaults()->getColorPrimary()));
-                       } catch (\OCP\AppFramework\QueryException $e) {
-                               $this->assign('themingInvertMenu', false);
-                       } catch (\OCP\AutoloadNotAllowedException $e) {
-                               $this->assign('themingInvertMenu', false);
-                       }
                } elseif ($renderAs === TemplateResponse::RENDER_AS_ERROR) {
                        parent::__construct('core', 'layout.guest', '', false);
                        $this->assign('bodyid', 'body-login');
index 16ad7273cd262c961f47adb2f0a4f8132e1c1010..b7a400d3269292ac295565a1cd6171a4c68edb7e 100644 (file)
@@ -105,7 +105,7 @@ class OC_Template extends \OC\Template\Base {
                        // apps that started before the template initialization can load their own scripts/styles
                        // so to make sure this scripts/styles here are loaded first we put all core scripts first
                        // check lib/public/Util.php
-                       OC_Util::addStyle('css-variables', null, true);
+                       // OC_Util::addStyle('css-variables', null, true);
                        OC_Util::addStyle('server', null, true);
 
                        // include common nextcloud webpack bundle