Merge pull request #412 from nextcloud/theming-foreground-color
Theming: invert foreground color on bright backgrounds
This commit is contained in:
commit
89a32a2f84
|
@ -31,17 +31,50 @@ 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('" + OC.getRootPath() + '/core/img/actions/' + icon + ".svg')");
|
||||
});
|
||||
}
|
||||
if (setting === 'logoMime') {
|
||||
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 {
|
||||
|
|
|
@ -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(\'' . \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); }';
|
||||
}
|
||||
|
||||
\OC_Response::setExpiresHeader(gmdate('D, d M Y H:i:s', time() + (60*60*24*45)) . ' GMT');
|
||||
\OC_Response::enableCaching();
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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(\'' . \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());
|
||||
}
|
||||
|
@ -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(\'' . \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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -324,6 +324,9 @@
|
|||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
|
||||
opacity: 1;
|
||||
}
|
||||
#expand .icon-caret {
|
||||
margin-top: 0;
|
||||
}
|
||||
#expanddiv {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
|
|
|
@ -73,7 +73,7 @@
|
|||
</div>
|
||||
<?php endif; ?>
|
||||
<span id="expandDisplayName"><?php p(trim($_['user_displayname']) != '' ? $_['user_displayname'] : $_['user_uid']) ?></span>
|
||||
<img alt="" src="<?php print_unescaped(image_path('', 'actions/caret.svg')); ?>">
|
||||
<div class="icon-caret"></div>
|
||||
</div>
|
||||
<div id="expanddiv">
|
||||
<ul>
|
||||
|
|
Loading…
Reference in New Issue