Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/server.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
authorJoas Schilling <coding@schilljs.com>2022-03-29 23:18:40 +0300
committerJohn Molakvoæ <skjnldsv@protonmail.com>2022-04-21 10:29:33 +0300
commitb3cf312edcefec3fb26bad8637f3a0969504be87 (patch)
tree43397a079ea2b558e23cef722d6abbca11bc7cfb /apps
parent12ed5c9ff3e9dac25b43a1ad934a97a86037000b (diff)
Start theming providers
Signed-off-by: Joas Schilling <coding@schilljs.com>
Diffstat (limited to 'apps')
-rw-r--r--apps/theming/appinfo/routes.php4
-rw-r--r--apps/theming/css/settings-admin.scss2
-rw-r--r--apps/theming/js/settings-admin.js52
-rw-r--r--apps/theming/lib/Controller/ThemingController.php109
-rw-r--r--apps/theming/lib/ITheme.php55
-rw-r--r--apps/theming/lib/Listener/BeforeTemplateRenderedListener.php49
-rw-r--r--apps/theming/lib/Service/ThemeInjectionService.php88
-rw-r--r--apps/theming/lib/Service/ThemesService.php56
-rw-r--r--apps/theming/lib/Themes/DarkHighContrastTheme.php47
-rw-r--r--apps/theming/lib/Themes/DarkTheme.php75
-rw-r--r--apps/theming/lib/Themes/DefaultTheme.php160
-rw-r--r--apps/theming/lib/Themes/HighContrastTheme.php47
-rw-r--r--apps/theming/lib/Util.php75
13 files changed, 643 insertions, 176 deletions
diff --git a/apps/theming/appinfo/routes.php b/apps/theming/appinfo/routes.php
index 0628ade8032..358f6a39ad4 100644
--- a/apps/theming/appinfo/routes.php
+++ b/apps/theming/appinfo/routes.php
@@ -44,8 +44,8 @@ return ['routes' => [
'verb' => 'POST'
],
[
- 'name' => 'Theming#getStylesheet',
- 'url' => '/styles',
+ 'name' => 'Theming#getThemeVariables',
+ 'url' => '/theme/{themeId}.css',
'verb' => 'GET',
],
[
diff --git a/apps/theming/css/settings-admin.scss b/apps/theming/css/settings-admin.scss
index 504760d4596..c4d67917506 100644
--- a/apps/theming/css/settings-admin.scss
+++ b/apps/theming/css/settings-admin.scss
@@ -100,6 +100,7 @@
margin-top: 10px;
margin-bottom: 20px;
cursor: pointer;
+ background-image: var(--image-login);
#theming-preview-logo {
cursor: pointer;
@@ -110,6 +111,7 @@
background-position: center;
background-repeat: no-repeat;
background-size: contain;
+ background-image: var(--image-logo);
}
}
diff --git a/apps/theming/js/settings-admin.js b/apps/theming/js/settings-admin.js
index 335492fdae2..7efdab6dda4 100644
--- a/apps/theming/js/settings-admin.js
+++ b/apps/theming/js/settings-admin.js
@@ -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) {
diff --git a/apps/theming/lib/Controller/ThemingController.php b/apps/theming/lib/Controller/ThemingController.php
index a735dfafc2c..e8f6bd430d3 100644
--- a/apps/theming/lib/Controller/ThemingController.php
+++ b/apps/theming/lib/Controller/ThemingController.php
@@ -37,12 +37,13 @@
*/
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
index 00000000000..7f3e49075ca
--- /dev/null
+++ b/apps/theming/lib/ITheme.php
@@ -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;
+}
diff --git a/apps/theming/lib/Listener/BeforeTemplateRenderedListener.php b/apps/theming/lib/Listener/BeforeTemplateRenderedListener.php
index 10a9434835c..6842a731b5f 100644
--- a/apps/theming/lib/Listener/BeforeTemplateRenderedListener.php
+++ b/apps/theming/lib/Listener/BeforeTemplateRenderedListener.php
@@ -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
index 00000000000..0b4890cd08b
--- /dev/null
+++ b/apps/theming/lib/Service/ThemeInjectionService.php
@@ -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
index 00000000000..3092b3bcbb5
--- /dev/null
+++ b/apps/theming/lib/Service/ThemesService.php
@@ -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
index 00000000000..1f00990c7de
--- /dev/null
+++ b/apps/theming/lib/Themes/DarkHighContrastTheme.php
@@ -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
index 00000000000..b7ec16aa56b
--- /dev/null
+++ b/apps/theming/lib/Themes/DarkTheme.php
@@ -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
index 00000000000..97650bf6292
--- /dev/null
+++ b/apps/theming/lib/Themes/DefaultTheme.php
@@ -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
index 00000000000..cae7cc5be98
--- /dev/null
+++ b/apps/theming/lib/Themes/HighContrastTheme.php
@@ -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;
+ }
+}
diff --git a/apps/theming/lib/Util.php b/apps/theming/lib/Util.php
index 05b954d5059..beaca679149 100644
--- a/apps/theming/lib/Util.php
+++ b/apps/theming/lib/Util.php
@@ -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());
}
/**