Added letter based avatar provider

This commit is contained in:
Frederic Guillot 2016-03-19 11:17:58 -04:00
parent fa86542f90
commit 9d4ba1471d
11 changed files with 238 additions and 9 deletions

View File

@ -3,6 +3,7 @@ Version 1.0.27 (unreleased)
Improvements: Improvements:
* Added letter avatar provider
* Added pluggable Avatar providers * Added pluggable Avatar providers
* Improve task summary sections * Improve task summary sections
* Put back the action sidebar in task view * Put back the action sidebar in task view

View File

@ -1,6 +1,6 @@
BUILD_DIR = /tmp BUILD_DIR = /tmp
CSS_APP = $(addprefix assets/css/src/, $(addsuffix .css, base links title table form button alert tooltip header board task comment subtask tasklink markdown listing activity dashboard pagination popover confirm sidebar responsive dropdown upload filters gantt project files views accordion)) CSS_APP = $(addprefix assets/css/src/, $(addsuffix .css, base links title table form button alert tooltip header board task comment subtask tasklink markdown listing activity dashboard pagination popover confirm sidebar responsive dropdown upload filters gantt project files views accordion avatar))
CSS_PRINT = $(addprefix assets/css/src/, $(addsuffix .css, print links table board task comment subtask tasklink markdown)) CSS_PRINT = $(addprefix assets/css/src/, $(addsuffix .css, print links table board task comment subtask tasklink markdown))
CSS_VENDOR = $(addprefix assets/css/vendor/, $(addsuffix .css, jquery-ui.min jquery-ui-timepicker-addon.min chosen.min fullcalendar.min font-awesome.min c3.min)) CSS_VENDOR = $(addprefix assets/css/vendor/, $(addsuffix .css, jquery-ui.min jquery-ui-timepicker-addon.min chosen.min fullcalendar.min font-awesome.min c3.min))

View File

