diff options
author | Ferdinand Thiessen <opensource@fthiessen.de> | 2024-01-20 01:56:18 +0100 |
---|---|---|
committer | Ferdinand Thiessen <opensource@fthiessen.de> | 2024-05-21 20:36:21 +0200 |
commit | 705335e6a55b9ef0e4ebf0bb83c07efac23e52c8 (patch) | |
tree | f392d4cb7170ba1e4b78b02280cf18e2edf27cca | |
parent | 9d2c3c1164926eadb61d1ec8360644dfeb75a78c (diff) | |
download | nextcloud-server-705335e6a55b9ef0e4ebf0bb83c07efac23e52c8.tar.gz nextcloud-server-705335e6a55b9ef0e4ebf0bb83c07efac23e52c8.zip |
feat(theming): Allow to configure primary color separate from background in admin settings
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
-rw-r--r-- | apps/theming/lib/Capabilities.php | 15 | ||||
-rw-r--r-- | apps/theming/lib/Command/UpdateConfig.php | 9 | ||||
-rw-r--r-- | apps/theming/lib/Controller/ThemingController.php | 15 | ||||
-rw-r--r-- | apps/theming/lib/Controller/UserThemeController.php | 5 | ||||
-rw-r--r-- | apps/theming/lib/ImageManager.php | 1 | ||||
-rw-r--r-- | apps/theming/lib/ResponseDefinitions.php | 1 | ||||
-rw-r--r-- | apps/theming/lib/Service/BackgroundService.php | 93 | ||||
-rw-r--r-- | apps/theming/lib/Service/JSDataService.php | 18 | ||||
-rw-r--r-- | apps/theming/lib/Settings/Admin.php | 3 | ||||
-rw-r--r-- | apps/theming/lib/Themes/CommonThemeTrait.php | 5 | ||||
-rw-r--r-- | apps/theming/src/AdminTheming.vue | 69 | ||||
-rw-r--r-- | apps/theming/src/components/BackgroundSettings.vue | 68 | ||||
-rw-r--r-- | apps/theming/src/components/admin/ColorPickerField.vue | 20 |
13 files changed, 197 insertions, 125 deletions
diff --git a/apps/theming/lib/Capabilities.php b/apps/theming/lib/Capabilities.php index b0c6a71731b..eec2b5187ea 100644 --- a/apps/theming/lib/Capabilities.php +++ b/apps/theming/lib/Capabilities.php @@ -101,8 +101,9 @@ class Capabilities implements IPublicCapability { $colorText = $this->util->invertTextColor($color) ? '#000000' : '#ffffff'; $backgroundLogo = $this->config->getAppValue('theming', 'backgroundMime', ''); - $backgroundPlain = $backgroundLogo === 'backgroundColor' || ($backgroundLogo === '' && $color !== '#0082c9'); - $background = $backgroundPlain ? $color : $this->url->getAbsoluteURL($this->theming->getBackground()); + $backgroundColor = $this->theming->getDefaultColorBackground(); + $backgroundPlain = $backgroundLogo === 'backgroundColor' || ($backgroundLogo === '' && $backgroundColor !== BackgroundService::DEFAULT_COLOR); + $background = $backgroundPlain ? $backgroundColor : $this->url->getAbsoluteURL($this->theming->getBackground()); $user = $this->userSession->getUser(); if ($user instanceof IUser) { @@ -112,10 +113,9 @@ class Capabilities implements IPublicCapability { * @see \OCA\Theming\Themes\CommonThemeTrait::generateUserBackgroundVariables() */ $color = $this->theming->getColorPrimary(); - if ($color === BackgroundService::DEFAULT_COLOR) { - $color = BackgroundService::DEFAULT_ACCESSIBLE_COLOR; - } - $colorText = $this->util->invertTextColor($color) ? '#000000' : '#ffffff'; + $colorText = $this->theming->getTextColorPrimary(); + $backgroundColor = $this->theming->getColorBackground(); + $backgroundText = $this->theming->getTextColorBackground(); $backgroundImage = $this->config->getUserValue($user->getUID(), Application::APP_ID, 'background_image', BackgroundService::BACKGROUND_DEFAULT); if ($backgroundImage === BackgroundService::BACKGROUND_CUSTOM) { @@ -126,7 +126,7 @@ class Capabilities implements IPublicCapability { $background = $this->url->linkTo(Application::APP_ID, "img/background/$backgroundImage"); } elseif ($backgroundImage !== BackgroundService::BACKGROUND_DEFAULT) { $backgroundPlain = true; - $background = $color; + $background = $backgroundColor; } } @@ -142,6 +142,7 @@ class Capabilities implements IPublicCapability { 'color-element-dark' => $this->util->elementColor($color, false), 'logo' => $this->url->getAbsoluteURL($this->theming->getLogo()), 'background' => $background, + 'background-text' => $backgroundText, 'background-plain' => $backgroundPlain, 'background-default' => !$this->util->isBackgroundThemed(), 'logoheader' => $this->url->getAbsoluteURL($this->theming->getLogo()), diff --git a/apps/theming/lib/Command/UpdateConfig.php b/apps/theming/lib/Command/UpdateConfig.php index de180db6ce9..88f029a72bf 100644 --- a/apps/theming/lib/Command/UpdateConfig.php +++ b/apps/theming/lib/Command/UpdateConfig.php @@ -33,7 +33,7 @@ use Symfony\Component\Console\Output\OutputInterface; class UpdateConfig extends Command { public const SUPPORTED_KEYS = [ - 'name', 'url', 'imprintUrl', 'privacyUrl', 'slogan', 'color', 'disable-user-theming' + 'name', 'url', 'imprintUrl', 'privacyUrl', 'slogan', 'color', 'primary_color', 'disable-user-theming' ]; private $themingDefaults; @@ -128,8 +128,13 @@ class UpdateConfig extends Command { $value = $this->imageManager->updateImage($key, $value); $key = $key . 'Mime'; } + + if ($key === 'color') { + $output->writeln('<warning>Using "color" is depreacted, use "primary_color" instead'); + $key = 'primary_color'; + } - if ($key === 'color' && !preg_match('/^\#([0-9a-f]{3}|[0-9a-f]{6})$/i', $value)) { + if ($key === 'primary_color' && !preg_match('/^\#([0-9a-f]{3}|[0-9a-f]{6})$/i', $value)) { $output->writeln('<error>The given color is invalid: ' . $value . '</error>'); return 1; } diff --git a/apps/theming/lib/Controller/ThemingController.php b/apps/theming/lib/Controller/ThemingController.php index 91012d1e37a..024e5433808 100644 --- a/apps/theming/lib/Controller/ThemingController.php +++ b/apps/theming/lib/Controller/ThemingController.php @@ -50,13 +50,11 @@ use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\FileDisplayResponse; use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\NotFoundResponse; -use OCP\Files\IAppData; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; use OCP\IConfig; use OCP\IL10N; use OCP\IRequest; -use OCP\ITempManager; use OCP\IURLGenerator; use ScssPhp\ScssPhp\Compiler; @@ -73,8 +71,6 @@ class ThemingController extends Controller { private ThemingDefaults $themingDefaults; private IL10N $l10n; private IConfig $config; - private ITempManager $tempManager; - private IAppData $appData; private IURLGenerator $urlGenerator; private IAppManager $appManager; private ImageManager $imageManager; @@ -86,8 +82,6 @@ class ThemingController extends Controller { IConfig $config, ThemingDefaults $themingDefaults, IL10N $l, - ITempManager $tempManager, - IAppData $appData, IURLGenerator $urlGenerator, IAppManager $appManager, ImageManager $imageManager, @@ -98,8 +92,6 @@ class ThemingController extends Controller { $this->themingDefaults = $themingDefaults; $this->l10n = $l; $this->config = $config; - $this->tempManager = $tempManager; - $this->appData = $appData; $this->urlGenerator = $urlGenerator; $this->appManager = $appManager; $this->imageManager = $imageManager; @@ -151,7 +143,12 @@ class ThemingController extends Controller { $error = $this->l10n->t('The given slogan is too long'); } break; - case 'color': + case 'primary_color': + if (!preg_match('/^\#([0-9a-f]{3}|[0-9a-f]{6})$/i', $value)) { + $error = $this->l10n->t('The given color is invalid'); + } + break; + case 'background_color': if (!preg_match('/^\#([0-9a-f]{3}|[0-9a-f]{6})$/i', $value)) { $error = $this->l10n->t('The given color is invalid'); } diff --git a/apps/theming/lib/Controller/UserThemeController.php b/apps/theming/lib/Controller/UserThemeController.php index 75fa414b170..309b9c04482 100644 --- a/apps/theming/lib/Controller/UserThemeController.php +++ b/apps/theming/lib/Controller/UserThemeController.php @@ -58,7 +58,6 @@ class UserThemeController extends OCSController { protected ?string $userId = null; private IConfig $config; - private IUserSession $userSession; private ThemesService $themesService; private ThemingDefaults $themingDefaults; private BackgroundService $backgroundService; @@ -72,7 +71,6 @@ class UserThemeController extends OCSController { BackgroundService $backgroundService) { parent::__construct($appName, $request); $this->config = $config; - $this->userSession = $userSession; $this->themesService = $themesService; $this->themingDefaults = $themingDefaults; $this->backgroundService = $backgroundService; @@ -186,7 +184,8 @@ class UserThemeController extends OCSController { $this->backgroundService->deleteBackgroundImage(); return new JSONResponse([ 'backgroundImage' => null, - 'backgroundColor' => $this->themingDefaults->getColorPrimary(), + 'backgroundColor' => $this->themingDefaults->getColorBackground(), + 'primaryColor' => $this->themingDefaults->getColorPrimary(), 'version' => $currentVersion, ]); } diff --git a/apps/theming/lib/ImageManager.php b/apps/theming/lib/ImageManager.php index 994e3f35118..69ab14731e8 100644 --- a/apps/theming/lib/ImageManager.php +++ b/apps/theming/lib/ImageManager.php @@ -4,6 +4,7 @@ * * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Daniel Kesselberg <mail@danielkesselberg.de> + * @author Ferdinand Thiessen <opensource@fthiessen.de> * @author Gary Kim <gary@garykim.dev> * @author Jacob Neplokh <me@jacobneplokh.com> * @author John Molakvoæ <skjnldsv@protonmail.com> diff --git a/apps/theming/lib/ResponseDefinitions.php b/apps/theming/lib/ResponseDefinitions.php index f1b0d09790a..29a1c029fdf 100644 --- a/apps/theming/lib/ResponseDefinitions.php +++ b/apps/theming/lib/ResponseDefinitions.php @@ -30,6 +30,7 @@ namespace OCA\Theming; * @psalm-type ThemingBackground = array{ * backgroundImage: ?string, * backgroundColor: string, + * primaryColor: string, * version: int, * } */ diff --git a/apps/theming/lib/Service/BackgroundService.php b/apps/theming/lib/Service/BackgroundService.php index 375511b37d3..edbcda47c05 100644 --- a/apps/theming/lib/Service/BackgroundService.php +++ b/apps/theming/lib/Service/BackgroundService.php @@ -8,6 +8,7 @@ declare(strict_types=1); * @author Jan C. Borchardt <hey@jancborchardt.net> * @author Julius Härtl <jus@bitgrid.net> * @author Christopher Ng <chrng8@gmail.com> + * @author Ferdinand Thiessen <opensource@fthiessen.de> * * @license GNU AGPL version 3 or any later version * @@ -30,7 +31,6 @@ namespace OCA\Theming\Service; use InvalidArgumentException; use OC\User\NoUserException; use OCA\Theming\AppInfo\Application; -use OCA\Theming\ThemingDefaults; use OCP\Files\File; use OCP\Files\IAppData; use OCP\Files\IRootFolder; @@ -43,160 +43,177 @@ use OCP\Lock\LockedException; use OCP\PreConditionNotMetException; class BackgroundService { - // true when the background is bright and need dark icons - public const THEMING_MODE_DARK = 'dark'; public const DEFAULT_COLOR = '#0082c9'; public const DEFAULT_ACCESSIBLE_COLOR = '#00679e'; + /** + * One of our shipped background images is used + */ public const BACKGROUND_SHIPPED = 'shipped'; + /** + * A custom background image is used + */ public const BACKGROUND_CUSTOM = 'custom'; + /** + * The default background image is used + */ public const BACKGROUND_DEFAULT = 'default'; - public const BACKGROUND_DISABLED = 'disabled'; + /** + * Just a background color is used + */ + public const BACKGROUND_COLOR = 'color'; public const DEFAULT_BACKGROUND_IMAGE = 'kamil-porembinski-clouds.jpg'; + + /** + * 'attribution': Name, artist and license + * 'description': Alternative text + * 'attribution_url': URL for attribution + * 'background_color': Cached mean color of the top part to calculate app menu colors and use as fallback + * 'primary_color': Recommended primary color for this theme / image + */ public const SHIPPED_BACKGROUNDS = [ 'hannah-maclean-soft-floral.jpg' => [ 'attribution' => 'Soft floral (Hannah MacLean, CC0)', 'description' => 'Abstract background picture in yellow and white color whith a flower on it', 'attribution_url' => 'https://stocksnap.io/photo/soft-floral-XOYWCCW5PA', - 'theming' => self::THEMING_MODE_DARK, - 'primary_color' => '#D8A06C', + 'background_color' => '#e4d2c1', + 'primary_color' => '#9f652f', ], 'ted-moravec-morning-fog.jpg' => [ 'attribution' => 'Morning fog (Ted Moravec, Public Domain)', 'description' => 'Background picture of a forest shrouded in fog', 'attribution_url' => 'https://flickr.com/photos/tmoravec/52392410261', - 'theming' => self::THEMING_MODE_DARK, - 'primary_color' => '#38A084', + 'background_color' => '#f6f7f6', + 'primary_color' => '#114c3b', ], 'stefanus-martanto-setyo-husodo-underwater-ocean.jpg' => [ 'attribution' => 'Underwater ocean (Stefanus Martanto Setyo Husodo, CC0)', 'description' => 'Background picture of an underwater ocean', 'attribution_url' => 'https://stocksnap.io/photo/underwater-ocean-TJA9LBH4WS', + 'background_color' => '#003351', 'primary_color' => '#04577e', ], 'zoltan-voros-rhythm-and-blues.jpg' => [ 'attribution' => 'Rhythm and blues (Zoltán Vörös, CC BY)', 'description' => 'Abstract background picture of sand dunes during night', 'attribution_url' => 'https://flickr.com/photos/v923z/51634409289/', + 'background_color' => '#1c2437', 'primary_color' => '#1c243c', ], 'anatoly-mikhaltsov-butterfly-wing-scale.jpg' => [ 'attribution' => 'Butterfly wing scale (Anatoly Mikhaltsov, CC BY-SA)', 'description' => 'Background picture of a red-ish butterfly wing under microscope', 'attribution_url' => 'https://commons.wikimedia.org/wiki/File:%D0%A7%D0%B5%D1%88%D1%83%D0%B9%D0%BA%D0%B8_%D0%BA%D1%80%D1%8B%D0%BB%D0%B0_%D0%B1%D0%B0%D0%B1%D0%BE%D1%87%D0%BA%D0%B8.jpg', + 'background_color' => '#652e11', 'primary_color' => '#a53c17', ], 'bernie-cetonia-aurata-take-off-composition.jpg' => [ 'attribution' => 'Cetonia aurata take off composition (Bernie, Public Domain)', 'description' => 'Montage of a cetonia aurata bug that takes off with white background', 'attribution_url' => 'https://commons.wikimedia.org/wiki/File:Cetonia_aurata_take_off_composition_05172009.jpg', - 'theming' => self::THEMING_MODE_DARK, - 'primary_color' => '#869171', + 'background_color' => '#dee0d3', + 'primary_color' => '#56633d', ], 'dejan-krsmanovic-ribbed-red-metal.jpg' => [ 'attribution' => 'Ribbed red metal (Dejan Krsmanovic, CC BY)', 'description' => 'Abstract background picture of red ribbed metal with two horizontal white elements on top of it', 'attribution_url' => 'https://www.flickr.com/photos/dejankrsmanovic/42971456774/', + 'background_color' => '#9b171c', 'primary_color' => '#9c4236', ], 'eduardo-neves-pedra-azul.jpg' => [ 'attribution' => 'Pedra azul milky way (Eduardo Neves, CC BY-SA)', 'description' => 'Background picture of the milky way during night with a mountain in front of it', 'attribution_url' => 'https://commons.wikimedia.org/wiki/File:Pedra_Azul_Milky_Way.jpg', + 'background_color' => '#1d242d', 'primary_color' => '#4f6071', ], 'european-space-agency-barents-bloom.jpg' => [ 'attribution' => 'Barents bloom (European Space Agency, CC BY-SA)', 'description' => 'Abstract background picture of blooming barents in blue and green colors', 'attribution_url' => 'https://www.esa.int/ESA_Multimedia/Images/2016/08/Barents_bloom', + 'background_color' => '#1c383d', 'primary_color' => '#396475', ], 'hannes-fritz-flippity-floppity.jpg' => [ 'attribution' => 'Flippity floppity (Hannes Fritz, CC BY-SA)', 'description' => 'Abstract background picture of many pairs of flip flops hanging on a wall in multiple colors', 'attribution_url' => 'http://hannes.photos/flippity-floppity', + 'background_color' => '#5b2d53', 'primary_color' => '#98415a', ], 'hannes-fritz-roulette.jpg' => [ 'attribution' => 'Roulette (Hannes Fritz, CC BY-SA)', 'description' => 'Background picture of a rotating giant wheel during night', 'attribution_url' => 'http://hannes.photos/roulette', + 'background_color' => '#000000', 'primary_color' => '#845334', ], 'hannes-fritz-sea-spray.jpg' => [ 'attribution' => 'Sea spray (Hannes Fritz, CC BY-SA)', 'description' => 'Background picture of a stone coast with fog and sea behind it', 'attribution_url' => 'http://hannes.photos/sea-spray', + 'background_color' => '#333f47', 'primary_color' => '#4f6071', ], 'kamil-porembinski-clouds.jpg' => [ 'attribution' => 'Clouds (Kamil Porembiński, CC BY-SA)', 'description' => 'Background picture of white clouds on in front of a blue sky', 'attribution_url' => 'https://www.flickr.com/photos/paszczak000/8715851521/', + 'background_color' => '#00679e', 'primary_color' => self::DEFAULT_COLOR, ], 'bernard-spragg-new-zealand-fern.jpg' => [ 'attribution' => 'New zealand fern (Bernard Spragg, CC0)', 'description' => 'Abstract background picture of fern leafes', 'attribution_url' => 'https://commons.wikimedia.org/wiki/File:NZ_Fern.(Blechnum_chambersii)_(11263534936).jpg', + 'background_color' => '#0c3c03', 'primary_color' => '#316b26', ], 'rawpixel-pink-tapioca-bubbles.jpg' => [ 'attribution' => 'Pink tapioca bubbles (Rawpixel, CC BY)', 'description' => 'Abstract background picture of pink tapioca bubbles', 'attribution_url' => 'https://www.flickr.com/photos/byrawpixel/27665140298/in/photostream/', - 'theming' => self::THEMING_MODE_DARK, - 'primary_color' => '#b17ab4', + 'background_color' => '#c56e95', + 'primary_color' => '#7b4e7e', ], 'nasa-waxing-crescent-moon.jpg' => [ 'attribution' => 'Waxing crescent moon (NASA, Public Domain)', 'description' => 'Background picture of glowing earth in foreground and moon in the background', 'attribution_url' => 'https://www.nasa.gov/image-feature/a-waxing-crescent-moon', + 'background_color' => '#000002', 'primary_color' => '#005ac1', ], 'tommy-chau-already.jpg' => [ 'attribution' => 'Cityscape (Tommy Chau, CC BY)', 'description' => 'Background picture of a skyscraper city during night', 'attribution_url' => 'https://www.flickr.com/photos/90975693@N05/16910999368', + 'background_color' => '#35229f', 'primary_color' => '#6a2af4', ], 'tommy-chau-lion-rock-hill.jpg' => [ 'attribution' => 'Lion rock hill (Tommy Chau, CC BY)', 'description' => 'Background picture of mountains during sunset or sunrise', 'attribution_url' => 'https://www.flickr.com/photos/90975693@N05/17136440246', - 'theming' => self::THEMING_MODE_DARK, - 'primary_color' => '#c074a9', + 'background_color' => '#cb92b7', + 'primary_color' => '#7f4f70', ], 'lali-masriera-yellow-bricks.jpg' => [ 'attribution' => 'Yellow bricks (Lali Masriera, CC BY)', 'description' => 'Background picture of yellow bricks with some yellow tubes', 'attribution_url' => 'https://www.flickr.com/photos/visualpanic/3982464447', - 'theming' => self::THEMING_MODE_DARK, - 'primary_color' => '#bc8210', + 'background_color' => '#c78a19', + 'primary_color' => '#7f5700', ], ]; - private IRootFolder $rootFolder; - private IAppData $appData; - private IConfig $config; - private string $userId; - private ThemingDefaults $themingDefaults; - - public function __construct(IRootFolder $rootFolder, - IAppData $appData, - IConfig $config, - ?string $userId, - ThemingDefaults $themingDefaults) { - if ($userId === null) { - return; - } - - $this->rootFolder = $rootFolder; - $this->config = $config; - $this->userId = $userId; - $this->appData = $appData; - $this->themingDefaults = $themingDefaults; + public function __construct( + private IRootFolder $rootFolder, + private IAppData $appData, + private IConfig $config, + private ?string $userId, + ) { } public function setDefaultBackground(): void { @@ -236,6 +253,7 @@ class BackgroundService { if (!array_key_exists($fileName, self::SHIPPED_BACKGROUNDS)) { throw new InvalidArgumentException('The given file name is invalid'); } + $this->setColorBackground(self::SHIPPED_BACKGROUNDS[$fileName]['background_color']); $this->config->setUserValue($this->userId, Application::APP_ID, 'background_image', $fileName); $this->config->setUserValue($this->userId, Application::APP_ID, 'primary_color', self::SHIPPED_BACKGROUNDS[$fileName]['primary_color']); } @@ -248,10 +266,11 @@ class BackgroundService { throw new InvalidArgumentException('The given color is invalid'); } $this->config->setUserValue($this->userId, Application::APP_ID, 'background_color', $color); + $this->config->setUserValue($this->userId, Application::APP_ID, 'background_image', self::BACKGROUND_COLOR); } public function deleteBackgroundImage(): void { - $this->config->setUserValue($this->userId, Application::APP_ID, 'background_image', self::BACKGROUND_DISABLED); + $this->config->setUserValue($this->userId, Application::APP_ID, 'background_image', self::BACKGROUND_COLOR); } public function getBackground(): ?ISimpleFile { diff --git a/apps/theming/lib/Service/JSDataService.php b/apps/theming/lib/Service/JSDataService.php index 5dc7b3fd632..65f91f9dda2 100644 --- a/apps/theming/lib/Service/JSDataService.php +++ b/apps/theming/lib/Service/JSDataService.php @@ -28,23 +28,16 @@ namespace OCA\Theming\Service; use OCA\Theming\ThemingDefaults; use OCA\Theming\Util; -use OCP\IConfig; class JSDataService implements \JsonSerializable { - private ThemingDefaults $themingDefaults; - private Util $util; - private IConfig $appConfig; - private ThemesService $themesService; public function __construct( - ThemingDefaults $themingDefaults, - Util $util, - IConfig $appConfig, - ThemesService $themesService + private ThemingDefaults $themingDefaults, + private Util $util, + private ThemesService $themesService, ) { $this->themingDefaults = $themingDefaults; $this->util = $util; - $this->appConfig = $appConfig; $this->themesService = $themesService; } @@ -53,13 +46,16 @@ class JSDataService implements \JsonSerializable { 'name' => $this->themingDefaults->getName(), 'url' => $this->themingDefaults->getBaseUrl(), 'slogan' => $this->themingDefaults->getSlogan(), - 'color' => $this->themingDefaults->getColorPrimary(), + 'color' => $this->themingDefaults->getColorPrimary(), // deprecated use primaryColor + 'primaryColor' => $this->themingDefaults->getColorPrimary(), + 'backgroundColor' => $this->themingDefaults->getColorBackground(), 'defaultColor' => $this->themingDefaults->getDefaultColorPrimary(), 'imprintUrl' => $this->themingDefaults->getImprintUrl(), 'privacyUrl' => $this->themingDefaults->getPrivacyUrl(), 'inverted' => $this->util->invertTextColor($this->themingDefaults->getColorPrimary()), 'cacheBuster' => $this->util->getCacheBuster(), 'enabledThemes' => $this->themesService->getEnabledThemes(), + '' => 'color is deprecated since Nextcloud 29, use primaryColor instead' ]; } } diff --git a/apps/theming/lib/Settings/Admin.php b/apps/theming/lib/Settings/Admin.php index dc5d9a2a6a4..8a34432a073 100644 --- a/apps/theming/lib/Settings/Admin.php +++ b/apps/theming/lib/Settings/Admin.php @@ -75,7 +75,8 @@ class Admin implements IDelegatedSettings { 'name' => $this->themingDefaults->getEntity(), 'url' => $this->themingDefaults->getBaseUrl(), 'slogan' => $this->themingDefaults->getSlogan(), - 'color' => $this->themingDefaults->getDefaultColorPrimary(), + 'primaryColor' => $this->themingDefaults->getDefaultColorPrimary(), + 'backgroundColor' => $this->themingDefaults->getDefaultColorBackground(), 'logoMime' => $this->config->getAppValue(Application::APP_ID, 'logoMime', ''), 'allowedMimeTypes' => $allowedMimeTypes, 'backgroundMime' => $this->config->getAppValue(Application::APP_ID, 'backgroundMime', ''), diff --git a/apps/theming/lib/Themes/CommonThemeTrait.php b/apps/theming/lib/Themes/CommonThemeTrait.php index 6d74a7bcdfc..efeb3f0d97c 100644 --- a/apps/theming/lib/Themes/CommonThemeTrait.php +++ b/apps/theming/lib/Themes/CommonThemeTrait.php @@ -6,6 +6,7 @@ declare(strict_types=1); * * @author Joas Schilling <coding@schilljs.com> * @author John Molakvoæ <skjnldsv@protonmail.com> + * @author Ferdinand Thiessen <opensource@fthiessen.de> * * @license GNU AGPL version 3 or any later version * @@ -143,8 +144,8 @@ trait CommonThemeTrait { '--background-image-invert-if-bright' => $isBackgroundBright ? 'invert(100%)' : 'no', ]; - // The user removed the background - if ($backgroundImage === BackgroundService::BACKGROUND_DISABLED) { + // Only use a background color without an image + if ($backgroundImage === BackgroundService::BACKGROUND_COLOR) { // Might be defined already by admin theming, needs to be overridden $variables['--image-background'] = 'none'; } diff --git a/apps/theming/src/AdminTheming.vue b/apps/theming/src/AdminTheming.vue index 2face1629ef..f0831d8e944 100644 --- a/apps/theming/src/AdminTheming.vue +++ b/apps/theming/src/AdminTheming.vue @@ -47,10 +47,21 @@ @update:theming="$emit('update:theming')" /> <!-- Primary color picker --> - <ColorPickerField :name="colorPickerField.name" - :default-value="colorPickerField.defaultValue" - :display-name="colorPickerField.displayName" - :value.sync="colorPickerField.value" + <ColorPickerField :name="primaryColorPickerField.name" + :description="primaryColorPickerField.description" + :default-value="primaryColorPickerField.defaultValue" + :display-name="primaryColorPickerField.displayName" + :value.sync="primaryColorPickerField.value" + data-admin-theming-setting-primary-color + @update:theming="$emit('update:theming')" /> + + <!-- Background color picker --> + <ColorPickerField :name="backgroundColorPickerField.name" + :description="backgroundColorPickerField.description" + :default-value="defaultBackground" + :display-name="backgroundColorPickerField.displayName" + :value.sync="backgroundColorPickerField.value" + data-admin-theming-setting-primary-color @update:theming="$emit('update:theming')" /> <!-- Default background picker --> @@ -122,8 +133,8 @@ import AppMenuSection from './components/admin/AppMenuSection.vue' const { backgroundMime, + backgroundColor, canThemeIcons, - color, docUrl, docUrlIcons, faviconMime, @@ -133,6 +144,7 @@ const { logoMime, name, notThemableErrorMessage, + primaryColor, privacyPolicyUrl, slogan, url, @@ -170,11 +182,19 @@ const textFields = [ }, ] -const colorPickerField = { - name: 'color', - value: color, +const primaryColorPickerField = { + name: 'primary_color', + value: primaryColor, defaultValue: '#0082c9', - displayName: t('theming', 'Color'), + displayName: t('theming', 'Primary color'), + description: t('theming', 'The primary color is used for highlighting elements like important buttons. It might get slightly adjusted depending on the current color schema.'), +} + +const backgroundColorPickerField = { + name: 'background_color', + value: backgroundColor, + displayName: t('theming', 'Background color'), + description: t('theming', 'Instead of a background image you can also configure a plain background color. If you use a background image changing this color will influence the color of the app menu icons.'), } const fileInputFields = [ @@ -267,7 +287,8 @@ export default { data() { return { textFields, - colorPickerField, + backgroundColorPickerField, + primaryColorPickerField, fileInputFields, advancedTextFields, advancedFileInputFields, @@ -279,8 +300,36 @@ export default { docUrlIcons, isThemable, notThemableErrorMessage, + + defaultBackground: this.calculateDefaultBackground(), } }, + + watch: { + backgroundColorPickerField: { + deep: true, + handler() { + this.defaultBackground = this.calculateDefaultBackground() + }, + }, + }, + + methods: { + calculateDefaultBackground() { + const toHex = (num) => `00${num.toString(16)}`.slice(-2) + const style = window.getComputedStyle(document.body).backgroundImage + const match = style.match(/url\("(http.+)"\)/) + if (!match) { + return '#0082c9' + } + const context = document.createElement('canvas').getContext('2d') + const img = new Image() + img.src = match[1] + context.imageSmoothingEnabled = true + context.drawImage(img, 0, 0, 1, 1) + return '#' + [...context.getImageData(0, 0, 1, 1).data.slice(0, 3)].map(toHex).join('') + }, + }, } </script> diff --git a/apps/theming/src/components/BackgroundSettings.vue b/apps/theming/src/components/BackgroundSettings.vue index deb0a93a51a..166fdbc5f4b 100644 --- a/apps/theming/src/components/BackgroundSettings.vue +++ b/apps/theming/src/components/BackgroundSettings.vue @@ -32,15 +32,32 @@ 'background background__filepicker': true, 'background--active': backgroundImage === 'custom' }" - :data-color-bright="invertTextColor(Theming.color)" data-user-theming-background-custom tabindex="0" @click="pickFile"> {{ t('theming', 'Custom background') }} - <ImageEdit v-if="backgroundImage !== 'custom'" :size="26" /> + <ImageEdit v-if="backgroundImage !== 'custom'" :size="20" /> <Check :size="44" /> </button> + <!-- Custom color picker --> + <NcColorPicker v-model="Theming.backgroundColor" @input="debouncePickColor"> + <button :class="{ + 'icon-loading': loading === 'color', + 'background background__color': true, + 'background--active': backgroundImage === 'color' + }" + :data-color="Theming.backgroundColor" + :data-color-bright="invertTextColor(Theming.backgroundColor)" + :style="{ backgroundColor: Theming.backgroundColor, '--border-color': Theming.backgroundColor}" + data-user-theming-background-color + tabindex="0"> + {{ t('theming', 'Plain background') /* TRANSLATORS: Background using a single color */ }} + <ColorPalette v-if="backgroundImage !== 'color'" :size="20" /> + <Check :size="44" /> + </button> + </NcColorPicker> + <!-- Default background --> <button :aria-pressed="backgroundImage === 'default'" :class="{ @@ -57,31 +74,6 @@ <Check :size="44" /> </button> - <!-- Custom color picker --> - <div class="background-color" - data-user-theming-background-color> - <NcColorPicker v-model="Theming.color" - @input="debouncePickColor"> - <NcButton type="ternary"> - {{ t('theming', 'Change color') }} - </NcButton> - </NcColorPicker> - </div> - - <!-- Remove background --> - <button :aria-pressed="isBackgroundDisabled" - :class="{ - 'background background__delete': true, - 'background--active': isBackgroundDisabled - }" - data-user-theming-background-clear - tabindex="0" - @click="removeBackground"> - {{ t('theming', 'No background') }} - <Close v-if="!isBackgroundDisabled" :size="32" /> - <Check :size="44" /> - </button> - <!-- Background set selection --> <button v-for="shippedBackground in shippedBackgrounds" :key="shippedBackground.name" @@ -93,7 +85,6 @@ 'icon-loading': loading === shippedBackground.name, 'background--active': backgroundImage === shippedBackground.name }" - :data-color-bright="shippedBackground.details.theming === 'dark'" :data-user-theming-background-shipped="shippedBackground.name" :style="{ backgroundImage: 'url(' + shippedBackground.preview + ')', '--border-color': shippedBackground.details.primary_color }" tabindex="0" @@ -112,12 +103,11 @@ import { Palette } from 'node-vibrant/lib/color.js' import axios from '@nextcloud/axios' import debounce from 'debounce' import NcColorPicker from '@nextcloud/vue/dist/Components/NcColorPicker.js' -import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' import Vibrant from 'node-vibrant' import Check from 'vue-material-design-icons/Check.vue' -import Close from 'vue-material-design-icons/Close.vue' import ImageEdit from 'vue-material-design-icons/ImageEdit.vue' +import ColorPalette from 'vue-material-design-icons/Palette.vue' const backgroundImage = loadState('theming', 'backgroundImage') const shippedBackgroundList = loadState('theming', 'shippedBackgrounds') @@ -126,14 +116,14 @@ const defaultShippedBackground = loadState('theming', 'defaultShippedBackground' const prefixWithBaseUrl = (url) => generateFilePath('theming', '', 'img/background/') + url +console.warn(loadState('theming', 'data', {})) export default { name: 'BackgroundSettings', components: { Check, - Close, + ColorPalette, ImageEdit, - NcButton, NcColorPicker, }, @@ -175,11 +165,6 @@ export default { isGlobalBackgroundDeleted() { return themingDefaultBackground === 'backgroundColor' }, - - isBackgroundDisabled() { - return this.backgroundImage === 'disabled' - || !this.backgroundImage - }, }, methods: { @@ -226,7 +211,7 @@ export default { async update(data) { // Update state this.backgroundImage = data.backgroundImage - this.Theming.color = data.backgroundColor + this.Theming.backgroundColor = data.backgroundColor // Notify parent and reload style this.$emit('update:background') @@ -359,14 +344,19 @@ export default { height: 96px; margin: 8px; text-align: center; + word-wrap: break-word; + hyphens: auto; border: 2px solid var(--color-main-background); border-radius: var(--border-radius-large); background-position: center center; background-size: cover; &__filepicker { + background-color: var(--color-main-text); + background-color: var(--color-background-dark); + &.background--active { - color: white; + color: var(--color-background-plain-text); background-image: var(--image-background); } } diff --git a/apps/theming/src/components/admin/ColorPickerField.vue b/apps/theming/src/components/admin/ColorPickerField.vue index fad40408b37..1cac5418d7a 100644 --- a/apps/theming/src/components/admin/ColorPickerField.vue +++ b/apps/theming/src/components/admin/ColorPickerField.vue @@ -26,14 +26,16 @@ <div class="field__row"> <NcColorPicker :value.sync="localValue" :advanced-fields="true" - data-admin-theming-setting-primary-color-picker @update:value="debounceSave"> - <NcButton type="secondary" - :id="id"> + <NcButton :id="id" + class="field__button" + type="primary" + :aria-label="t('theming', 'Select a custom color')" + data-admin-theming-setting-primary-color-picker> <template #icon> <Palette :size="20" /> </template> - {{ t('theming', 'Change color') }} + {{ value }} </NcButton> </NcColorPicker> <div class="field__color-preview" data-admin-theming-setting-primary-color /> @@ -47,6 +49,9 @@ </template> </NcButton> </div> + <div v-if="description" class="description"> + {{ description }} + </div> <NcNoteCard v-if="errorMessage" type="error" @@ -86,6 +91,10 @@ export default { type: String, required: true, }, + description: { + type: String, + default: '', + }, value: { type: String, required: true, @@ -110,6 +119,9 @@ export default { <style lang="scss" scoped> @import './shared/field.scss'; +.description { + color: var(--color-text-maxcontrast); +} .field { &__color-preview { |