From 44cc691c799ed1caae8353c792d9d326c818bbb2 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 13 Aug 2020 12:41:00 +0200 Subject: [PATCH] add avatar and sender user colors --- src/domain/session/avatar.js | 40 ++++++++++++++++++- src/domain/session/room/RoomViewModel.js | 6 ++- .../room/timeline/tiles/MessageTile.js | 5 +++ .../session/roomlist/RoomTileViewModel.js | 6 ++- src/ui/web/css/themes/element/theme.css | 27 +++++++++++++ src/ui/web/session/RoomTile.js | 4 +- src/ui/web/session/room/RoomView.js | 2 +- src/ui/web/session/room/timeline/ImageView.js | 10 ++--- .../session/room/timeline/TextMessageView.js | 9 ++--- src/ui/web/session/room/timeline/common.js | 14 +++++++ 10 files changed, 102 insertions(+), 21 deletions(-) create mode 100644 src/ui/web/session/room/timeline/common.js diff --git a/src/domain/session/avatar.js b/src/domain/session/avatar.js index 355a6cf1..9184762b 100644 --- a/src/domain/session/avatar.js +++ b/src/domain/session/avatar.js @@ -15,6 +15,42 @@ limitations under the License. */ export function avatarInitials(name) { - const words = name.split(" ").slice(0, 2); - return words.reduce((i, w) => i + w.charAt(0).toUpperCase(), ""); + let words = name.split(" "); + if (words.length === 1) { + words = words[0].split("-"); + } + words = words.slice(0, 2); + return words.reduce((i, w) => { + let firstChar = w.charAt(0); + if (firstChar === "!" || firstChar === "@" || firstChar === "#") { + firstChar = w.charAt(1); + } + return i + firstChar.toUpperCase(); + }, ""); +} + +/** + * calculates a numeric hash for a given string + * + * @param {string} str string to hash + * + * @return {number} + */ +function hashCode(str) { + let hash = 0; + let i; + let chr; + if (str.length === 0) { + return hash; + } + for (i = 0; i < str.length; i++) { + chr = str.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; + hash |= 0; + } + return Math.abs(hash); +} + +export function getIdentifierColorNumber(id) { + return (hashCode(id) % 8) + 1; } diff --git a/src/domain/session/room/RoomViewModel.js b/src/domain/session/room/RoomViewModel.js index d27f3737..af163c5f 100644 --- a/src/domain/session/room/RoomViewModel.js +++ b/src/domain/session/room/RoomViewModel.js @@ -15,7 +15,7 @@ limitations under the License. */ import {TimelineViewModel} from "./timeline/TimelineViewModel.js"; -import {avatarInitials} from "../avatar.js"; +import {avatarInitials, getIdentifierColorNumber} from "../avatar.js"; import {ViewModel} from "../../ViewModel.js"; export class RoomViewModel extends ViewModel { @@ -90,7 +90,9 @@ export class RoomViewModel extends ViewModel { return avatarInitials(this._room.name); } - + get avatarColorNumber() { + return getIdentifierColorNumber(this._room.id) + } async _sendMessage(message) { if (message) { diff --git a/src/domain/session/room/timeline/tiles/MessageTile.js b/src/domain/session/room/timeline/tiles/MessageTile.js index ffae90b8..e74a26a1 100644 --- a/src/domain/session/room/timeline/tiles/MessageTile.js +++ b/src/domain/session/room/timeline/tiles/MessageTile.js @@ -15,6 +15,7 @@ limitations under the License. */ import {SimpleTile} from "./SimpleTile.js"; +import {getIdentifierColorNumber} from "../../../avatar.js"; export class MessageTile extends SimpleTile { constructor(options) { @@ -32,6 +33,10 @@ export class MessageTile extends SimpleTile { return this._entry.sender; } + get senderColorNumber() { + return getIdentifierColorNumber(this._entry.sender); + } + get date() { return this._date.toLocaleDateString({}, {month: "numeric", day: "numeric"}); } diff --git a/src/domain/session/roomlist/RoomTileViewModel.js b/src/domain/session/roomlist/RoomTileViewModel.js index d1585262..a10ecc9c 100644 --- a/src/domain/session/roomlist/RoomTileViewModel.js +++ b/src/domain/session/roomlist/RoomTileViewModel.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {avatarInitials} from "../avatar.js"; +import {avatarInitials, getIdentifierColorNumber} from "../avatar.js"; import {ViewModel} from "../../ViewModel.js"; export class RoomTileViewModel extends ViewModel { @@ -60,4 +60,8 @@ export class RoomTileViewModel extends ViewModel { get avatarInitials() { return avatarInitials(this._room.name); } + + get avatarColorNumber() { + return getIdentifierColorNumber(this._room.id) + } } diff --git a/src/ui/web/css/themes/element/theme.css b/src/ui/web/css/themes/element/theme.css index 1af36ee3..1cf68963 100644 --- a/src/ui/web/css/themes/element/theme.css +++ b/src/ui/web/css/themes/element/theme.css @@ -21,6 +21,15 @@ limitations under the License. font-family: 'Inter', sans-serif, 'emoji'; background-color: white; color: #2e2f32; + + --usercolor1: #368BD6; + --usercolor2: #AC3BA8; + --usercolor3: #03B381; + --usercolor4: #E64F7A; + --usercolor5: #FF812D; + --usercolor6: #2DC2C5; + --usercolor7: #5C56F5; + --usercolor8: #74D12C; } .avatar { @@ -30,6 +39,15 @@ limitations under the License. color: white; } +.hydrogen .avatar.usercolor1 { background-color: var(--usercolor1); } +.hydrogen .avatar.usercolor2 { background-color: var(--usercolor2); } +.hydrogen .avatar.usercolor3 { background-color: var(--usercolor3); } +.hydrogen .avatar.usercolor4 { background-color: var(--usercolor4); } +.hydrogen .avatar.usercolor5 { background-color: var(--usercolor5); } +.hydrogen .avatar.usercolor6 { background-color: var(--usercolor6); } +.hydrogen .avatar.usercolor7 { background-color: var(--usercolor7); } +.hydrogen .avatar.usercolor8 { background-color: var(--usercolor8); } + .LeftPanel { background: rgba(245, 245, 245, 0.90); } @@ -135,6 +153,15 @@ a { font-weight: bold; } +.hydrogen .sender.usercolor1 { color: var(--usercolor1); } +.hydrogen .sender.usercolor2 { color: var(--usercolor2); } +.hydrogen .sender.usercolor3 { color: var(--usercolor3); } +.hydrogen .sender.usercolor4 { color: var(--usercolor4); } +.hydrogen .sender.usercolor5 { color: var(--usercolor5); } +.hydrogen .sender.usercolor6 { color: var(--usercolor6); } +.hydrogen .sender.usercolor7 { color: var(--usercolor7); } +.hydrogen .sender.usercolor8 { color: var(--usercolor8); } + .TextMessageView .message-container time { padding: 2px 0 0px 20px; font-size: 0.9em; diff --git a/src/ui/web/session/RoomTile.js b/src/ui/web/session/RoomTile.js index bdce01cc..9268bfe5 100644 --- a/src/ui/web/session/RoomTile.js +++ b/src/ui/web/session/RoomTile.js @@ -17,9 +17,9 @@ limitations under the License. import {TemplateView} from "../general/TemplateView.js"; export class RoomTile extends TemplateView { - render(t) { + render(t, vm) { return t.li({"className": {"active": vm => vm.isOpen}}, [ - t.div({className: "avatar medium"}, vm => vm.avatarInitials), + t.div({className: `avatar medium usercolor${vm.avatarColorNumber}`}, vm => vm.avatarInitials), t.div({className: "description"}, t.div({className: "name"}, vm => vm.name)) ]); } diff --git a/src/ui/web/session/room/RoomView.js b/src/ui/web/session/room/RoomView.js index 6080368d..0c7ace9e 100644 --- a/src/ui/web/session/room/RoomView.js +++ b/src/ui/web/session/room/RoomView.js @@ -30,7 +30,7 @@ export class RoomView extends TemplateView { t.div({className: "TimelinePanel"}, [ t.div({className: "RoomHeader"}, [ t.button({className: "back", onClick: () => vm.close()}), - t.div({className: "avatar large"}, vm => vm.avatarInitials), + t.div({className: `avatar large usercolor${vm.avatarColorNumber}`}, vm => vm.avatarInitials), t.div({className: "room-description"}, [ t.h2(vm => vm.name), ]), diff --git a/src/ui/web/session/room/timeline/ImageView.js b/src/ui/web/session/room/timeline/ImageView.js index d5f9279f..4770510c 100644 --- a/src/ui/web/session/room/timeline/ImageView.js +++ b/src/ui/web/session/room/timeline/ImageView.js @@ -15,6 +15,7 @@ limitations under the License. */ import {TemplateView} from "../../../general/TemplateView.js"; +import {renderMessage} from "./common.js"; export class ImageView extends TemplateView { render(t, vm) { @@ -33,13 +34,8 @@ export class ImageView extends TemplateView { style: `padding-top: ${heightRatioPercent}%; width: ${vm.thumbnailWidth}px;` }, image); - return t.li( - {className: {"TextMessageView": true, own: vm.isOwn, pending: vm.isPending}}, - t.div({className: "message-container"}, [ - t.div({className: "sender"}, vm => vm.isContinuation ? "" : vm.sender), - t.div(linkContainer), - t.p(t.time(vm.date + " " + vm.time)), - ]) + return renderMessage(t, vm, + [t.div(linkContainer), t.p(t.time(vm.date + " " + vm.time))] ); } } diff --git a/src/ui/web/session/room/timeline/TextMessageView.js b/src/ui/web/session/room/timeline/TextMessageView.js index ed67a1fa..260eaf29 100644 --- a/src/ui/web/session/room/timeline/TextMessageView.js +++ b/src/ui/web/session/room/timeline/TextMessageView.js @@ -15,15 +15,12 @@ limitations under the License. */ import {TemplateView} from "../../../general/TemplateView.js"; +import {renderMessage} from "./common.js"; export class TextMessageView extends TemplateView { render(t, vm) { - return t.li( - {className: {"TextMessageView": true, own: vm.isOwn, pending: vm.isPending}}, - t.div({className: "message-container"}, [ - t.div({className: "sender"}, vm => vm.isContinuation ? "" : vm.sender), - t.p([vm.text, t.time(vm.date + " " + vm.time)]), - ]) + return renderMessage(t, vm, + [t.p([vm.text, t.time(vm.date + " " + vm.time)])] ); } } diff --git a/src/ui/web/session/room/timeline/common.js b/src/ui/web/session/room/timeline/common.js new file mode 100644 index 00000000..848f1cf5 --- /dev/null +++ b/src/ui/web/session/room/timeline/common.js @@ -0,0 +1,14 @@ +export function renderMessage(t, vm, children) { + const classes = { + "TextMessageView": true, + own: vm.isOwn, + pending: vm.isPending, + continuation: vm.isContinuation, + }; + const sender = t.div({className: `sender usercolor${vm.senderColorNumber}`}, vm => vm.isContinuation ? "" : vm.sender); + children = [sender].concat(children); + return t.li( + {className: classes}, + t.div({className: "message-container"}, children) + ); +}