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];
+}