diff --git a/src/app/atoms/avatar/Avatar.jsx b/src/app/atoms/avatar/Avatar.jsx index 891637c9..6120e551 100644 --- a/src/app/atoms/avatar/Avatar.jsx +++ b/src/app/atoms/avatar/Avatar.jsx @@ -8,6 +8,7 @@ import Text from '../text/Text'; import RawIcon from '../system-icons/RawIcon'; import ImageBrokenSVG from '../../../../public/res/svg/image-broken.svg'; +import { avatarInitials } from '../../../util/common'; function Avatar({ text, bgColor, iconSrc, iconColor, imageSrc, size, @@ -40,7 +41,7 @@ function Avatar({ ? : text !== null && ( - {twemojify([...text][0])} + {twemojify(avatarInitials(text))} ) } diff --git a/src/app/atoms/avatar/render.js b/src/app/atoms/avatar/render.js new file mode 100644 index 00000000..07303dd1 --- /dev/null +++ b/src/app/atoms/avatar/render.js @@ -0,0 +1,61 @@ +import { avatarInitials } from '../../../util/common'; + +function cssVar(name) { + return getComputedStyle(document.body).getPropertyValue(name); +} + +// renders the avatar and returns it as an URL +export default async function renderAvatar({ + text, bgColor, imageSrc, size, borderRadius, scale, +}) { + try { + const canvas = document.createElement('canvas'); + canvas.width = size * scale; + canvas.height = size * scale; + + const ctx = canvas.getContext('2d'); + + ctx.scale(scale, scale); + + // rounded corners + ctx.beginPath(); + ctx.moveTo(size, size); + ctx.arcTo(0, size, 0, 0, borderRadius); + ctx.arcTo(0, 0, size, 0, borderRadius); + ctx.arcTo(size, 0, size, size, borderRadius); + ctx.arcTo(size, size, 0, size, borderRadius); + + if (imageSrc) { + // clip corners of image + ctx.closePath(); + ctx.clip(); + + const img = new Image(); + img.crossOrigin = 'anonymous'; + const promise = new Promise((resolve, reject) => { + img.onerror = reject; + img.onload = resolve; + }); + img.src = imageSrc; + await promise; + + ctx.drawImage(img, 0, 0, size, size); + } else { + // colored background + ctx.fillStyle = cssVar(bgColor); + ctx.fill(); + + // centered letter + ctx.fillStyle = '#fff'; + ctx.font = `${cssVar('--fs-s1')} ${cssVar('--font-primary')}`; + ctx.textBaseline = 'middle'; + ctx.textAlign = 'center'; + ctx.fillText(avatarInitials(text), size / 2, size / 2); + } + + return canvas.toDataURL(); + } catch (e) { + console.error(e); + return imageSrc; + } +} diff --git a/src/client/state/Notifications.js b/src/client/state/Notifications.js index 94626649..a41633ba 100644 --- a/src/client/state/Notifications.js +++ b/src/client/state/Notifications.js @@ -1,4 +1,6 @@ import EventEmitter from 'events'; +import renderAvatar from '../../app/atoms/avatar/render'; +import { cssColorMXID } from '../../util/colorMXID'; import { selectRoom } from '../action/navigation'; import cons from './cons'; import navigation from './navigation'; @@ -183,9 +185,19 @@ class Notifications extends EventEmitter { title = `${mEvent.sender.name} (${room.name})`; } + const iconSize = 36; + const icon = await renderAvatar({ + text: mEvent.sender.name, + bgColor: cssColorMXID(mEvent.getSender()), + imageSrc: mEvent.sender?.getAvatarUrl(this.matrixClient.baseUrl, iconSize, iconSize, 'crop'), + size: iconSize, + borderRadius: 8, + scale: 8, + }); + const noti = new window.Notification(title, { body: mEvent.getContent().body, - icon: mEvent.sender?.getAvatarUrl(this.matrixClient.baseUrl, 36, 36, 'crop'), + icon, }); noti.onclick = () => selectRoom(room.roomId, mEvent.getId()); } diff --git a/src/util/colorMXID.js b/src/util/colorMXID.js index 4745120c..4d303aae 100644 --- a/src/util/colorMXID.js +++ b/src/util/colorMXID.js @@ -1,16 +1,6 @@ // https://github.com/cloudrac3r/cadencegq/blob/master/pug/mxid.pug -const colors = [ - 'var(--mx-uc-1)', - 'var(--mx-uc-2)', - 'var(--mx-uc-3)', - 'var(--mx-uc-4)', - 'var(--mx-uc-5)', - 'var(--mx-uc-6)', - 'var(--mx-uc-7)', - 'var(--mx-uc-8)', -]; -function hashCode(str) { +export function hashCode(str) { let hash = 0; let i; let chr; @@ -26,7 +16,12 @@ function hashCode(str) { } return Math.abs(hash); } -export default function colorMXID(userId) { + +export function cssColorMXID(userId) { const colorNumber = hashCode(userId) % 8; - return colors[colorNumber]; + return `--mx-uc-${colorNumber + 1}`; +} + +export default function colorMXID(userId) { + return `var(${cssColorMXID(userId)})`; } diff --git a/src/util/common.js b/src/util/common.js index 91424208..941f34cf 100644 --- a/src/util/common.js +++ b/src/util/common.js @@ -110,3 +110,7 @@ export function getScrollInfo(target) { scroll.isScrollable = scroll.height > scroll.viewHeight; return scroll; } + +export function avatarInitials(text) { + return [...text][0]; +}