Browse Source

Implement XEP-0392: Consistent Color Generation

Refactor the color system and adapt the internal palette
pull/1431/head
Timothée Jaussoin 8 months ago
parent
commit
ff6e9db071
  1. 2
      CHANGELOG.md
  2. 5
      app/Contact.php
  3. 41
      app/Helpers/StringHelper.php
  4. 7
      app/Member.php
  5. 6
      app/Message.php
  6. 2
      app/MujiCallParticipant.php
  7. 13
      app/Post.php
  8. 11
      app/Presence.php
  9. 2
      app/User.php
  10. 2
      app/Widgets/Avatar/_avatar.tpl
  11. 2
      app/Widgets/Blog/blog.tpl
  12. 2
      app/Widgets/Chat/Chat.php
  13. 5
      app/Widgets/Config/config.css
  14. 2
      app/Widgets/ContactActions/_contactactions_drawer.tpl
  15. 2
      app/Widgets/ContactHeader/_contactheader.tpl
  16. 4
      app/Widgets/Post/_post.tpl
  17. 4
      app/Widgets/RoomsUtils/_rooms_drawer.tpl
  18. 8
      doap.xml
  19. 77
      public/theme/css/color.css

2
CHANGELOG.md

@ -7,6 +7,8 @@ v0.30.1 (master)
* Remove HTTP ranges in the Picture proxy and simply stop Curl when the download is above the declared file size, small refactor
* Redesign the avatar and banner edition layout
* Enable animated WebP pictures support in the images proxyfier
* Implement XEP-0392: Consistent Color Generation
* Refactor the color system and adapt the internal palette
v0.30
---------------------------

5
app/Contact.php

