summaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorJohn Molakvoæ <skjnldsv@protonmail.com>2022-10-20 16:03:19 +0200
committerJohn Molakvoæ <skjnldsv@protonmail.com>2022-11-29 11:23:05 +0100
commit064fa10ecfe4725398895a21ab8bafd171e2eadd (patch)
tree5f2d8124eb131a65eac207edee560c49ea7835f3 /apps
parentcedae7c6d74e11c8aaa59b09a38db04dbebc818d (diff)
downloadnextcloud-server-064fa10ecfe4725398895a21ab8bafd171e2eadd.tar.gz
nextcloud-server-064fa10ecfe4725398895a21ab8bafd171e2eadd.zip
Extract colour from custom background
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
Diffstat (limited to 'apps')
-rw-r--r--apps/theming/css/default.css5
-rw-r--r--apps/theming/css/settings-admin.css148
-rw-r--r--apps/theming/css/settings-admin.scss168
-rw-r--r--apps/theming/lib/Controller/UserThemeController.php15
-rw-r--r--apps/theming/lib/ImageManager.php2
-rw-r--r--apps/theming/lib/Service/BackgroundService.php11
-rw-r--r--apps/theming/lib/Themes/CommonThemeTrait.php5
-rw-r--r--apps/theming/lib/ThemingDefaults.php2
-rw-r--r--apps/theming/src/AdminTheming.vue11
-rw-r--r--apps/theming/src/components/BackgroundSettings.vue114
-rw-r--r--apps/theming/tests/Controller/ThemingControllerTest.php2
-rw-r--r--apps/theming/tests/Themes/DefaultThemeTest.php7
-rw-r--r--apps/theming/tests/ThemingDefaultsTest.php28
13 files changed, 141 insertions, 377 deletions
diff --git a/apps/theming/css/default.css b/apps/theming/css/default.css
index d028364ec7b..7b62656c4f9 100644
--- a/apps/theming/css/default.css
+++ b/apps/theming/css/default.css
@@ -54,9 +54,6 @@
--background-invert-if-dark: no;
--background-invert-if-bright: invert(100%);
--background-image-invert-if-bright: no;
- --image-background: url('/core/img/app-background.jpg');
- --image-background-default: url('/core/img/app-background.jpg');
- --color-background-plain: #0082c9;
--primary-invert-if-bright: no;
--color-primary: #006aa3;
--color-primary-default: #0082c9;
@@ -75,4 +72,6 @@
--color-primary-element-light-hover: #dbe5ea;
--color-primary-element-text-dark: #ededed;
--gradient-primary-background: linear-gradient(40deg, var(--color-primary) 0%, var(--color-primary-hover) 100%);
+ --image-background-default: url('/apps/theming/img/background/kamil-porembinski-clouds.jpg');
+ --color-background-plain: #0082c9;
}
diff --git a/apps/theming/css/settings-admin.css b/apps/theming/css/settings-admin.css
deleted file mode 100644
index 1979387c5dd..00000000000
--- a/apps/theming/css/settings-admin.css
+++ /dev/null
@@ -1,148 +0,0 @@
-#theming input {
- width: 230px;
-}
-#theming input:focus,
-#theming input:active {
- padding-right: 30px;
-}
-#theming .fileupload {
- display: none;
-}
-#theming div > label {
- position: relative;
-}
-#theming .theme-undo {
- position: absolute;
- top: -7px;
- right: 4px;
- cursor: pointer;
- opacity: 0.3;
- padding: 7px;
- vertical-align: top;
- display: inline-block;
- visibility: hidden;
- height: 32px;
- width: 32px;
-}
-#theming form.uploadButton {
- width: 411px;
- display: flex;
- align-items: center;
-}
-#theming form .theme-undo,
-#theming .theme-remove-bg {
- cursor: pointer;
- opacity: 0.3;
- padding: 7px;
- vertical-align: top;
- display: inline-block;
- float: right;
- position: relative;
- top: 4px;
- right: 0px;
- visibility: visible;
- height: 32px;
- width: 32px;
- margin-left: auto;
-}
-#theming form .theme-undo:not([style*="display:"]) ~ .theme-remove-bg {
- margin-left: 0;
-}
-#theming input[type=text]:hover + .theme-undo,
-#theming input[type=text] + .theme-undo:hover,
-#theming input[type=text]:focus + .theme-undo,
-#theming input[type=text]:active + .theme-undo,
-#theming input[type=url]:hover + .theme-undo,
-#theming input[type=url] + .theme-undo:hover,
-#theming input[type=url]:focus + .theme-undo,
-#theming input[type=url]:active + .theme-undo {
- visibility: visible;
-}
-#theming label span {
- display: inline-block;
- min-width: 175px;
- max-width: 175px;
- white-space: wrap;
- padding: 8px 0px;
- vertical-align: top;
-}
-#theming .icon-upload,
-#theming .uploadButton .icon-loading-small {
- padding: 8px 20px;
- width: 20px;
- margin: 2px 0px;
- min-height: 32px;
- display: inline-block;
-}
-#theming #theming_settings_status {
- height: 26px;
- margin: 10px;
-}
-#theming #theming_settings_loading {
- display: inline-block;
- vertical-align: middle;
- margin-right: 10px;
-}
-#theming #theming_settings_msg {
- vertical-align: middle;
- border-radius: 3px;
-}
-#theming #theming-preview {
- width: 230px;
- height: 140px;
- background-size: cover;
- background-position: center center;
- text-align: center;
- margin-left: 178px;
- margin-top: 10px;
- margin-bottom: 20px;
- cursor: pointer;
- background-color: var(--color-primary-default);
- background-image: var(--image-background-default, var(--image-background-plain, linear-gradient(40deg, #0082c9 0%, #30b6ff 100%)));
-}
-#theming #theming-preview #theming-preview-logo {
- cursor: pointer;
- width: 20%;
- height: 20%;
- margin-top: 20px;
- display: inline-block;
- background-position: center;
- background-repeat: no-repeat;
- background-size: contain;
- background-image: var(--image-logo, url("../../../core/img/logo/logo.svg"));
-}
-#theming .theming-hints {
- margin-top: 20px;
-}
-#theming .image-preview {
- display: inline-block;
- width: 80px;
- height: 36px;
- background-position: center;
- background-repeat: no-repeat;
- background-size: contain;
-}
-#theming #theming-preview-logoheader {
- background-image: var(--image-logoheader);
-}
-#theming #theming-preview-favicon {
- background-image: var(--image-favicon);
-}
-#theming #user-theming {
- margin-top: 44px;
- display: flex;
-}
-#theming #user-theming > div {
- max-width: 400px;
- margin-bottom: 44px;
-}
-
-/* transition effects for theming value changes */
-#header {
- transition: background-color 500ms linear;
-}
-#header svg, #header img {
- transition: 500ms filter linear;
-}
-
-/*# sourceMappingURL=settings-admin.css.map */
diff --git a/apps/theming/css/settings-admin.scss b/apps/theming/css/settings-admin.scss
deleted file mode 100644
index f34dea52698..00000000000
--- a/apps/theming/css/settings-admin.scss
+++ /dev/null
@@ -1,168 +0,0 @@
-#theming {
- input {
- width: 230px;
- }
-
- input:focus,
- input:active {
- padding-right: 30px;
- }
-
- .fileupload {
- display: none;
- }
-
- div > label {
- position: relative;
- }
-
- .theme-undo {
- position: absolute;
- top: -7px; // input padding
- right: 4px; // input right margin + border
- cursor: pointer;
- opacity: .3;
- padding: 7px;
- vertical-align: top;
- display: inline-block;
- visibility: hidden;
- height: 32px; // height of input
- width: 32px; // height of input
- }
- form.uploadButton {
- width: 411px;
- display: flex;
- align-items: center;
- }
- form .theme-undo,
- .theme-remove-bg {
- cursor: pointer;
- opacity: .3;
- padding: 7px;
- vertical-align: top;
- display: inline-block;
- float: right;
- position: relative;
- top: 4px;
- right: 0px;
- visibility: visible;
- height: 32px;
- width: 32px;
- // right align
- margin-left: auto;
- }
- form .theme-undo:not([style*="display:"]) ~ .theme-remove-bg {
- // Only align the undo button if both are shown
- margin-left: 0;
- }
-
- input[type='text']:hover + .theme-undo,
- input[type='text'] + .theme-undo:hover,
- input[type='text']:focus + .theme-undo,
- input[type='text']:active + .theme-undo,
- input[type='url']:hover + .theme-undo,
- input[type='url'] + .theme-undo:hover,
- input[type='url']:focus + .theme-undo,
- input[type='url']:active + .theme-undo{
- visibility: visible;
- }
-
- label span {
- display: inline-block;
- min-width: 175px;
- max-width: 175px;
- white-space: wrap;
- padding: 8px 0px;
- vertical-align: top;
- }
-
- .icon-upload,
- .uploadButton .icon-loading-small {
- padding: 8px 20px;
- width: 20px;
- margin: 2px 0px;
- min-height: 32px;
- display: inline-block;
- }
-
- #theming_settings_status {
- height: 26px;
- margin: 10px;
- }
-
- #theming_settings_loading {
- display: inline-block;
- vertical-align: middle;
- margin-right: 10px;
- }
-
- #theming_settings_msg {
- vertical-align: middle;
- border-radius: 3px;
- }
-
- #theming-preview {
- width: 230px;
- height: 140px;
- background-size: cover;
- background-position: center center;
- text-align: center;
- margin-left: 178px;
- margin-top: 10px;
- margin-bottom: 20px;
- cursor: pointer;
- background-color: var(--color-primary-default);
- background-image: var(--image-background-default, var(--image-background-plain, linear-gradient(40deg, #0082c9 0%, #30b6ff 100%)));
-
- #theming-preview-logo {
- cursor: pointer;
- width: 20%;
- height: 20%;
- margin-top: 20px;
- display: inline-block;
- background-position: center;
- background-repeat: no-repeat;
- background-size: contain;
- background-image: var(--image-logo, url('../../../core/img/logo/logo.svg'));
- }
- }
-
- .theming-hints {
- margin-top: 20px;
- }
-
- .image-preview {
- display: inline-block;
- width: 80px;
- height: 36px;
- background-position: center;
- background-repeat: no-repeat;
- background-size: contain;
- }
-
- #theming-preview-logoheader {
- // Only using --image-logoheader to show the custom value only
- background-image: var(--image-logoheader);
- }
-
- #theming-preview-favicon {
- background-image: var(--image-favicon);
- }
-
- #user-theming {
- margin-top: 44px;
- display: flex;
- & > div {
- max-width: 400px;
- margin-bottom: 44px;
- }
- }
-}
-
-/* transition effects for theming value changes */
-#header {
- transition: background-color 500ms linear;
- svg, img {
- transition: 500ms filter linear;
- }
-}
diff --git a/apps/theming/lib/Controller/UserThemeController.php b/apps/theming/lib/Controller/UserThemeController.php
index 888ab9a0ca8..112a8a23638 100644
--- a/apps/theming/lib/Controller/UserThemeController.php
+++ b/apps/theming/lib/Controller/UserThemeController.php
@@ -168,9 +168,15 @@ class UserThemeController extends OCSController {
/**
* @NoAdminRequired
*/
- public function setBackground(string $type = BackgroundService::BACKGROUND_DEFAULT, string $value = ''): JSONResponse {
+ public function setBackground(string $type = BackgroundService::BACKGROUND_DEFAULT, string $value = '', string $color = null): JSONResponse {
$currentVersion = (int)$this->config->getUserValue($this->userId, Application::APP_ID, 'userCacheBuster', '0');
+ // Set color if provided
+ if ($color) {
+ $this->backgroundService->setColorBackground($color);
+ }
+
+ // Set background image if provided
try {
switch ($type) {
case BackgroundService::BACKGROUND_SHIPPED:
@@ -179,14 +185,13 @@ class UserThemeController extends OCSController {
case BackgroundService::BACKGROUND_CUSTOM:
$this->backgroundService->setFileBackground($value);
break;
- case 'color':
- $this->backgroundService->setColorBackground($value);
- break;
case BackgroundService::BACKGROUND_DEFAULT:
$this->backgroundService->setDefaultBackground();
break;
default:
- return new JSONResponse(['error' => 'Invalid type provided'], Http::STATUS_BAD_REQUEST);
+ if (!$color) {
+ return new JSONResponse(['error' => 'Invalid type provided'], Http::STATUS_BAD_REQUEST);
+ }
}
} catch (\InvalidArgumentException $e) {
return new JSONResponse(['error' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
diff --git a/apps/theming/lib/ImageManager.php b/apps/theming/lib/ImageManager.php
index ce9c2525802..d4f4353b8a8 100644
--- a/apps/theming/lib/ImageManager.php
+++ b/apps/theming/lib/ImageManager.php
@@ -94,7 +94,7 @@ class ImageManager {
case 'favicon':
return $this->urlGenerator->imagePath('core', 'logo/logo.png') . '?v=' . $cacheBusterCounter;
case 'background':
- return $this->urlGenerator->linkTo(Application::APP_ID, "img/background/" . BackgroundService::DEFAULT_BACKGROUND);
+ return $this->urlGenerator->linkTo(Application::APP_ID, 'img/background/' . BackgroundService::DEFAULT_BACKGROUND);
}
return '';
}
diff --git a/apps/theming/lib/Service/BackgroundService.php b/apps/theming/lib/Service/BackgroundService.php
index 667ca99a1f9..002ca169a83 100644
--- a/apps/theming/lib/Service/BackgroundService.php
+++ b/apps/theming/lib/Service/BackgroundService.php
@@ -30,7 +30,7 @@ namespace OCA\Theming\Service;
use InvalidArgumentException;
use OC\User\NoUserException;
use OCA\Theming\AppInfo\Application;
-use OCP\Files\AppData\IAppDataFactory;
+use OCA\Theming\ThemingDefaults;
use OCP\Files\File;
use OCP\Files\IAppData;
use OCP\Files\IRootFolder;
@@ -140,13 +140,13 @@ class BackgroundService {
private IAppData $appData;
private IConfig $config;
private string $userId;
- private IAppDataFactory $appDataFactory;
+ private ThemingDefaults $themingDefaults;
public function __construct(IRootFolder $rootFolder,
IAppData $appData,
IConfig $config,
?string $userId,
- IAppDataFactory $appDataFactory) {
+ ThemingDefaults $themingDefaults) {
if ($userId === null) {
return;
}
@@ -155,11 +155,12 @@ class BackgroundService {
$this->config = $config;
$this->userId = $userId;
$this->appData = $appData;
- $this->appDataFactory = $appDataFactory;
+ $this->themingDefaults = $themingDefaults;
}
public function setDefaultBackground(): void {
$this->config->deleteUserValue($this->userId, Application::APP_ID, 'background_image');
+ $this->config->setUserValue($this->userId, Application::APP_ID, 'background_color', $this->themingDefaults->getDefaultColorPrimary());
}
/**
@@ -171,7 +172,7 @@ class BackgroundService {
* @throws NoUserException
*/
public function setFileBackground($path): void {
- $this->config->setUserValue($this->userId, Application::APP_ID, 'background_image', self::BACKGROUND_DEFAULT);
+ $this->config->setUserValue($this->userId, Application::APP_ID, 'background_image', self::BACKGROUND_CUSTOM);
$userFolder = $this->rootFolder->getUserFolder($this->userId);
/** @var File $file */
diff --git a/apps/theming/lib/Themes/CommonThemeTrait.php b/apps/theming/lib/Themes/CommonThemeTrait.php
index c58a3fd43e3..40feddd2b38 100644
--- a/apps/theming/lib/Themes/CommonThemeTrait.php
+++ b/apps/theming/lib/Themes/CommonThemeTrait.php
@@ -97,7 +97,7 @@ trait CommonThemeTrait {
if ($backgroundDeleted) {
$variables['--color-background-plain'] = $this->themingDefaults->getColorPrimary();
if ($this->themingDefaults->isUserThemingDisabled() || $user === null) {
- $variables['--image-background-plain'] = 'true';
+ $variables['--image-background-plain'] = 'yes';
}
}
@@ -108,13 +108,12 @@ trait CommonThemeTrait {
if ($image === 'background') {
// If background deleted is set, ignoring variable
if ($backgroundDeleted) {
- $variables['--image-background-default'] = 'no';
continue;
}
$variables['--image-background-size'] = 'cover';
$variables['--image-background-default'] = "url('" . $imageUrl . "')";
}
- // --image-background is overriden by user theming
+ // --image-background is overridden by user theming
$variables["--image-$image"] = "url('" . $imageUrl . "')";
}
}
diff --git a/apps/theming/lib/ThemingDefaults.php b/apps/theming/lib/ThemingDefaults.php
index 42965ca6795..954b1be651b 100644
--- a/apps/theming/lib/ThemingDefaults.php
+++ b/apps/theming/lib/ThemingDefaults.php
@@ -247,7 +247,7 @@ class ThemingDefaults extends \OC_Defaults {
* Return the default color primary
*/
public function getDefaultColorPrimary(): string {
- $color = $this->config->getAppValue(Application::APP_ID, 'color');
+ $color = $this->config->getAppValue(Application::APP_ID, 'color', '');
if (!preg_match('/^\#([0-9a-f]{3}|[0-9a-f]{6})$/i', $color)) {
$color = '#0082c9';
}
diff --git a/apps/theming/src/AdminTheming.vue b/apps/theming/src/AdminTheming.vue
index 1d9f5b69512..4b1877ccf7d 100644
--- a/apps/theming/src/AdminTheming.vue
+++ b/apps/theming/src/AdminTheming.vue
@@ -285,8 +285,15 @@ export default {
background-position: center;
text-align: center;
margin-top: 10px;
- background-color: var(--color-primary-default);
- background-image: var(--image-background-default, var(--image-background-plain, url('../../../core/img/app-background.jpg'), linear-gradient(40deg, #0082c9 0%, #30b6ff 100%)));
+ /* This is basically https://github.com/nextcloud/server/blob/master/core/css/guest.css
+ But without the user variables. That way the admin can preview the render as guest*/
+ /* As guest, there is no user color color-background-plain */
+ background-color: var(--color-primary-default, #0082c9);
+ /* As guest, there is no user background (--image-background)
+ 1. Empty background if defined
+ 2. Else default background
+ 3. Finally default gradient (should not happened, the background is always defined anyway) */
+ background-image: var(--image-background-plain, var(--image-background-default, linear-gradient(40deg, #0082c9 0%, #30b6ff 100%)));
&-logo {
width: 20%;
diff --git a/apps/theming/src/components/BackgroundSettings.vue b/apps/theming/src/components/BackgroundSettings.vue
index 9890f9ad3f0..e1d8b731bc4 100644
--- a/apps/theming/src/components/BackgroundSettings.vue
+++ b/apps/theming/src/components/BackgroundSettings.vue
@@ -1,10 +1,10 @@
<!--
- @copyright Copyright (c) 2020 Julius Härtl <jus@bitgrid.net>
- - @copyright Copyright (c) 2022 Greta Doci <gretadoci@gmail.com>
-
- - @author Julius Härtl <jus@bitgrid.net>
- - @author Greta Doci <gretadoci@gmail.com>
- @author Christopher Ng <chrng8@gmail.com>
+ - @author Greta Doci <gretadoci@gmail.com>
+ - @author John Molakvoæ <skjnldsv@protonmail.com>
+ - @author Julius Härtl <jus@bitgrid.net>
-
- @license GNU AGPL version 3 or any later version
-
@@ -24,13 +24,16 @@
-->
<template>
- <div class="background-selector">
+ <div class="background-selector" data-user-theming-background-settings>
<!-- Custom background -->
<button class="background background__filepicker"
- :class="{ 'background--active': backgroundImage === 'custom' }"
+ :class="{ 'icon-loading': loading === 'custom', 'background--active': backgroundImage === 'custom' }"
+ :data-color-bright="invertTextColor(Theming.color)"
+ data-user-theming-background-custom
tabindex="0"
@click="pickFile">
{{ t('theming', 'Custom background') }}
+ <Check :size="44" />
</button>
<!-- Default background -->
@@ -38,6 +41,7 @@
:class="{ 'icon-loading': loading === 'default', 'background--active': backgroundImage === 'default' }"
:data-color-bright="invertTextColor(Theming.defaultColor)"
:style="{ '--border-color': Theming.defaultColor }"
+ data-user-theming-background-default
tabindex="0"
@click="setDefault">
{{ t('theming', 'Default background') }}
@@ -50,6 +54,7 @@
:data-color="Theming.color"
:data-color-bright="invertTextColor(Theming.color)"
:style="{ backgroundColor: Theming.color, '--border-color': Theming.color}"
+ data-user-theming-background-color
tabindex="0">
{{ t('theming', 'Change color') }}
</button>
@@ -61,6 +66,7 @@
v-tooltip="shippedBackground.details.attribution"
:class="{ '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 }"
class="background background__shipped"
tabindex="0"
@@ -70,16 +76,17 @@
<!-- Remove background -->
<button class="background background__delete"
+ data-user-theming-background-clear
tabindex="0"
@click="removeBackground">
{{ t('theming', 'Remove background') }}
- <Close :size="24" />
+ <Close :size="32" />
</button>
</div>
</template>
<script>
-import { generateFilePath, generateUrl } from '@nextcloud/router'
+import { generateFilePath, generateRemoteUrl, generateUrl } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
import axios from '@nextcloud/axios'
import Check from 'vue-material-design-icons/Check.vue'
@@ -87,6 +94,10 @@ import Close from 'vue-material-design-icons/Close.vue'
import debounce from 'debounce'
import NcColorPicker from '@nextcloud/vue/dist/Components/NcColorPicker'
import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip'
+import Vibrant from 'node-vibrant'
+import { Palette } from 'node-vibrant/lib/color'
+import { getFilePickerBuilder } from '@nextcloud/dialogs'
+import { getCurrentUser } from '@nextcloud/auth'
const backgroundColor = loadState('theming', 'backgroundColor')
const backgroundImage = loadState('theming', 'backgroundImage')
@@ -95,6 +106,12 @@ const themingDefaultBackground = loadState('theming', 'themingDefaultBackground'
const defaultShippedBackground = loadState('theming', 'defaultShippedBackground')
const prefixWithBaseUrl = (url) => generateFilePath('theming', '', 'img/background/') + url
+const picker = getFilePickerBuilder(t('theming', 'Select a background from your files'))
+ .setMultiSelect(false)
+ .setModal(true)
+ .setType(1)
+ .setMimeTypeFilter(['image/png', 'image/gif', 'image/jpeg', 'image/svg+xml', 'image/svg'])
+ .build()
export default {
name: 'BackgroundSettings',
@@ -213,9 +230,9 @@ export default {
this.update(result.data)
},
- async setFile(path) {
+ async setFile(path, color = null) {
this.loading = 'custom'
- const result = await axios.post(generateUrl('/apps/theming/background/custom'), { value: path })
+ const result = await axios.post(generateUrl('/apps/theming/background/custom'), { value: path, color })
this.update(result.data)
},
@@ -228,19 +245,55 @@ export default {
async pickColor(event) {
this.loading = 'color'
const color = event?.target?.dataset?.color || this.Theming?.color || '#0082c9'
- const result = await axios.post(generateUrl('/apps/theming/background/color'), { value: color })
+ const result = await axios.post(generateUrl('/apps/theming/background/color'), { color })
this.update(result.data)
},
debouncePickColor: debounce(function() {
this.pickColor(...arguments)
}, 200),
- pickFile() {
- window.OC.dialogs.filepicker(t('theming', 'Select a background from your files'), (path, type) => {
- if (type === OC.dialogs.FILEPICKER_TYPE_CHOOSE) {
- this.setFile(path)
- }
- }, false, ['image/png', 'image/gif', 'image/jpeg', 'image/svg'], true, OC.dialogs.FILEPICKER_TYPE_CHOOSE)
+ async pickFile() {
+ const path = await picker.pick()
+ this.loading = 'custom'
+
+ // Extract primary color from image
+ let response = null
+ let color = null
+ try {
+ const fileUrl = generateRemoteUrl('dav/files/' + getCurrentUser().uid + path)
+ response = await axios.get(fileUrl, { responseType: 'blob' })
+ const blobUrl = URL.createObjectURL(response.data)
+ const palette = await this.getColorPaletteFromBlob(blobUrl)
+
+ // DarkVibrant is accessible AND visually pleasing
+ // Vibrant is not accessible enough and others are boring
+ color = palette?.DarkVibrant?.hex
+ this.setFile(path, color)
+
+ // Log data
+ console.debug('Extracted colour', color, 'from custom image', path, palette)
+ } catch (error) {
+ this.setFile(path)
+ console.error('Unable to extract colour from custom image', { error, path, response, color })
+ }
+ },
+
+ /**
+ * Extract a Vibrant color palette from a blob URL
+ *
+ * @param {string} blobUrl the blob URL
+ * @return {Promise<Palette>}
+ */
+ getColorPaletteFromBlob(blobUrl) {
+ return new Promise((resolve, reject) => {
+ const vibrant = new Vibrant(blobUrl)
+ vibrant.getPalette((error, palette) => {
+ if (error) {
+ reject(error)
+ }
+ resolve(palette)
+ })
+ })
},
},
}
@@ -263,6 +316,13 @@ export default {
background-position: center center;
background-size: cover;
+ &__filepicker {
+ &.background--active {
+ color: white;
+ background-image: var(--image-background);
+ }
+ }
+
&__default {
background-color: var(--color-primary-default);
background-image: var(--image-background-default);
@@ -277,6 +337,12 @@ export default {
background-color: var(--color-primary-default);
}
+ // Over a background image
+ &__default,
+ &__shipped {
+ color: white;
+ }
+
// Text and svg icon dark on bright background
&[data-color-bright] {
color: black;
@@ -294,18 +360,14 @@ export default {
margin: 4px;
}
- &__default,
- &__shipped {
- color: white;
- span {
- display: none;
- }
+ &__filepicker span,
+ &__default span,
+ &__shipped span {
+ display: none;
}
- &--active:not(.icon-loading) {
- span {
- display: block;
- }
+ &--active:not(.icon-loading) span {
+ display: block !important;
}
}
}
diff --git a/apps/theming/tests/Controller/ThemingControllerTest.php b/apps/theming/tests/Controller/ThemingControllerTest.php
index c67be434631..9042a338fb7 100644
--- a/apps/theming/tests/Controller/ThemingControllerTest.php
+++ b/apps/theming/tests/Controller/ThemingControllerTest.php
@@ -680,7 +680,7 @@ class ThemingControllerTest extends TestCase {
public function testGetLoginBackground() {
$file = $this->createMock(ISimpleFile::class);
- $file->method('getName')->willReturn('app-background.jpg');
+ $file->method('getName')->willReturn('background.png');
$file->method('getMTime')->willReturn(42);
$this->imageManager->expects($this->once())
->method('getImage')
diff --git a/apps/theming/tests/Themes/DefaultThemeTest.php b/apps/theming/tests/Themes/DefaultThemeTest.php
index 4471dddb01e..2a62211f268 100644
--- a/apps/theming/tests/Themes/DefaultThemeTest.php
+++ b/apps/theming/tests/Themes/DefaultThemeTest.php
@@ -22,8 +22,10 @@
*/
namespace OCA\Theming\Tests\Service;
+use OCA\Theming\AppInfo\Application;
use OCA\Theming\ImageManager;
use OCA\Theming\ITheme;
+use OCA\Theming\Service\BackgroundService;
use OCA\Theming\Themes\DefaultTheme;
use OCA\Theming\ThemingDefaults;
use OCA\Theming\Util;
@@ -80,6 +82,11 @@ class DefaultThemeTest extends TestCase {
->method('getDefaultColorPrimary')
->willReturn('#0082c9');
+ $this->themingDefaults
+ ->expects($this->any())
+ ->method('getBackground')
+ ->willReturn('/apps/' . Application::APP_ID . '/img/background/' . BackgroundService::DEFAULT_BACKGROUND);
+
$this->l10n
->expects($this->any())
->method('t')
diff --git a/apps/theming/tests/ThemingDefaultsTest.php b/apps/theming/tests/ThemingDefaultsTest.php
index caed7002980..04fce553c33 100644
--- a/apps/theming/tests/ThemingDefaultsTest.php
+++ b/apps/theming/tests/ThemingDefaultsTest.php
@@ -473,6 +473,7 @@ class ThemingDefaultsTest extends TestCase {
public function testGetColorPrimaryWithCustomBackground() {
$backgroundIndex = 2;
$background = array_values(BackgroundService::SHIPPED_BACKGROUNDS)[$backgroundIndex];
+
$user = $this->createMock(IUser::class);
$this->userSession->expects($this->any())
->method('getUser')
@@ -484,14 +485,15 @@ class ThemingDefaultsTest extends TestCase {
$this->config
->expects($this->once())
->method('getUserValue')
- ->with('user', 'theming', 'background_image', '')
- ->willReturn(array_keys(BackgroundService::SHIPPED_BACKGROUNDS)[$backgroundIndex]);
+ ->with('user', 'theming', 'background_color', '')
+ ->willReturn($background['primary_color']);
+
$this->config
->expects($this->exactly(2))
->method('getAppValue')
->willReturnMap([
- ['theming', 'disable-user-theming', 'no', 'no'],
['theming', 'color', '', ''],
+ ['theming', 'disable-user-theming', 'no', 'no'],
]);
$this->assertEquals($background['primary_color'], $this->template->getColorPrimary());
@@ -509,14 +511,14 @@ class ThemingDefaultsTest extends TestCase {
$this->config
->expects($this->once())
->method('getUserValue')
- ->with('user', 'theming', 'background_image', '')
+ ->with('user', 'theming', 'background_color', '')
->willReturn('#fff');
$this->config
->expects($this->exactly(2))
->method('getAppValue')
->willReturnMap([
- ['theming', 'disable-user-theming', 'no', 'no'],
['theming', 'color', '', ''],
+ ['theming', 'disable-user-theming', 'no', 'no'],
]);
$this->assertEquals('#fff', $this->template->getColorPrimary());
@@ -534,14 +536,14 @@ class ThemingDefaultsTest extends TestCase {
$this->config
->expects($this->once())
->method('getUserValue')
- ->with('user', 'theming', 'background_image', '')
+ ->with('user', 'theming', 'background_color', '')
->willReturn('nextcloud');
$this->config
->expects($this->exactly(3))
->method('getAppValue')
->willReturnMap([
- ['theming', 'disable-user-theming', 'no', 'no'],
['theming', 'color', '', ''],
+ ['theming', 'disable-user-theming', 'no', 'no'],
]);
$this->assertEquals($this->template->getDefaultColorPrimary(), $this->template->getColorPrimary());
@@ -650,16 +652,14 @@ class ThemingDefaultsTest extends TestCase {
->method('deleteAppValue')
->with('theming', 'color');
$this->config
- ->expects($this->exactly(3))
+ ->expects($this->exactly(2))
->method('getAppValue')
->withConsecutive(
['theming', 'cachebuster', '0'],
['theming', 'color', null],
- ['theming', 'disable-user-theming', 'no'],
)->willReturnOnConsecutiveCalls(
'15',
$this->defaults->getColorPrimary(),
- 'no',
);
$this->config
->expects($this->once())
@@ -778,10 +778,10 @@ class ThemingDefaultsTest extends TestCase {
$this->imageManager->expects($this->exactly(4))
->method('getImageUrl')
->willReturnMap([
- ['logo', true, 'custom-logo?v=0'],
- ['logoheader', true, 'custom-logoheader?v=0'],
- ['favicon', true, 'custom-favicon?v=0'],
- ['background_image', true, 'custom-background?v=0'],
+ ['logo', 'custom-logo?v=0'],
+ ['logoheader', 'custom-logoheader?v=0'],
+ ['favicon', 'custom-favicon?v=0'],
+ ['background', 'custom-background?v=0'],
]);
$expected = [