@ -5,7 +5,7 @@ namespace Kanboard\Core\User\Avatar;
/** /**
* Avatar Manager * Avatar Manager
* *
* @package user * @package avatar
* @author Frederic Guillot * @author Frederic Guillot
*/ */
class AvatarManager class AvatarManager
@ -51,6 +51,8 @@ class AvatarManager
'email' => $email, 'email' => $email,
); );
krsort($this->providers);
foreach ($this->providers as $provider) { foreach ($this->providers as $provider) {
if ($provider->isActive($user)) { if ($provider->isActive($user)) {
return $provider->render($user, $size); return $provider->render($user, $size);

View File

@ -171,7 +171,7 @@ class UserHelper extends Base
*/ */
public function avatar($user_id, $username, $name, $email) public function avatar($user_id, $username, $name, $email)
{ {
$html = $this->avatarManager->render($user_id, $username, $name, $email, 25); $html = $this->avatarManager->render($user_id, $username, $name, $email, 48);
return '<div class="avatar">'.$html.'</div>'; return '<div class="avatar">'.$html.'</div>';
} }
} }

View File

@ -6,6 +6,7 @@ use Pimple\Container;
use Pimple\ServiceProviderInterface; use Pimple\ServiceProviderInterface;
use Kanboard\Core\User\Avatar\AvatarManager; use Kanboard\Core\User\Avatar\AvatarManager;
use Kanboard\User\Avatar\GravatarProvider; use Kanboard\User\Avatar\GravatarProvider;
use Kanboard\User\Avatar\LetterAvatarProvider;
/** /**
* Avatar Provider * Avatar Provider
@ -25,6 +26,7 @@ class AvatarProvider implements ServiceProviderInterface
public function register(Container $container) public function register(Container $container)
{ {
$container['avatarManager'] = new AvatarManager; $container['avatarManager'] = new AvatarManager;
$container['avatarManager']->register(new LetterAvatarProvider($container));
$container['avatarManager']->register(new GravatarProvider($container)); $container['avatarManager']->register(new GravatarProvider($container));
return $container; return $container;
} }

View File

@ -5,6 +5,12 @@ namespace Kanboard\User\Avatar;
use Kanboard\Core\Base; use Kanboard\Core\Base;
use Kanboard\Core\User\Avatar\AvatarProviderInterface; use Kanboard\Core\User\Avatar\AvatarProviderInterface;
/**
* Gravatar Avatar Provider
*
* @package avatar
* @author Frederic Guillot
*/
class GravatarProvider extends Base implements AvatarProviderInterface class GravatarProvider extends Base implements AvatarProviderInterface
{ {
/** /**

View File

@ -0,0 +1,169 @@
<?php
namespace Kanboard\User\Avatar;
use Kanboard\Core\Base;
use Kanboard\Core\User\Avatar\AvatarProviderInterface;
/**
* Letter Avatar Provider
*
* The color hash algorithm is backported from the Javascript library color-hash
* Source: https://github.com/zenozeng/color-hash
* Author: Zeno Zeng (MIT License)
*
* @package avatar
* @author Frederic Guillot
*/
class LetterAvatarProvider extends Base implements AvatarProviderInterface
{
protected $lightness = array(0.35, 0.5, 0.65);
protected $saturation = array(0.35, 0.5, 0.65);
/**
* Render avatar html
*
* @access public
* @param array $user
* @param int $size
*/
public function render(array $user, $size)
{
$initials = $this->helper->user->getInitials($user['name'] ?: $user['username']);
$rgb = $this->getBackgroundColor($initials);
return sprintf(
'<div class="avatar-letter" style="background-color: rgb(%d, %d, %d)" title="%s">%s</div>',
$rgb[0],
$rgb[1],
$rgb[2],
$this->helper->text->e($user['name'] ?: $user['username']),
$initials
);
}
/**
* Determine if the provider is active
*
* @access public
* @param array $user
* @return boolean
*/
public function isActive(array $user)
{
return true;
}
/**
* Get background color based on a string
*
* @param string $str
* @return array
*/
public function getBackgroundColor($str)
{
$hsl = $this->getHSL($str);
return $this->getRGB($hsl[0], $hsl[1], $hsl[2]);
}
/**
* Convert HSL to RGB
*
* @access protected
* @param integer $hue Hue [0, 360)
* @param integer $saturation Saturation [0, 1]
* @param integer $lightness Lightness [0, 1]
* @return array
*/
protected function getRGB($hue, $saturation, $lightness)
{
$hue /= 360;
$q = $lightness < 0.5 ? $lightness * (1 + $saturation) : $lightness + $saturation - $lightness * $saturation;
$p = 2 * $lightness - $q;
return array_map(function ($color) use ($q, $p) {
if ($color < 0) {
$color++;
}
if ($color > 1) {
$color--;
}
if ($color < 1/6) {
$color = $p + ($q - $p) * 6 * $color;
} else if ($color < 0.5) {
$color = $q;
} else if ($color < 2/3) {
$color = $p + ($q - $p) * 6 * (2/3 - $color);
} else {
$color = $p;
}
return round($color * 255);
}, array($hue + 1/3, $hue, $hue - 1/3));
}
/**
* Returns the hash in [h, s, l].
* Note that H [0, 360); S [0, 1]; L [0, 1];
*
* @access protected
* @param string $str
* @return int[]
*/
protected function getHSL($str)
{
$hash = $this->hash($str);
$hue = $hash % 359;
$hash = intval($hash / 360);
$saturation = $this->saturation[$hash % count($this->saturation)];
$hash = intval($hash / count($this->saturation));
$lightness = $this->lightness[$hash % count($this->lightness)];
return array($hue, $saturation, $lightness);
}
/**
* BKDR Hash (modified version)
*
* @access protected
* @param string $str
* @return integer
*/
protected function hash($str)
{
$seed = 131;
$seed2 = 137;
$hash = 0;
// Make hash more sensitive for short string like 'a', 'b', 'c'
$str .= 'x';
$max = intval(9007199254740991 / $seed2);
for ($i = 0, $ilen = mb_strlen($str); $i < $ilen; $i++) {
if ($hash > $max) {
$hash = intval($hash / $seed2);
}
$hash = $hash * $seed + $this->getCharCode($str[$i]);
}
return $hash;
}
/**
* Backport of Javascript function charCodeAt()
*
* @access protected
* @param string $c
* @return integer
*/
protected function getCharCode($c)
{
list(, $ord) = unpack('N', mb_convert_encoding($c, 'UCS-4BE', 'UTF-8'));
return $ord;
}
}

File diff suppressed because one or more lines are too long

17
assets/css/src/avatar.css Normal file
View File

@ -0,0 +1,17 @@
.avatar {
float: left;
margin-right: 10px;
}
.avatar img,
.avatar div {
border-radius: 30px;
}
.avatar-letter {
color: #fff;
line-height: 48px;
width: 48px;
font-size: 25px;
text-align: center;
}

View File

@ -47,11 +47,6 @@ hr {
min-height: 27px; /* Reserve some space to avoid re-layout due to chosen */ min-height: 27px; /* Reserve some space to avoid re-layout due to chosen */
} }
.avatar {
float: left;
margin-right: 10px;
}
#ui-datepicker-div { #ui-datepicker-div {
font-size: 0.8em; font-size: 0.8em;
} }

View File

@ -0,0 +1,37 @@
<?php
require_once __DIR__.'/../../Base.php';
use Kanboard\User\Avatar\LetterAvatarProvider;
class LetterAvatarProviderTest extends Base
{
public function testGetBackgroundColor()
{
$provider = new LetterAvatarProvider($this->container);
$rgb = $provider->getBackgroundColor('Test');
$this->assertEquals(array(107, 83, 172), $rgb);
}
public function testIsActive()
{
$provider = new LetterAvatarProvider($this->container);
$this->assertTrue($provider->isActive(array()));
}
public function testRenderWithFullName()
{
$provider = new LetterAvatarProvider($this->container);
$user = array('id' => 123, 'name' => 'Kanboard Admin', 'username' => 'bob', 'email' => '');
$expected = '<div class="avatar-letter" style="background-color: rgb(187, 224, 108)" title="Kanboard Admin">KA</div>';
$this->assertEquals($expected, $provider->render($user, 48));
}
public function testRenderWithUsername()
{
$provider = new LetterAvatarProvider($this->container);
$user = array('id' => 123, 'name' => '', 'username' => 'admin', 'email' => '');
$expected = '<div class="avatar-letter" style="background-color: rgb(210, 97, 45)" title="admin">A</div>';
$this->assertEquals($expected, $provider->render($user, 48));
}
}