@ -40,6 +40,11 @@ class Contact extends Model
->where('id', '!=', User::me()->id);
}
public function getColorAttribute(): string
{
return stringToColor($this->jid);
}
public function scopeOrderByPresence($query)
{
return $query->leftJoin(DB::raw('(

41
app/Helpers/StringHelper.php

@ -325,14 +325,22 @@ function isLongitude(float $longitude): bool
}
/**
* @desc Return a color generated from the string
* @desc XEP-0392: Consistent Color Generation
*/
function stringToColor($string): string
{
// Get the Hue angle from the XEP definition
$arr = unpack('C*' ,hex2bin(hash('sha1', $string)));
$angle = (($arr[1] + $arr[2] * 256) / 65536.0) * 360;
$colors = array_keys(palette());
$s = abs(crc32($string));
return $colors[$s % count($colors)];
// Pick the closest color from the palette
$color = round($angle / (360 / count($colors)));
if ($color == 16) $color = 15;
return $colors[$color];
}
/**
@ -341,23 +349,22 @@ function stringToColor($string): string
function palette(bool $withBlack = false): array
{
$palette = [
'red' => '#F44336',
'purple' => '#9C27B0',
'indigo' => '#3F51B5',
'blue' => '#2196F3',
'green' => '#689F38',
'dorange' => '#FF5722',
'orange' => '#FF9800',
'yellow' => '#FDD835',
'brown' => '#795548',
'gray' => '#9E9E9E',
'amber' => '#FFC107',
'yellow' => '#FFEB3B',
'lime' => '#CDDC39',
'cyan' => '#00bcd4',
'lgreen' => '#8BC34A',
'green' => '#4CAF50',
'teal' => '#009688',
'pink' => '#e91e63',
'dorange' => '#ff5722',
'lblue' => '#03a9f4',
'amber' => '#ffc107',
'bgray' => '#607d8b',
'cyan' => '#00BCD4',
'lblue' => '#03A9F4',
'blue' => '#2196F3',
'indigo' => '#3F51B5',
'dpurple' => '#673AB7',
'purple' => '#9C27B0',
'pink' => '#E91E63',
'red' => '#F44336',
];
if ($withBlack) return $palette + ['black' => '#000000'];

7
app/Member.php

@ -25,7 +25,7 @@ class Member extends Model
return $this->hasOne('App\Contact', 'id', 'jid');
}
public function getTruenameAttribute()
public function getTruenameAttribute(): string
{
if ($this->contact && $this->contact->truename) {
return $this->contact->truename;
@ -33,4 +33,9 @@ class Member extends Model
return explodeJid($this->jid)['username'] ?? $this->jid;
}
public function getColorAttribute(): string
{
return stringToColor($this->jid);
}
}

6
app/Message.php

@ -729,11 +729,9 @@ class Message extends Model
}
}
public function resolveColor()
public function resolveColor(): string
{
$this->color = stringToColor(
$this->resource . $this->type
);
$this->color = stringToColor($this->resource);
return $this->color;
}

2
app/MujiCallParticipant.php

@ -47,6 +47,6 @@ class MujiCallParticipant extends Model
public function getConferencePictureAttribute(): string
{
return Image::getOrCreate($this->jid, 120) ?? avatarPlaceholder($this->jid . 'groupchat');
return Image::getOrCreate($this->jid, 120) ?? avatarPlaceholder($this->jid);
}
}

13
app/Post.php

@ -318,6 +318,19 @@ class Post extends Model
return $query;
}
public function getColorAttribute(): string
{
if ($this->contact) {
return $this->contact->color;
}
if ($this->aid) {
return stringToColor($this->aid);
}
return stringToColor($this->node);
}
public function getPreviousAttribute(): ?Post
{
return \App\Post::where('server', $this->server)

11
app/Presence.php

@ -84,18 +84,15 @@ class Presence extends Model
public function getConferencePictureAttribute(): string
{
return Image::getOrCreate($this->mucjid, 120) ?? avatarPlaceholder($this->resource . 'groupchat');
return Image::getOrCreate($this->mucjid, 120) ?? avatarPlaceholder($this->resource);
}
public function getConferenceColorAttribute()
public function getConferenceColorAttribute(): string
{
return stringToColor(
$this->resource . 'groupchat'
);
return stringToColor($this->resource);
}
public static function findByStanza($stanza)
public static function findByStanza($stanza): Presence
{
$temporary = new self;
$temporary->set($stanza);

2
app/User.php

@ -29,7 +29,7 @@ class User extends Model
'notifications_since' => 'datetime:Y-m-d H:i:s',
];
public const ACCENT_COLORS = ['blue', 'teal', 'green', 'dorange', 'red', 'pink', 'purple'];
public const ACCENT_COLORS = ['red', 'pink', 'purple', 'dpurple', 'indigo', 'blue', 'cyan', 'teal', 'green', 'lgreen', 'orange', 'dorange'];
public function save(array $options = [])
{

2
app/Widgets/Avatar/_avatar.tpl

@ -2,7 +2,7 @@
<ul class="list thick">
<li class="block">
<span
class="primary icon bubble color {$me->jid|stringToColor}"
class="primary icon bubble color {$me->color}"
style="background-image: url({$me->getPicture()})">
</span>
<div>

2
app/Widgets/Blog/blog.tpl

@ -2,7 +2,7 @@
{if="$mode == 'blog'"}
{if="$contact && $contact->isPublic()"}
{$banner = $contact->getBanner()}
<header class="big top color {if="$contact"}{$contact->jid|stringToColor}{/if}"
<header class="big top color {if="$contact"}{$contact->color}{/if}"
style="
background-image:
linear-gradient(to top, rgba(23,23,23,0.9) 0, rgba(23,23,23,0.6) 5rem, rgba(23,23,23,0) 12rem)

2
app/Widgets/Chat/Chat.php

@ -1524,7 +1524,7 @@ class Chat extends \Movim\Widget\Base
$message->icon_url = $this->_mucPresences[$key]->conferencePicture;
} else {
// No presence, we set a placeholder avatar as a fallback
$message->icon_url = avatarPlaceholder($message->resource . 'groupchat');
$message->icon_url = avatarPlaceholder($message->resource);
$this->_mucPresences[$key] = true;
}
}

5
app/Widgets/Config/config.css

@ -11,6 +11,7 @@ form div > span.supporting.night_mode_detected {
form li#accent_color div.radio {
display: inline-block;
margin-right: 0.5rem;
line-height: 5rem;
}
form li#accent_color div.radio label {
@ -22,7 +23,3 @@ form li#accent_color div.radio label {
form li#accent_color div.radio input[type=radio] {
display: none;
}
form li#accent_color > div {
height: 9rem;
}

2
app/Widgets/ContactActions/_contactactions_drawer.tpl

@ -1,5 +1,5 @@
<section>
<header class="big color {$contact->jid|stringToColor}"
<header class="big color {$contact->color}"
style="background-image: linear-gradient(to bottom, rgba(23,23,23,0.8) 0%, rgba(23,23,23,0.5) 100%), url('{$contact->getBanner(\Movim\ImageSize::XXL)}');"
>
<ul class="list thick">

2
app/Widgets/ContactHeader/_contactheader.tpl

@ -4,7 +4,7 @@
</a>
{/if}
<header class="big top color {$contact->jid|stringToColor}"
<header class="big top color {$contact->color}"
style="
background-image:
linear-gradient(to top, rgba(23,23,23,0.9) 0, rgba(23,23,23,0.6) 5rem, rgba(23,23,23,0) 12rem), url('{$contact->getBanner(\Movim\ImageSize::XXL)}');

4
app/Widgets/Post/_post.tpl

@ -50,7 +50,7 @@
</a>
</span>
{else}
<span class="icon primary bubble color {$post->aid|stringToColor}">
<span class="icon primary bubble color {$post->color}">
<a href="#" onclick="Post_ajaxGetContact('{$post->aid}')">
<i class="material-symbols">person</i>
</a>
@ -64,7 +64,7 @@
<img src="{$post->info->getPicture(\Movim\ImageSize::L)}"/>
</span>
{else}
<span class="primary icon bubble color {$post->node|stringToColor} active"
<span class="primary icon bubble color {$post->color} active"
onclick="MovimUtils.reload('{$c->route('community', [$post->server, $post->node])}')"
>
{$post->node|firstLetterCapitalize}

4
app/Widgets/RoomsUtils/_rooms_drawer.tpl

@ -222,7 +222,7 @@
<img loading="lazy" src="{$value->contact->getPicture(\Movim\ImageSize::S)}">
</span>
{else}
<span class="primary icon bubble small color {$value->jid|stringToColor} status">
<span class="primary icon bubble small color {$value->color} status">
<i class="material-symbols">people</i>
</span>
{/if}
@ -311,7 +311,7 @@
<img loading="lazy" src="{$value->contact->getPicture(\Movim\ImageSize::S)}">
</span>
{else}
<span class="primary icon bubble small color {$value->jid|stringToColor}">
<span class="primary icon bubble small color {$value->color}">
<i class="material-symbols">people</i>
</span>
{/if}

8
doap.xml

@ -535,6 +535,14 @@
<xmpp:since>0.24</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0392.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.0.0</xmpp:version>
<xmpp:since>0.30.1</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0393.html"/>

77
public/theme/css/color.css

@ -102,65 +102,52 @@ span.icon span.counter.alt {
/* Elements */
.button.red, .icon.red , span.resource.red, p.encrypted.error { color: var(--p-red); }
.button.purple, .icon.purple, span.resource.purple { color: var(--p-purple); }
.button.indigo, .icon.indigo, span.resource.indigo { color: var(--p-indigo); }
.button.blue, .icon.blue , span.resource.blue { color: var(--p-blue); }
.button.green, .icon.green , span.resource.green { color: var(--p-green); }
ul li.action > div.action,
ul li.action > form > div.action,
.button.dorange,.icon.dorange , span.resource.dorange { color: var(--p-dorange) }
li div.bubble.moderator:after,
ul li div > p > span.moderator,
.button.orange, .icon.orange, span.resource.orange { color: var(--p-orange); }
.button.amber, .icon.anber , span.resource.amber { color: var(--p-amber) }
.button.yellow, .icon.yellow, span.resource.yellow { color: var(--p-yellow); }
.button.brown, .icon.brown , span.resource.brown { color: var(--p-brown); }
ul li.action > div.action,
ul li.action > form > div.action,
.button.gray, .icon.gray , span.resource.gray { color: var(--p-gray); }
.button.black, .icon.black , span.resource.black { color: var(--p-black); }
.button.lime, .icon.lime , span.resource.lime { color: var(--p-lime) }
.button.cyan, .icon.cyan , span.resource.cyan { color: var(--p-cyan) }
.button.lgreen, .icon.lgreen, span.resource.lgreen { color: var(--p-lgreen); }
.button.green, .icon.green , span.resource.green { color: var(--p-green); }
.button.teal, .icon.teal , span.resource.teal { color: var(--p-teal) }
.button.pink, .icon.pink , span.resource.pink { color: var(--p-pink) }
.button.dorange,.icon.dorange , span.resource.dorange { color: var(--p-dorange) }
.button.cyan, .icon.cyan , span.resource.cyan { color: var(--p-cyan) }
.button.lblue, .icon.lblue , span.resource.lblue { color: var(--p-lblue) }
.button.amber, .icon.anber , span.resource.amber { color: var(--p-amber) }
.button.bgray, .icon.bgray , span.resource.bgray { color: var(--p-bgray) }
.button.blue, .icon.blue , span.resource.blue { color: var(--p-blue); }
.button.red, .icon.red , span.resource.red, p.encrypted.error { color: var(--p-red); }
.button.indigo, .icon.indigo, span.resource.indigo { color: var(--p-indigo); }
.button.dpurple,.icon.dpurple,span.resource.dpurple { color: var(--p-dpurple); }
.button.purple, .icon.purple, span.resource.purple { color: var(--p-purple); }
.button.pink, .icon.pink , span.resource.pink { color: var(--p-pink) }
.button.black, .icon.black , span.resource.black { color: var(--p-black); }
.color { color: white; background-color: var(--p-bgray); border-color: var(--p-bgray) }
span.icon:not(.composing):not([data-counter]):not(.story).status.dnd:after,
.color.red { color: white; background-color: var(--p-red); border-color: var(--p-red) }
span.icon:not(.composing):not([data-counter]):not(.story).status.xa:after,
.color.slack,
.color.discord,
.color.purple { color: white; background-color: var(--p-purple); border-color: var(--p-purple) }
.color.signal,
.color.mattermost,
.color.indigo { color: white; background-color: var(--p-indigo); border-color: var(--p-indigo) }
.color.facebook,
.color.blue { color: white; background-color: var(--p-blue); border-color: var(--p-blue) }
span.icon:not(.composing):not([data-counter]):not(.story).status.chat:after,
span.icon:not(.composing):not([data-counter]):not(.story).status.online:after,
.color.xmpp,
.color.green { color: white; background-color: var(--p-green); border-color: var(--p-green) }
.color,
.color.dorange { color: white; background-color: var(--p-dorange); border-color: var(--p-dorange) }
.color.orange { color: white; background-color: var(--p-orange); border-color: var(--p-orange) }
.color.amber { color: white; background-color: var(--p-amber); border-color: var(--p-amber) }
span.icon:not(.composing):not([data-counter]):not(.story).status.away:after,
.color.yellow { color: white; background-color: var(--p-yellow); border-color: var(--p-yellow) }
span.icon:not(.composing):not([data-counter]):not(.story).status.server_error:after,
.color.brown { color: white; background-color: var(--p-brown); border-color: var(--p-brown) }
.color.gray { color: white; background-color: var(--p-gray); border-color: var(--p-gray) }
.color.black { color: white; background-color: var(--p-black); border-color: var(--p-black) }
.color.lime { color: white; background-color: var(--p-lime); border-color: var(--p-lime) }
.color.telegram,
.color.cyan { color: white; background-color: var(--p-cyan); border-color: var(--p-cyan) }
.color.whatsapp,
span.icon:not(.composing):not([data-counter]):not(.story).status.chat:after,
span.icon:not(.composing):not([data-counter]):not(.story).status.online:after,
.color.lgreen { color: white; background-color: var(--p-lgreen); border-color: var(--p-lgreen) }
.color.green { color: white; background-color: var(--p-green); border-color: var(--p-green) }
.color.teal { color: white; background-color: var(--p-teal); border-color: var(--p-teal) }
.color.pink { color: white; background-color: var(--p-pink); border-color: var(--p-pink) }
.color.hackernews,
.color.dorange { color: white; background-color: var(--p-dorange); border-color: var(--p-dorange) }
.color.skype,
.color.cyan { color: white; background-color: var(--p-cyan); border-color: var(--p-cyan) }
.color.lblue { color: white; background-color: var(--p-lblue); border-color: var(--p-lblue) }
.color.amber { color: white; background-color: var(--p-amber); border-color: var(--p-amber) }
.color.steam,
.color.bgray { color: white; background-color: var(--p-bgray); border-color: var(--p-bgray) }
.color.blue { color: white; background-color: var(--p-blue); border-color: var(--p-blue) }
span.icon:not(.composing):not([data-counter]):not(.story).status.server_error:after,
.color.indigo { color: white; background-color: var(--p-indigo); border-color: var(--p-indigo) }
.color.dpurple { color: white; background-color: var(--p-dpurple); border-color: var(--p-dpurple) }
span.icon:not(.composing):not([data-counter]):not(.story).status.xa:after,
.color.purple { color: white; background-color: var(--p-purple); border-color: var(--p-purple) }
.color.pink { color: white; background-color: var(--p-pink); border-color: var(--p-pink) }
span.icon:not(.composing):not([data-counter]):not(.story).status.dnd:after,
.color.red { color: white; background-color: var(--p-red); border-color: var(--p-red) }
.color.black { color: white; background-color: var(--p-black); border-color: var(--p-black) }
.color.none { color: white; background-color: transparent; }
.color.transparent { color: white; backdrop-filter: blur(1rem); background-color: rgba(0, 0, 0, 0.25); }

Loading…
Cancel
Save