diff options
author | Simon L <szaimen@e.mail.de> | 2022-10-13 20:57:28 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-10-13 20:57:28 +0300 |
commit | b28757fddc90f753412bfe80baa1964e7ae912db (patch) | |
tree | 50e9032a9e7b4a2e915a2e50b8608d3b86f7cb3e /apps | |
parent | dff4f8a5e4dce3b14f86b03557cedb063c12d5ad (diff) | |
parent | 2f8601a27edc1d63b2607539982e8959533f0f40 (diff) |
Merge pull request #34437 from nextcloud/feat/theming-default-system-value
Use default system primary
Diffstat (limited to 'apps')
-rw-r--r-- | apps/theming/css/default.css | 1 | ||||
-rw-r--r-- | apps/theming/css/settings-admin.css | 2 | ||||
-rw-r--r-- | apps/theming/css/settings-admin.scss | 2 | ||||
-rw-r--r-- | apps/theming/lib/Service/JSDataService.php | 1 | ||||
-rw-r--r-- | apps/theming/lib/Settings/Admin.php | 2 | ||||
-rw-r--r-- | apps/theming/lib/Themes/CommonThemeTrait.php | 1 | ||||
-rw-r--r-- | apps/theming/lib/Themes/DefaultTheme.php | 11 | ||||
-rw-r--r-- | apps/theming/lib/ThemingDefaults.php | 42 | ||||
-rw-r--r-- | apps/theming/src/UserThemes.vue | 8 | ||||
-rw-r--r-- | apps/theming/src/components/BackgroundSettings.vue | 111 | ||||
-rw-r--r-- | apps/theming/tests/Service/ThemesServiceTest.php | 4 | ||||
-rw-r--r-- | apps/theming/tests/Settings/AdminTest.php | 4 | ||||
-rw-r--r-- | apps/theming/tests/Settings/PersonalTest.php | 4 | ||||
-rw-r--r-- | apps/theming/tests/Themes/DefaultThemeTest.php | 5 | ||||
-rw-r--r-- | apps/theming/tests/Themes/DyslexiaFontTest.php | 5 | ||||
-rw-r--r-- | apps/theming/tests/ThemingDefaultsTest.php | 72 |
16 files changed, 237 insertions, 38 deletions
diff --git a/apps/theming/css/default.css b/apps/theming/css/default.css index da4814d66ef..1f0a241307b 100644 --- a/apps/theming/css/default.css +++ b/apps/theming/css/default.css @@ -57,6 +57,7 @@ --background-invert-if-bright: invert(100%); --image-main-background: url('/core/img/app-background.jpg'); --color-primary: #00639a; + --color-primary-default: #0082c9; --color-primary-text: #ffffff; --color-primary-hover: #3282ae; --color-primary-light: #e5eff4; diff --git a/apps/theming/css/settings-admin.css b/apps/theming/css/settings-admin.css index 2b91404ec3b..00d4e2414fa 100644 --- a/apps/theming/css/settings-admin.css +++ b/apps/theming/css/settings-admin.css @@ -89,7 +89,7 @@ margin-top: 10px; margin-bottom: 20px; cursor: pointer; - background-color: var(--color-main-background-not-plain, var(--color-primary)); + background-color: var(--color-primary-default); background-image: var(--image-background, var(--image-background-plain, url("../../../core/img/app-background.jpg"), linear-gradient(40deg, #0082c9 0%, #30b6ff 100%))); } #theming #theming-preview #theming-preview-logo { diff --git a/apps/theming/css/settings-admin.scss b/apps/theming/css/settings-admin.scss index 706bd37f495..f43d3b8c417 100644 --- a/apps/theming/css/settings-admin.scss +++ b/apps/theming/css/settings-admin.scss @@ -100,7 +100,7 @@ margin-top: 10px; margin-bottom: 20px; cursor: pointer; - background-color: var(--color-main-background-not-plain, var(--color-primary)); + background-color: var(--color-primary-default); background-image: var(--image-background, var(--image-background-plain, url('../../../core/img/app-background.jpg'), linear-gradient(40deg, #0082c9 0%, #30b6ff 100%))); #theming-preview-logo { diff --git a/apps/theming/lib/Service/JSDataService.php b/apps/theming/lib/Service/JSDataService.php index fdc85ea445a..26cda8c0012 100644 --- a/apps/theming/lib/Service/JSDataService.php +++ b/apps/theming/lib/Service/JSDataService.php @@ -55,6 +55,7 @@ class JSDataService implements \JsonSerializable { 'url' => $this->themingDefaults->getBaseUrl(), 'slogan' => $this->themingDefaults->getSlogan(), 'color' => $this->themingDefaults->getColorPrimary(), + 'defaultColor' => $this->themingDefaults->getDefaultColorPrimary(), 'imprintUrl' => $this->themingDefaults->getImprintUrl(), 'privacyUrl' => $this->themingDefaults->getPrivacyUrl(), 'inverted' => $this->util->invertTextColor($this->themingDefaults->getColorPrimary()), diff --git a/apps/theming/lib/Settings/Admin.php b/apps/theming/lib/Settings/Admin.php index 6caa174d99b..e89ea6b6fe9 100644 --- a/apps/theming/lib/Settings/Admin.php +++ b/apps/theming/lib/Settings/Admin.php @@ -75,7 +75,7 @@ class Admin implements IDelegatedSettings { 'name' => $this->themingDefaults->getEntity(), 'url' => $this->themingDefaults->getBaseUrl(), 'slogan' => $this->themingDefaults->getSlogan(), - 'color' => $this->themingDefaults->getColorPrimary(), + 'color' => $this->themingDefaults->getDefaultColorPrimary(), 'uploadLogoRoute' => $this->urlGenerator->linkToRoute('theming.Theming.uploadImage'), 'canThemeIcons' => $this->imageManager->shouldReplaceIcons(), 'iconDocs' => $this->urlGenerator->linkToDocs('admin-theming-icons'), diff --git a/apps/theming/lib/Themes/CommonThemeTrait.php b/apps/theming/lib/Themes/CommonThemeTrait.php index 631879ea832..d88a6a319fb 100644 --- a/apps/theming/lib/Themes/CommonThemeTrait.php +++ b/apps/theming/lib/Themes/CommonThemeTrait.php @@ -42,6 +42,7 @@ trait CommonThemeTrait { // primary related colours return [ '--color-primary' => $this->primaryColor, + '--color-primary-default' => $this->defaultPrimaryColor, '--color-primary-text' => $this->util->invertTextColor($this->primaryColor) ? '#000000' : '#ffffff', '--color-primary-hover' => $this->util->mix($this->primaryColor, $colorMainBackground, 60), '--color-primary-light' => $colorPrimaryLight, diff --git a/apps/theming/lib/Themes/DefaultTheme.php b/apps/theming/lib/Themes/DefaultTheme.php index e295d5d880a..4dce1dca809 100644 --- a/apps/theming/lib/Themes/DefaultTheme.php +++ b/apps/theming/lib/Themes/DefaultTheme.php @@ -48,6 +48,7 @@ class DefaultTheme implements ITheme { public IConfig $config; public IL10N $l; + public string $defaultPrimaryColor; public string $primaryColor; public function __construct(Util $util, @@ -65,9 +66,13 @@ class DefaultTheme implements ITheme { $this->config = $config; $this->l = $l; - $initialPrimaryColor = $this->themingDefaults->getColorPrimary(); - // Override default color if set to improve accessibility - $this->primaryColor = $initialPrimaryColor === BackgroundService::DEFAULT_COLOR ? BackgroundService::DEFAULT_ACCESSIBLE_COLOR : $initialPrimaryColor; + $this->defaultPrimaryColor = $this->themingDefaults->getDefaultColorPrimary(); + $this->primaryColor = $this->themingDefaults->getColorPrimary(); + + // Override default defaultPrimaryColor if set to improve accessibility + if ($this->primaryColor === BackgroundService::DEFAULT_COLOR) { + $this->primaryColor = BackgroundService::DEFAULT_ACCESSIBLE_COLOR; + } } public function getId(): string { diff --git a/apps/theming/lib/ThemingDefaults.php b/apps/theming/lib/ThemingDefaults.php index ae12530fb5c..9d5183a6504 100644 --- a/apps/theming/lib/ThemingDefaults.php +++ b/apps/theming/lib/ThemingDefaults.php @@ -214,26 +214,50 @@ class ThemingDefaults extends \OC_Defaults { /** * Color that is used for the header as well as for mail headers - * - * @return string */ - public function getColorPrimary() { + public function getColorPrimary(): string { $user = $this->userSession->getUser(); - $color = $this->config->getAppValue(Application::APP_ID, 'color', ''); - if ($color === '' && !empty($user)) { - $themingBackground = $this->config->getUserValue($user->getUID(), Application::APP_ID, 'background', 'default'); - if ($themingBackground === 'default') { + // admin-defined primary color + $defaultColor = $this->getDefaultColorPrimary(); + + // user-defined primary color + $themingBackground = ''; + if (!empty($user)) { + $themingBackground = $this->config->getUserValue($user->getUID(), Application::APP_ID, 'background', ''); + // If the user selected the default background + if ($themingBackground === '') { return BackgroundService::DEFAULT_COLOR; - } else if (isset(BackgroundService::SHIPPED_BACKGROUNDS[$themingBackground]['primary_color'])) { + } + + // If the user selected a specific colour + if (preg_match('/^\#([0-9a-f]{3}|[0-9a-f]{6})$/i', $themingBackground)) { + return $themingBackground; + } + + // if the user-selected background is a background reference + if (isset(BackgroundService::SHIPPED_BACKGROUNDS[$themingBackground]['primary_color'])) { return BackgroundService::SHIPPED_BACKGROUNDS[$themingBackground]['primary_color']; } } - if (!preg_match('/^\#([0-9a-f]{3}|[0-9a-f]{6})$/i', $color)) { + // If the default color is not valid, return the default background one + if (!preg_match('/^\#([0-9a-f]{3}|[0-9a-f]{6})$/i', $defaultColor)) { return BackgroundService::DEFAULT_COLOR; } + // Finally, return the system global primary color + return $defaultColor; + } + + /** + * Return the default color primary + */ + public function getDefaultColorPrimary(): string { + $color = $this->config->getAppValue(Application::APP_ID, 'color'); + if (!preg_match('/^\#([0-9a-f]{3}|[0-9a-f]{6})$/i', $color)) { + $color = '#0082c9'; + } return $color; } diff --git a/apps/theming/src/UserThemes.vue b/apps/theming/src/UserThemes.vue index 3e3b0afed16..1eeeb3c5b21 100644 --- a/apps/theming/src/UserThemes.vue +++ b/apps/theming/src/UserThemes.vue @@ -156,16 +156,16 @@ export default { }, }, - mounted() { - this.updateGlobalStyles() - }, - watch: { shortcutsDisabled(newState) { this.changeShortcutsDisabled(newState) }, }, + mounted() { + this.updateGlobalStyles() + }, + methods: { updateBackground(data) { this.background = (data.type === 'custom' || data.type === 'default') ? data.type : data.value diff --git a/apps/theming/src/components/BackgroundSettings.vue b/apps/theming/src/components/BackgroundSettings.vue index 39210569689..6d2fcecbd3b 100644 --- a/apps/theming/src/components/BackgroundSettings.vue +++ b/apps/theming/src/components/BackgroundSettings.vue @@ -25,42 +25,67 @@ <template> <div class="background-selector"> + <!-- Custom background --> <button class="background filepicker" :class="{ active: background === 'custom' }" tabindex="0" @click="pickFile"> {{ t('theming', 'Pick from Files') }} </button> + + <!-- Default background --> <button class="background default" tabindex="0" :class="{ 'icon-loading': loading === 'default', active: background === 'default' }" @click="setDefault"> {{ t('theming', 'Default image') }} </button> + + <!-- Custom color picker --> + <NcColorPicker v-model="Theming.color" @input="debouncePickColor"> + <button class="background color" + :class="{ active: background === Theming.color}" + tabindex="0" + :data-color="Theming.color" + :data-color-bright="invertTextColor(Theming.color)" + :style="{ backgroundColor: Theming.color, color: invertTextColor(Theming.color) ? '#000000' : '#ffffff'}"> + {{ t('theming', 'Custom color') }} + </button> + </NcColorPicker> + + <!-- Default admin primary color --> <button class="background color" - :class="{ active: background.startsWith('#') }" + :class="{ active: background === Theming.defaultColor }" tabindex="0" - @click="pickColor"> + :data-color="Theming.defaultColor" + :data-color-bright="invertTextColor(Theming.defaultColor)" + :style="{ color: invertTextColor(Theming.defaultColor) ? '#000000' : '#ffffff'}" + @click="debouncePickColor"> {{ t('theming', 'Plain background') }} </button> + + <!-- Background set selection --> <button v-for="shippedBackground in shippedBackgrounds" :key="shippedBackground.name" v-tooltip="shippedBackground.details.attribution" :class="{ 'icon-loading': loading === shippedBackground.name, active: background === shippedBackground.name }" tabindex="0" class="background" + :data-color-bright="shippedBackground.details.theming === 'dark'" :style="{ 'background-image': 'url(' + shippedBackground.preview + ')' }" @click="setShipped(shippedBackground.name)" /> </div> </template> <script> -import axios from '@nextcloud/axios' -import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip' import { generateUrl } from '@nextcloud/router' -import { loadState } from '@nextcloud/initial-state' import { getBackgroundUrl } from '../helpers/getBackgroundUrl.js' +import { loadState } from '@nextcloud/initial-state' import { prefixWithBaseUrl } from '../helpers/prefixWithBaseUrl.js' +import axios from '@nextcloud/axios' +import debounce from 'debounce' +import NcColorPicker from '@nextcloud/vue/dist/Components/NcColorPicker' +import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip' const shippedBackgroundList = loadState('theming', 'shippedBackgrounds') @@ -69,6 +94,11 @@ export default { directives: { Tooltip, }, + + components: { + NcColorPicker, + }, + props: { background: { type: String, @@ -79,12 +109,15 @@ export default { default: '', }, }, + data() { return { backgroundImage: generateUrl('/apps/theming/background') + '?v=' + Date.now(), loading: false, + Theming: loadState('theming', 'data', {}), } }, + computed: { shippedBackgrounds() { return Object.keys(shippedBackgroundList).map(fileName => { @@ -97,7 +130,39 @@ export default { }) }, }, + methods: { + /** + * Do we need to invert the text if color is too bright? + * + * @param {string} color the hex color + */ + invertTextColor(color) { + return this.calculateLuma(color) > 0.6 + }, + + /** + * Calculate luminance of provided hex color + * + * @param {string} color the hex color + */ + calculateLuma(color) { + const [red, green, blue] = this.hexToRGB(color) + return (0.2126 * red + 0.7152 * green + 0.0722 * blue) / 255 + }, + + /** + * Convert hex color to RGB + * + * @param {string} hex the hex color + */ + hexToRGB(hex) { + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) + return result + ? [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)] + : null + }, + async update(data) { const background = data.type === 'custom' || data.type === 'default' ? data.type : data.value this.backgroundImage = getBackgroundUrl(background, data.version, this.themingDefaultBackground) @@ -113,27 +178,35 @@ export default { } image.src = this.backgroundImage }, + async setDefault() { this.loading = 'default' const result = await axios.post(generateUrl('/apps/theming/background/default')) this.update(result.data) }, + async setShipped(shipped) { this.loading = shipped const result = await axios.post(generateUrl('/apps/theming/background/shipped'), { value: shipped }) this.update(result.data) }, + async setFile(path) { this.loading = 'custom' const result = await axios.post(generateUrl('/apps/theming/background/custom'), { value: path }) this.update(result.data) }, - async pickColor() { + + debouncePickColor: debounce(function() { + this.pickColor(...arguments) + }, 200), + async pickColor(event) { this.loading = 'color' - const color = OCA && OCA.Theming ? OCA.Theming.color : '#0082c9' + const color = event?.target?.dataset?.color || this.Theming?.color || '#0082c9' const result = await axios.post(generateUrl('/apps/theming/background/color'), { value: color }) this.update(result.data) }, + pickFile() { window.OC.dialogs.filepicker(t('theming', 'Insert from {productName}', { productName: OC.theme.name }), (path, type) => { if (type === OC.dialogs.FILEPICKER_TYPE_CHOOSE) { @@ -171,7 +244,7 @@ export default { } &.color { - background-color: var(--color-main-background-not-plain, var(--color-primary)); + background-color: var(--color-primary-default); color: var(--color-primary-text); } @@ -181,14 +254,20 @@ export default { border: 2px solid var(--color-primary); } - &.active:not(.icon-loading):after { - background-image: var(--icon-checkmark-white); - background-repeat: no-repeat; - background-position: center; - background-size: 44px; - content: ''; - display: block; - height: 100%; + &.active:not(.icon-loading) { + &:after { + background-image: var(--icon-checkmark-white); + background-repeat: no-repeat; + background-position: center; + background-size: 44px; + content: ''; + display: block; + height: 100%; + } + + &[data-color-bright]:after { + background-image: var(--icon-checkmark-dark); + } } } } diff --git a/apps/theming/tests/Service/ThemesServiceTest.php b/apps/theming/tests/Service/ThemesServiceTest.php index 5a2907ec073..62f00ab0e31 100644 --- a/apps/theming/tests/Service/ThemesServiceTest.php +++ b/apps/theming/tests/Service/ThemesServiceTest.php @@ -68,6 +68,10 @@ class ThemesServiceTest extends TestCase { ->method('getColorPrimary') ->willReturn('#0082c9'); + $this->themingDefaults->expects($this->any()) + ->method('getDefaultColorPrimary') + ->willReturn('#0082c9'); + $this->initThemes(); $this->themesService = new ThemesService( diff --git a/apps/theming/tests/Settings/AdminTest.php b/apps/theming/tests/Settings/AdminTest.php index b10196a1ac5..8f259e29ba5 100644 --- a/apps/theming/tests/Settings/AdminTest.php +++ b/apps/theming/tests/Settings/AdminTest.php @@ -97,7 +97,7 @@ class AdminTest extends TestCase { ->willReturn('MySlogan'); $this->themingDefaults ->expects($this->once()) - ->method('getColorPrimary') + ->method('getDefaultColorPrimary') ->willReturn('#fff'); $this->urlGenerator ->expects($this->once()) @@ -156,7 +156,7 @@ class AdminTest extends TestCase { ->willReturn('MySlogan'); $this->themingDefaults ->expects($this->once()) - ->method('getColorPrimary') + ->method('getDefaultColorPrimary') ->willReturn('#fff'); $this->urlGenerator ->expects($this->once()) diff --git a/apps/theming/tests/Settings/PersonalTest.php b/apps/theming/tests/Settings/PersonalTest.php index 0cb289cb86a..8597461a175 100644 --- a/apps/theming/tests/Settings/PersonalTest.php +++ b/apps/theming/tests/Settings/PersonalTest.php @@ -138,6 +138,10 @@ class PersonalTest extends TestCase { $themingDefaults->expects($this->any()) ->method('getColorPrimary') ->willReturn('#0082c9'); + + $themingDefaults->expects($this->any()) + ->method('getDefaultColorPrimary') + ->willReturn('#0082c9'); $this->themes = [ 'default' => new DefaultTheme( diff --git a/apps/theming/tests/Themes/DefaultThemeTest.php b/apps/theming/tests/Themes/DefaultThemeTest.php index c1de2810396..eafd66ef663 100644 --- a/apps/theming/tests/Themes/DefaultThemeTest.php +++ b/apps/theming/tests/Themes/DefaultThemeTest.php @@ -70,6 +70,11 @@ class DefaultThemeTest extends TestCase { ->method('getColorPrimary') ->willReturn('#0082c9'); + $this->themingDefaults + ->expects($this->any()) + ->method('getDefaultColorPrimary') + ->willReturn('#0082c9'); + $this->l10n ->expects($this->any()) ->method('t') diff --git a/apps/theming/tests/Themes/DyslexiaFontTest.php b/apps/theming/tests/Themes/DyslexiaFontTest.php index 77eb77a2818..8a0df960205 100644 --- a/apps/theming/tests/Themes/DyslexiaFontTest.php +++ b/apps/theming/tests/Themes/DyslexiaFontTest.php @@ -84,6 +84,11 @@ class DyslexiaFontTest extends TestCase { ->method('getColorPrimary') ->willReturn('#0082c9'); + $this->themingDefaults + ->expects($this->any()) + ->method('getDefaultColorPrimary') + ->willReturn('#0082c9'); + $this->l10n ->expects($this->any()) ->method('t') diff --git a/apps/theming/tests/ThemingDefaultsTest.php b/apps/theming/tests/ThemingDefaultsTest.php index 5aa36211053..5106b2551b8 100644 --- a/apps/theming/tests/ThemingDefaultsTest.php +++ b/apps/theming/tests/ThemingDefaultsTest.php @@ -35,6 +35,7 @@ namespace OCA\Theming\Tests; use OCA\Theming\ImageManager; +use OCA\Theming\Service\BackgroundService; use OCA\Theming\ThemingDefaults; use OCA\Theming\Util; use OCP\App\IAppManager; @@ -46,6 +47,7 @@ use OCP\IConfig; use OCP\IL10N; use OCP\INavigationManager; use OCP\IURLGenerator; +use OCP\IUser; use OCP\IUserSession; use Test\TestCase; @@ -420,7 +422,7 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals('<a href="url" target="_blank" rel="noreferrer noopener" class="entity-name">Name</a> – Slogan', $this->template->getShortFooter()); } - public function testgetColorPrimaryWithDefault() { + public function testGetColorPrimaryWithDefault() { $this->config ->expects($this->once()) ->method('getAppValue') @@ -440,6 +442,74 @@ class ThemingDefaultsTest extends TestCase { $this->assertEquals('#fff', $this->template->getColorPrimary()); } + public function testGetColorPrimaryWithDefaultBackground() { + $user = $this->createMock(IUser::class); + $this->userSession->expects($this->any()) + ->method('getUser') + ->willReturn($user); + $user->expects($this->any()) + ->method('getUID') + ->willReturn('user'); + + $this->assertEquals(BackgroundService::DEFAULT_COLOR, $this->template->getColorPrimary()); + } + + public function testGetColorPrimaryWithCustomBackground() { + $backgroundIndex = 2; + $background = array_values(BackgroundService::SHIPPED_BACKGROUNDS)[$backgroundIndex]; + $user = $this->createMock(IUser::class); + $this->userSession->expects($this->any()) + ->method('getUser') + ->willReturn($user); + $user->expects($this->any()) + ->method('getUID') + ->willReturn('user'); + + $this->config + ->expects($this->once()) + ->method('getUserValue') + ->with('user', 'theming', 'background', '') + ->willReturn(array_keys(BackgroundService::SHIPPED_BACKGROUNDS)[$backgroundIndex]); + + $this->assertEquals($background['primary_color'], $this->template->getColorPrimary()); + } + + public function testGetColorPrimaryWithCustomBackgroundColor() { + $user = $this->createMock(IUser::class); + $this->userSession->expects($this->any()) + ->method('getUser') + ->willReturn($user); + $user->expects($this->any()) + ->method('getUID') + ->willReturn('user'); + + $this->config + ->expects($this->once()) + ->method('getUserValue') + ->with('user', 'theming', 'background', '') + ->willReturn('#fff'); + + $this->assertEquals('#fff', $this->template->getColorPrimary()); + } + + public function testGetColorPrimaryWithInvalidCustomBackgroundColor() { + $user = $this->createMock(IUser::class); + $this->userSession->expects($this->any()) + ->method('getUser') + ->willReturn($user); + $user->expects($this->any()) + ->method('getUID') + ->willReturn('user'); + + $this->config + ->expects($this->once()) + ->method('getUserValue') + ->with('user', 'theming', 'background', '') + ->willReturn('nextcloud'); + + $this->assertEquals($this->template->getDefaultColorPrimary(), $this->template->getColorPrimary()); + } + public function testSet() { $this->config ->expects($this->exactly(2)) |