feat(theming): Allow to configure primary color separate from background in admin settings

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
pull/42977/head
Ferdinand Thiessen 10 months ago
parent 9d2c3c1164
commit 705335e6a5
No known key found for this signature in database
GPG Key ID: 45FAE7268762B400
  1. 15
      apps/theming/lib/Capabilities.php
  2. 9
      apps/theming/lib/Command/UpdateConfig.php
  3. 15
      apps/theming/lib/Controller/ThemingController.php
  4. 5
      apps/theming/lib/Controller/UserThemeController.php
  5. 1
      apps/theming/lib/ImageManager.php
  6. 1
      apps/theming/lib/ResponseDefinitions.php
  7. 93
      apps/theming/lib/Service/BackgroundService.php
  8. 18
      apps/theming/lib/Service/JSDataService.php
  9. 3
      apps/theming/lib/Settings/Admin.php
  10. 5
      apps/theming/lib/Themes/CommonThemeTrait.php
  11. 69
      apps/theming/src/AdminTheming.vue
  12. 68
      apps/theming/src/components/BackgroundSettings.vue
  13. 20
      apps/theming/src/components/admin/ColorPickerField.vue

@ -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()),

@ -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;
}

@ -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');
}

@ -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,
]);
}

@ -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>

@ -30,6 +30,7 @@ namespace OCA\Theming;
* @psalm-type ThemingBackground = array{
* backgroundImage: ?string,
* backgroundColor: string,
* primaryColor: string,
* version: int,
* }
*/

@ -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 {

@ -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'
];
}
}

@ -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', ''),

@ -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';
}

@ -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>

@ -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);
}
}

@ -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 {

Loading…
Cancel
Save