Signed-off-by: Joas Schilling <coding@schilljs.com>tags/v25.0.0beta1
'verb' => 'POST' | 'verb' => 'POST' | ||||
], | ], | ||||
[ | [ | ||||
'name' => 'Theming#getStylesheet', | |||||
'url' => '/styles', | |||||
'name' => 'Theming#getThemeVariables', | |||||
'url' => '/theme/{themeId}.css', | |||||
'verb' => 'GET', | 'verb' => 'GET', | ||||
], | ], | ||||
[ | [ |
margin-top: 10px; | margin-top: 10px; | ||||
margin-bottom: 20px; | margin-bottom: 20px; | ||||
cursor: pointer; | cursor: pointer; | ||||
background-image: var(--image-login); | |||||
#theming-preview-logo { | #theming-preview-logo { | ||||
cursor: pointer; | cursor: pointer; | ||||
background-position: center; | background-position: center; | ||||
background-repeat: no-repeat; | background-repeat: no-repeat; | ||||
background-size: contain; | background-size: contain; | ||||
background-image: var(--image-logo); | |||||
} | } | ||||
} | } | ||||
startLoading(); | startLoading(); | ||||
$.post( | $.post( | ||||
OC.generateUrl('/apps/theming/ajax/updateStylesheet'), {'setting' : setting, 'value' : value} | OC.generateUrl('/apps/theming/ajax/updateStylesheet'), {'setting' : setting, 'value' : value} | ||||
).done(function(response) { | |||||
).done(function() { | |||||
hideUndoButton(setting, value); | hideUndoButton(setting, value); | ||||
preview(setting, value, response.data.serverCssUrl); | |||||
preview(setting, value); | |||||
}).fail(function(response) { | }).fail(function(response) { | ||||
OC.msg.finishedSaving('#theming_settings_msg', response.responseJSON); | OC.msg.finishedSaving('#theming_settings_msg', response.responseJSON); | ||||
$('#theming_settings_loading').hide(); | $('#theming_settings_loading').hide(); | ||||
function preview(setting, value, serverCssUrl) { | function preview(setting, value, serverCssUrl) { | ||||
OC.msg.startAction('#theming_settings_msg', t('theming', 'Loading preview…')); | 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') { | if (setting === 'name') { | ||||
window.document.title = t('core', 'Admin') + " - " + value; | 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); | hideUndoButton(setting, value); | ||||
} | } | ||||
function hideUndoButton(setting, value) { | function hideUndoButton(setting, value) { |
*/ | */ | ||||
namespace OCA\Theming\Controller; | namespace OCA\Theming\Controller; | ||||
use OC\Template\SCSSCacher; | |||||
use OCA\Theming\ImageManager; | use OCA\Theming\ImageManager; | ||||
use OCA\Theming\Service\ThemesService; | |||||
use OCA\Theming\ThemingDefaults; | use OCA\Theming\ThemingDefaults; | ||||
use OCP\App\IAppManager; | use OCP\App\IAppManager; | ||||
use OCP\AppFramework\Controller; | use OCP\AppFramework\Controller; | ||||
use OCP\AppFramework\Http; | use OCP\AppFramework\Http; | ||||
use OCP\AppFramework\Http\DataDisplayResponse; | |||||
use OCP\AppFramework\Http\DataResponse; | use OCP\AppFramework\Http\DataResponse; | ||||
use OCP\AppFramework\Http\FileDisplayResponse; | use OCP\AppFramework\Http\FileDisplayResponse; | ||||
use OCP\AppFramework\Http\NotFoundResponse; | use OCP\AppFramework\Http\NotFoundResponse; | ||||
* @package OCA\Theming\Controller | * @package OCA\Theming\Controller | ||||
*/ | */ | ||||
class ThemingController extends 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( | public function __construct( | ||||
$appName, | $appName, | ||||
IRequest $request, | IRequest $request, | ||||
IL10N $l, | IL10N $l, | ||||
ITempManager $tempManager, | ITempManager $tempManager, | ||||
IAppData $appData, | IAppData $appData, | ||||
SCSSCacher $scssCacher, | |||||
IURLGenerator $urlGenerator, | IURLGenerator $urlGenerator, | ||||
IAppManager $appManager, | IAppManager $appManager, | ||||
ImageManager $imageManager | |||||
ImageManager $imageManager, | |||||
ThemesService $themesService | |||||
) { | ) { | ||||
parent::__construct($appName, $request); | parent::__construct($appName, $request); | ||||
$this->config = $config; | $this->config = $config; | ||||
$this->tempManager = $tempManager; | $this->tempManager = $tempManager; | ||||
$this->appData = $appData; | $this->appData = $appData; | ||||
$this->scssCacher = $scssCacher; | |||||
$this->urlGenerator = $urlGenerator; | $this->urlGenerator = $urlGenerator; | ||||
$this->appManager = $appManager; | $this->appManager = $appManager; | ||||
$this->imageManager = $imageManager; | $this->imageManager = $imageManager; | ||||
$this->themesService = $themesService; | |||||
} | } | ||||
/** | /** | ||||
$this->themingDefaults->set($setting, $value); | $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' | |||||
]); | |||||
} | } | ||||
/** | /** | ||||
} | } | ||||
$name = $image['name']; | $name = $image['name']; | ||||
$cssCached = $this->scssCacher->process(\OC::$SERVERROOT, 'core/css/css-variables.scss', 'core'); | |||||
return new DataResponse( | return new DataResponse( | ||||
[ | [ | ||||
'name' => $name, | 'name' => $name, | ||||
'url' => $this->imageManager->getImageUrl($key), | 'url' => $this->imageManager->getImageUrl($key), | ||||
'message' => $this->l10n->t('Saved'), | 'message' => $this->l10n->t('Saved'), | ||||
'serverCssUrl' => $this->urlGenerator->linkTo('', $this->scssCacher->getCachedSCSS('core', '/core/css/css-variables.scss')) | |||||
], | ], | ||||
'status' => 'success' | 'status' => 'success' | ||||
] | ] | ||||
*/ | */ | ||||
public function undo(string $setting): DataResponse { | public function undo(string $setting): DataResponse { | ||||
$value = $this->themingDefaults->undo($setting); | $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( | return new DataResponse( | ||||
[ | [ | ||||
[ | [ | ||||
'value' => $value, | 'value' => $value, | ||||
'message' => $this->l10n->t('Saved'), | 'message' => $this->l10n->t('Saved'), | ||||
'serverCssUrl' => $this->urlGenerator->linkTo('', $this->scssCacher->getCachedSCSS('core', '/core/css/css-variables.scss')) | |||||
], | ], | ||||
'status' => 'success' | 'status' => 'success' | ||||
] | ] | ||||
* @NoSameSiteCookieRequired | * @NoSameSiteCookieRequired | ||||
* | * | ||||
* @return FileDisplayResponse|NotFoundResponse | * @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(); | 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 { | 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); | $response->cacheFor(86400); | ||||
return $response; | return $response; | ||||
} catch (NotFoundException $e) { | } catch (NotFoundException $e) { |
<?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; | |||||
} |
use OCA\Theming\AppInfo\Application; | use OCA\Theming\AppInfo\Application; | ||||
use OCA\Theming\Service\JSDataService; | use OCA\Theming\Service\JSDataService; | ||||
use OCA\Theming\Service\ThemeInjectionService; | |||||
use OCA\Theming\Service\ThemesService; | |||||
use OCP\EventDispatcher\Event; | use OCP\EventDispatcher\Event; | ||||
use OCP\EventDispatcher\IEventListener; | use OCP\EventDispatcher\IEventListener; | ||||
use OCP\IConfig; | use OCP\IConfig; | ||||
class BeforeTemplateRenderedListener implements IEventListener { | 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( | public function __construct( | ||||
IInitialStateService $initialStateService, | IInitialStateService $initialStateService, | ||||
IURLGenerator $urlGenerator, | |||||
IConfig $config, | |||||
IServerContainer $serverContainer | |||||
IServerContainer $serverContainer, | |||||
ThemeInjectionService $themeInjectionService | |||||
) { | ) { | ||||
$this->initialStateService = $initialStateService; | $this->initialStateService = $initialStateService; | ||||
$this->urlGenerator = $urlGenerator; | |||||
$this->config = $config; | |||||
$this->serverContainer = $serverContainer; | $this->serverContainer = $serverContainer; | ||||
$this->themeInjectionService = $themeInjectionService; | |||||
} | } | ||||
public function handle(Event $event): void { | public function handle(Event $event): void { | ||||
return $serverContainer->query(JSDataService::class); | 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 | // Making sure to inject just after core | ||||
\OCP\Util::addScript('theming', 'theming', 'core'); | \OCP\Util::addScript('theming', 'theming', 'core'); |
<?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' | |||||
]); | |||||
} | |||||
} |
<?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(); | |||||
} | |||||
} |
<?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; | |||||
} | |||||
} |
<?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%)', | |||||
]); | |||||
} | |||||
} |
<?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', | |||||
]; | |||||
} | |||||
} |
<?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; | |||||
} | |||||
} |
use OCP\Files\NotFoundException; | use OCP\Files\NotFoundException; | ||||
use OCP\Files\SimpleFS\ISimpleFile; | use OCP\Files\SimpleFS\ISimpleFile; | ||||
use OCP\IConfig; | use OCP\IConfig; | ||||
use Mexitek\PHPColors\Color; | |||||
class Util { | 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. | * Util constructor. | ||||
return $color; | 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 | * Convert RGB to HSL | ||||
* | * | ||||
* | * | ||||
* @return array | * @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 | * @param string $color rgb color value | ||||
* @return float | * @return float | ||||
*/ | */ | ||||
public function calculateLuminance($color) { | |||||
public function calculateLuminance(string $color): float { | |||||
[$red, $green, $blue] = $this->hexToRGB($color); | [$red, $green, $blue] = $this->hexToRGB($color); | ||||
$hsl = $this->toHSL($red, $green, $blue); | $hsl = $this->toHSL($red, $green, $blue); | ||||
return $hsl[2] / 100; | return $hsl[2] / 100; | ||||
* @param string $color rgb color value | * @param string $color rgb color value | ||||
* @return float | * @return float | ||||
*/ | */ | ||||
public function calculateLuma($color) { | |||||
public function calculateLuma(string $color): float { | |||||
[$red, $green, $blue] = $this->hexToRGB($color); | [$red, $green, $blue] = $this->hexToRGB($color); | ||||
return (0.2126 * $red + 0.7152 * $green + 0.0722 * $blue) / 255; | return (0.2126 * $red + 0.7152 * $green + 0.0722 * $blue) / 255; | ||||
} | } | ||||
* @return int[] | * @return int[] | ||||
* @psalm-return array{0: int, 1: int, 2: 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()); | |||||
} | } | ||||
/** | /** |
"ext-pdo": "*", | "ext-pdo": "*", | ||||
"ext-simplexml": "*", | "ext-simplexml": "*", | ||||
"ext-xmlreader": "*", | "ext-xmlreader": "*", | ||||
"ext-zip": "*" | |||||
"ext-zip": "*", | |||||
"mexitek/phpcolors": "^1.0" | |||||
}, | }, | ||||
"require-dev": { | "require-dev": { | ||||
"bamarni/composer-bin-plugin": "^1.4" | "bamarni/composer-bin-plugin": "^1.4" |
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", | ||||
"This file is @generated automatically" | "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": [ | "packages-dev": [ | ||||
{ | { | ||||
"name": "bamarni/composer-bin-plugin", | "name": "bamarni/composer-bin-plugin", |
margin-right: 11px; | margin-right: 11px; | ||||
width: 16px; | width: 16px; | ||||
height: 16px; | height: 16px; | ||||
// Legacy invert if bright background | |||||
filter: var(--background-invert-if-bright); | |||||
} | } | ||||
/* counter can also be inside the link */ | /* counter can also be inside the link */ |
// Make sure most app names don’t ellipsize | // Make sure most app names don’t ellipsize | ||||
letter-spacing: -0.5px; | letter-spacing: -0.5px; | ||||
font-size: 12px; | 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 */ | /* focused app visual feedback */ |
background-size: 20px 20px; | background-size: 20px 20px; | ||||
padding: 14px; | padding: 14px; | ||||
cursor: pointer; | cursor: pointer; | ||||
filter: var(--primary-invert-if-bright); | |||||
&:hover, | &:hover, | ||||
&:focus, | &:focus, |
</div> | </div> | ||||
</a> | </a> | ||||
<ul id="appmenu" <?php if ($_['themingInvertMenu']) { ?>class="inverted"<?php } ?>> | |||||
<ul id="appmenu"> | |||||
<?php foreach ($_['navigation'] as $entry): ?> | <?php foreach ($_['navigation'] as $entry): ?> | ||||
<li data-id="<?php p($entry['id']); ?>" class="hidden" tabindex="-1"> | <li data-id="<?php p($entry['id']); ?>" class="hidden" tabindex="-1"> | ||||
<a href="<?php print_unescaped($entry['href']); ?>" | <a href="<?php print_unescaped($entry['href']); ?>" | ||||
aria-label="<?php p($entry['name']); ?>"> | 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 } ?>> | <svg width="24" height="20" viewBox="0 0 24 20" alt=""<?php if ($entry['unread'] !== 0) { ?> class="has-unread"<?php } ?>> | ||||
<defs> | <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"> | <mask id="hole"> | ||||
<rect width="100%" height="100%" fill="white"/> | <rect width="100%" height="100%" fill="white"/> | ||||
<circle r="4.5" cx="21" cy="3" fill="black"/> | <circle r="4.5" cx="21" cy="3" fill="black"/> | ||||
</mask> | </mask> | ||||
</defs> | </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"/> | <circle class="app-icon-notification" r="3" cx="21" cy="3" fill="red"/> | ||||
</svg> | </svg> | ||||
<div class="unread-counter" aria-hidden="true"><?php p($entry['unread']); ?></div> | <div class="unread-counter" aria-hidden="true"><?php p($entry['unread']); ?></div> |
$this->assign('userAvatarSet', true); | $this->assign('userAvatarSet', true); | ||||
$this->assign('userAvatarVersion', $this->config->getUserValue(\OC_User::getUser(), 'avatar', 'version', 0)); | $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) { | } elseif ($renderAs === TemplateResponse::RENDER_AS_ERROR) { | ||||
parent::__construct('core', 'layout.guest', '', false); | parent::__construct('core', 'layout.guest', '', false); | ||||
$this->assign('bodyid', 'body-login'); | $this->assign('bodyid', 'body-login'); |
// apps that started before the template initialization can load their own scripts/styles | // 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 | // so to make sure this scripts/styles here are loaded first we put all core scripts first | ||||
// check lib/public/Util.php | // 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); | OC_Util::addStyle('server', null, true); | ||||
// include common nextcloud webpack bundle | // include common nextcloud webpack bundle |