From 387550be88046bfe4f6098f7eaaba319b6a623b5 Mon Sep 17 00:00:00 2001 From: Julius Haertl Date: Tue, 12 Jul 2016 14:59:28 +0200 Subject: Theming: Implement swapping the foreground color for bright colors --- apps/theming/js/settings-admin.js | 33 +++++++++++++ apps/theming/lib/controller/themingcontroller.php | 7 +++ apps/theming/lib/util.php | 57 +++++++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 apps/theming/lib/util.php (limited to 'apps') diff --git a/apps/theming/js/settings-admin.js b/apps/theming/js/settings-admin.js index bd4b4b34ed1..172c6bd31ca 100644 --- a/apps/theming/js/settings-admin.js +++ b/apps/theming/js/settings-admin.js @@ -31,11 +31,44 @@ function setThemingValue(setting, value) { preview(setting, value); } +function calculateLuminance(rgb) { + var hexValue = rgb.replace(/[^0-9A-Fa-f]/,''); + var r,g,b; + if(hexValue.length === 3) { + hexValue = hexValue[0] + hexValue[0] + hexValue[1] + hexValue[1] + hexValue[2] + hexValue[2]; + } + if(hexValue.length !== 6) { + return 0; + } + r = parseInt(hexValue.substring(0,2), 16); + g = parseInt(hexValue.substring(2,4), 16); + b = parseInt(hexValue.substring(4,6), 16); + return (0.299*r + 0.587*g + 0.114*b)/255; +} + function preview(setting, value) { if (setting === 'color') { var headerClass = document.getElementById('header'); + var expandDisplayNameClass = document.getElementById('expandDisplayName'); + var headerAppName = headerClass.getElementsByClassName('header-appname')[0]; + var textColor, icon; + + if (calculateLuminance(value) > 0.5) { + textColor = "#000000"; + icon = 'caret-dark'; + } else { + textColor = "#ffffff"; + icon = 'caret'; + } + headerClass.style.background = value; headerClass.style.backgroundImage = '../img/logo-icon.svg'; + expandDisplayNameClass.style.color = textColor; + headerAppName.style.color = textColor; + + $(headerClass).find('.icon-caret').each(function() { + $(this).css('background-image','url(/core/img/actions/'+icon+'.svg)'); + }); } if (setting === 'logoMime') { console.log(setting); diff --git a/apps/theming/lib/controller/themingcontroller.php b/apps/theming/lib/controller/themingcontroller.php index a9ac36ca786..6eec5aafefa 100644 --- a/apps/theming/lib/controller/themingcontroller.php +++ b/apps/theming/lib/controller/themingcontroller.php @@ -30,6 +30,7 @@ use OCP\Files\IRootFolder; use OCP\IConfig; use OCP\IL10N; use OCP\IRequest; +use OCA\Theming\Util; /** * Class ThemingController @@ -231,6 +232,12 @@ class ThemingController extends Controller { background-image: url(\'./loginbackground?v='.$cacheBusterValue.'\'); }'; } + if(Util::invertTextColor($color)) { + $responseCss .= '#header .header-appname, #expandDisplayName { color: #000000; } '; + $responseCss .= '#header .icon-caret { background-image: url(/core/img/actions/caret-dark.svg); } '; + $responseCss .= '.searchbox input[type="search"] { background: transparent url(\'../../../core/img/actions/search.svg\') no-repeat 6px center; color: #000; }'; + $responseCss .= '.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid { color: #000; border: 1px solid rgba(0, 0, 0, .5); }'; + } \OC_Response::setExpiresHeader(gmdate('D, d M Y H:i:s', time() + (60*60*24*45)) . ' GMT'); \OC_Response::enableCaching(); diff --git a/apps/theming/lib/util.php b/apps/theming/lib/util.php new file mode 100644 index 00000000000..8ff5ae89b14 --- /dev/null +++ b/apps/theming/lib/util.php @@ -0,0 +1,57 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Theming; + +class Util { + + /** + * @param string $color rgb color value + * @return bool + */ + public static function invertTextColor($color) { + $l = self::calculateLuminance($color); + if($l>0.5) { + return true; + } else { + return false; + } + } + + /** + * @param string $color rgb color value + * @return float + */ + public static function calculateLuminance($color) { + $hex = preg_replace("/[^0-9A-Fa-f]/", '', $color); + if (strlen($hex) === 3) { + $hex = $hex{0} . $hex{0} . $hex{1} . $hex{1} . $hex{2} . $hex{2}; + } + if (strlen($hex) !== 6) { + return 0; + } + $r = hexdec(substr($hex, 0, 2)); + $g = hexdec(substr($hex, 2, 2)); + $b = hexdec(substr($hex, 4, 2)); + return (0.299 * $r + 0.587 * $g + 0.114 * $b)/255; + } + +} -- cgit v1.2.3 From 639be661c43c433b734f088c9ad123372f09e8d9 Mon Sep 17 00:00:00 2001 From: Julius Haertl Date: Fri, 15 Jul 2016 14:04:19 +0200 Subject: Theming: Add tests for inverted colors --- apps/theming/tests/lib/UtilTest.php | 66 +++++++++++++++++++++ .../tests/lib/controller/ThemingControllerTest.php | 67 +++++++++++++++++++++- 2 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 apps/theming/tests/lib/UtilTest.php (limited to 'apps') diff --git a/apps/theming/tests/lib/UtilTest.php b/apps/theming/tests/lib/UtilTest.php new file mode 100644 index 00000000000..266e6fc6c2d --- /dev/null +++ b/apps/theming/tests/lib/UtilTest.php @@ -0,0 +1,66 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +namespace OCA\Theming\Tests; + +use OCA\Theming\Util; +use Test\TestCase; + +class UtilTest extends TestCase { + + public function testInvertTextColorLight() { + $invert = Util::invertTextColor('#ffffff'); + $this->assertEquals(true, $invert); + } + + public function testInvertTextColorDark() { + $invert = Util::invertTextColor('#000000'); + $this->assertEquals(false, $invert); + } + + public function testCalculateLuminanceLight() { + $luminance = Util::calculateLuminance('#ffffff'); + $this->assertEquals(1, $luminance); + } + + public function testCalculateLuminanceDark() { + $luminance = Util::calculateLuminance('#000000'); + $this->assertEquals(0, $luminance); + } + + public function testCalculateLuminanceLightShorthand() { + $luminance = Util::calculateLuminance('#fff'); + $this->assertEquals(1, $luminance); + } + + public function testCalculateLuminanceDarkShorthand() { + $luminance = Util::calculateLuminance('#000'); + $this->assertEquals(0, $luminance); + } + public function testInvertTextColorInvalid() { + $invert = Util::invertTextColor('aaabbbcccddd123'); + $this->assertEquals(false, $invert); + } + + public function testInvertTextColorEmpty() { + $invert = Util::invertTextColor(''); + $this->assertEquals(false, $invert); + } +} diff --git a/apps/theming/tests/lib/controller/ThemingControllerTest.php b/apps/theming/tests/lib/controller/ThemingControllerTest.php index d08ebce8663..0975fc1459e 100644 --- a/apps/theming/tests/lib/controller/ThemingControllerTest.php +++ b/apps/theming/tests/lib/controller/ThemingControllerTest.php @@ -302,6 +302,33 @@ class ThemingControllerTest extends TestCase { } public function testGetStylesheetWithOnlyColor() { + $this->config + ->expects($this->at(0)) + ->method('getAppValue') + ->with('theming', 'cachebuster', '0') + ->willReturn('0'); + $this->config + ->expects($this->at(1)) + ->method('getAppValue') + ->with('theming', 'color', '') + ->willReturn('#000'); + $this->config + ->expects($this->at(2)) + ->method('getAppValue') + ->with('theming', 'logoMime', '') + ->willReturn(''); + $this->config + ->expects($this->at(3)) + ->method('getAppValue') + ->with('theming', 'backgroundMime', '') + ->willReturn(''); + + $expected = new Http\DataDownloadResponse('#body-user #header,#body-settings #header,#body-public #header {background-color: #000}', 'style', 'text/css'); + $expected->cacheFor(3600); + @$this->assertEquals($expected, $this->themingController->getStylesheet()); + } + + public function testGetStylesheetWithOnlyColorInvert() { $this->config ->expects($this->at(0)) ->method('getAppValue') @@ -323,7 +350,7 @@ class ThemingControllerTest extends TestCase { ->with('theming', 'backgroundMime', '') ->willReturn(''); - $expected = new Http\DataDownloadResponse('#body-user #header,#body-settings #header,#body-public #header {background-color: #fff}', 'style', 'text/css'); + $expected = new Http\DataDownloadResponse('#body-user #header,#body-settings #header,#body-public #header {background-color: #fff}#header .header-appname, #expandDisplayName { color: #000000; } #header .icon-caret { background-image: url(/core/img/actions/caret-dark.svg); } .searchbox input[type="search"] { background: transparent url(\'../../../core/img/actions/search.svg\') no-repeat 6px center; color: #000; }.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid { color: #000; border: 1px solid rgba(0, 0, 0, .5); }', 'style', 'text/css'); $expected->cacheFor(3600); @$this->assertEquals($expected, $this->themingController->getStylesheet()); } @@ -400,7 +427,7 @@ class ThemingControllerTest extends TestCase { ->expects($this->at(1)) ->method('getAppValue') ->with('theming', 'color', '') - ->willReturn('#abc'); + ->willReturn('#000'); $this->config ->expects($this->at(2)) ->method('getAppValue') @@ -412,7 +439,7 @@ class ThemingControllerTest extends TestCase { ->with('theming', 'backgroundMime', '') ->willReturn('image/png'); - $expected = new Http\DataDownloadResponse('#body-user #header,#body-settings #header,#body-public #header {background-color: #abc}#header .logo { + $expected = new Http\DataDownloadResponse('#body-user #header,#body-settings #header,#body-public #header {background-color: #000}#header .logo { background-image: url(\'./logo?v=0\'); } #header .logo-icon { @@ -424,5 +451,39 @@ class ThemingControllerTest extends TestCase { $expected->cacheFor(3600); @$this->assertEquals($expected, $this->themingController->getStylesheet()); } + public function testGetStylesheetWithAllCombinedInverted() { + $this->config + ->expects($this->at(0)) + ->method('getAppValue') + ->with('theming', 'cachebuster', '0') + ->willReturn('0'); + $this->config + ->expects($this->at(1)) + ->method('getAppValue') + ->with('theming', 'color', '') + ->willReturn('#fff'); + $this->config + ->expects($this->at(2)) + ->method('getAppValue') + ->with('theming', 'logoMime', '') + ->willReturn('text/svg'); + $this->config + ->expects($this->at(3)) + ->method('getAppValue') + ->with('theming', 'backgroundMime', '') + ->willReturn('image/png'); + + $expected = new Http\DataDownloadResponse('#body-user #header,#body-settings #header,#body-public #header {background-color: #fff}#header .logo { + background-image: url(\'./logo?v=0\'); + } + #header .logo-icon { + background-image: url(\'./logo?v=0\'); + background-size: 62px 34px; + }#body-login { + background-image: url(\'./loginbackground?v=0\'); + }#header .header-appname, #expandDisplayName { color: #000000; } #header .icon-caret { background-image: url(/core/img/actions/caret-dark.svg); } .searchbox input[type="search"] { background: transparent url(\'../../../core/img/actions/search.svg\') no-repeat 6px center; color: #000; }.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid { color: #000; border: 1px solid rgba(0, 0, 0, .5); }', 'style', 'text/css'); + $expected->cacheFor(3600); + @$this->assertEquals($expected, $this->themingController->getStylesheet()); + } } -- cgit v1.2.3 From 3f47138d2791d64817644c755e6ab763d26ebc7b Mon Sep 17 00:00:00 2001 From: Julius Haertl Date: Fri, 15 Jul 2016 14:45:05 +0200 Subject: Theming: Fix spaces in settings-admin.js --- apps/theming/js/settings-admin.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'apps') diff --git a/apps/theming/js/settings-admin.js b/apps/theming/js/settings-admin.js index 172c6bd31ca..ca915bc9ffe 100644 --- a/apps/theming/js/settings-admin.js +++ b/apps/theming/js/settings-admin.js @@ -32,12 +32,12 @@ function setThemingValue(setting, value) { } function calculateLuminance(rgb) { - var hexValue = rgb.replace(/[^0-9A-Fa-f]/,''); + var hexValue = rgb.replace(/[^0-9A-Fa-f]/, ''); var r,g,b; - if(hexValue.length === 3) { + if (hexValue.length === 3) { hexValue = hexValue[0] + hexValue[0] + hexValue[1] + hexValue[1] + hexValue[2] + hexValue[2]; } - if(hexValue.length !== 6) { + if (hexValue.length !== 6) { return 0; } r = parseInt(hexValue.substring(0,2), 16); @@ -74,7 +74,7 @@ function preview(setting, value) { console.log(setting); var logos = document.getElementsByClassName('logo-icon'); var timestamp = new Date().getTime(); - if(value !== '') { + if (value !== '') { logos[0].style.background = "url('" + OC.generateUrl('/apps/theming/logo') + "?v" + timestamp + "')"; logos[0].style.backgroundSize = "62px 34px"; } else { -- cgit v1.2.3 From 48ac845266fb4b4d9b3266ee58e622ac5965d0d3 Mon Sep 17 00:00:00 2001 From: Julius Haertl Date: Sat, 16 Jul 2016 08:56:40 +0200 Subject: Theming: Fix image paths for caret icon --- apps/theming/js/settings-admin.js | 2 +- apps/theming/lib/controller/themingcontroller.php | 4 ++-- apps/theming/tests/lib/controller/ThemingControllerTest.php | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'apps') diff --git a/apps/theming/js/settings-admin.js b/apps/theming/js/settings-admin.js index ca915bc9ffe..85e781411ed 100644 --- a/apps/theming/js/settings-admin.js +++ b/apps/theming/js/settings-admin.js @@ -67,7 +67,7 @@ function preview(setting, value) { headerAppName.style.color = textColor; $(headerClass).find('.icon-caret').each(function() { - $(this).css('background-image','url(/core/img/actions/'+icon+'.svg)'); + $(this).css('background-image', "url('" + OC.getRootPath() + '/core/img/actions/' + icon + ".svg')"); }); } if (setting === 'logoMime') { diff --git a/apps/theming/lib/controller/themingcontroller.php b/apps/theming/lib/controller/themingcontroller.php index 6eec5aafefa..303bb85f540 100644 --- a/apps/theming/lib/controller/themingcontroller.php +++ b/apps/theming/lib/controller/themingcontroller.php @@ -234,8 +234,8 @@ class ThemingController extends Controller { } if(Util::invertTextColor($color)) { $responseCss .= '#header .header-appname, #expandDisplayName { color: #000000; } '; - $responseCss .= '#header .icon-caret { background-image: url(/core/img/actions/caret-dark.svg); } '; - $responseCss .= '.searchbox input[type="search"] { background: transparent url(\'../../../core/img/actions/search.svg\') no-repeat 6px center; color: #000; }'; + $responseCss .= '#header .icon-caret { background-image: url(\'' . \OC::$WEBROOT . '/core/img/actions/caret-dark.svg\'); } '; + $responseCss .= '.searchbox input[type="search"] { background: transparent url(\'' . \OC::$WEBROOT . '/core/img/actions/search.svg\') no-repeat 6px center; color: #000; }'; $responseCss .= '.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid { color: #000; border: 1px solid rgba(0, 0, 0, .5); }'; } diff --git a/apps/theming/tests/lib/controller/ThemingControllerTest.php b/apps/theming/tests/lib/controller/ThemingControllerTest.php index 0975fc1459e..1ed82ab8b58 100644 --- a/apps/theming/tests/lib/controller/ThemingControllerTest.php +++ b/apps/theming/tests/lib/controller/ThemingControllerTest.php @@ -350,7 +350,7 @@ class ThemingControllerTest extends TestCase { ->with('theming', 'backgroundMime', '') ->willReturn(''); - $expected = new Http\DataDownloadResponse('#body-user #header,#body-settings #header,#body-public #header {background-color: #fff}#header .header-appname, #expandDisplayName { color: #000000; } #header .icon-caret { background-image: url(/core/img/actions/caret-dark.svg); } .searchbox input[type="search"] { background: transparent url(\'../../../core/img/actions/search.svg\') no-repeat 6px center; color: #000; }.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid { color: #000; border: 1px solid rgba(0, 0, 0, .5); }', 'style', 'text/css'); + $expected = new Http\DataDownloadResponse('#body-user #header,#body-settings #header,#body-public #header {background-color: #fff}#header .header-appname, #expandDisplayName { color: #000000; } #header .icon-caret { background-image: url(\'' . \OC::$WEBROOT . '/core/img/actions/caret-dark.svg\'); } .searchbox input[type="search"] { background: transparent url(\'' . \OC::$WEBROOT . '/core/img/actions/search.svg\') no-repeat 6px center; color: #000; }.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid { color: #000; border: 1px solid rgba(0, 0, 0, .5); }', 'style', 'text/css'); $expected->cacheFor(3600); @$this->assertEquals($expected, $this->themingController->getStylesheet()); } @@ -481,7 +481,7 @@ class ThemingControllerTest extends TestCase { background-size: 62px 34px; }#body-login { background-image: url(\'./loginbackground?v=0\'); - }#header .header-appname, #expandDisplayName { color: #000000; } #header .icon-caret { background-image: url(/core/img/actions/caret-dark.svg); } .searchbox input[type="search"] { background: transparent url(\'../../../core/img/actions/search.svg\') no-repeat 6px center; color: #000; }.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid { color: #000; border: 1px solid rgba(0, 0, 0, .5); }', 'style', 'text/css'); + }#header .header-appname, #expandDisplayName { color: #000000; } #header .icon-caret { background-image: url(\'' . \OC::$WEBROOT . '/core/img/actions/caret-dark.svg\'); } .searchbox input[type="search"] { background: transparent url(\'' . \OC::$WEBROOT . '/core/img/actions/search.svg\') no-repeat 6px center; color: #000; }.searchbox input[type="search"]:focus,.searchbox input[type="search"]:active,.searchbox input[type="search"]:valid { color: #000; border: 1px solid rgba(0, 0, 0, .5); }', 'style', 'text/css'); $expected->cacheFor(3600); @$this->assertEquals($expected, $this->themingController->getStylesheet()); } -- cgit v1.2.3