Create icons for users without an avatar in desktop notifications (#305)
* Add notifications icon for users without an avatar * Render icon at higher resolution * Use scale to render at higher resolution
This commit is contained in:
parent
fe674ef2ea
commit
8d3f0a9f4d
5 changed files with 88 additions and 15 deletions
|
@ -8,6 +8,7 @@ import Text from '../text/Text';
|
||||||
import RawIcon from '../system-icons/RawIcon';
|
import RawIcon from '../system-icons/RawIcon';
|
||||||
|
|
||||||
import ImageBrokenSVG from '../../../../public/res/svg/image-broken.svg';
|
import ImageBrokenSVG from '../../../../public/res/svg/image-broken.svg';
|
||||||
|
import { avatarInitials } from '../../../util/common';
|
||||||
|
|
||||||
function Avatar({
|
function Avatar({
|
||||||
text, bgColor, iconSrc, iconColor, imageSrc, size,
|
text, bgColor, iconSrc, iconColor, imageSrc, size,
|
||||||
|
@ -40,7 +41,7 @@ function Avatar({
|
||||||
? <RawIcon size={size} src={iconSrc} color={iconColor} />
|
? <RawIcon size={size} src={iconSrc} color={iconColor} />
|
||||||
: text !== null && (
|
: text !== null && (
|
||||||
<Text variant={textSize} primary>
|
<Text variant={textSize} primary>
|
||||||
{twemojify([...text][0])}
|
{twemojify(avatarInitials(text))}
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
61
src/app/atoms/avatar/render.js
Normal file
61
src/app/atoms/avatar/render.js
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
import EventEmitter from 'events';
|
import EventEmitter from 'events';
|
||||||
|
import renderAvatar from '../../app/atoms/avatar/render';
|
||||||
|
import { cssColorMXID } from '../../util/colorMXID';
|
||||||
import { selectRoom } from '../action/navigation';
|
import { selectRoom } from '../action/navigation';
|
||||||
import cons from './cons';
|
import cons from './cons';
|
||||||
import navigation from './navigation';
|
import navigation from './navigation';
|
||||||
|
@ -183,9 +185,19 @@ class Notifications extends EventEmitter {
|
||||||
title = `${mEvent.sender.name} (${room.name})`;
|
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, {
|
const noti = new window.Notification(title, {
|
||||||
body: mEvent.getContent().body,
|
body: mEvent.getContent().body,
|
||||||
icon: mEvent.sender?.getAvatarUrl(this.matrixClient.baseUrl, 36, 36, 'crop'),
|
icon,
|
||||||
});
|
});
|
||||||
noti.onclick = () => selectRoom(room.roomId, mEvent.getId());
|
noti.onclick = () => selectRoom(room.roomId, mEvent.getId());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,6 @@
|
||||||
// https://github.com/cloudrac3r/cadencegq/blob/master/pug/mxid.pug
|
// https://github.com/cloudrac3r/cadencegq/blob/master/pug/mxid.pug
|
||||||
|
|
||||||
const colors = [
|
export function hashCode(str) {
|
||||||
'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) {
|
|
||||||
let hash = 0;
|
let hash = 0;
|
||||||
let i;
|
let i;
|
||||||
let chr;
|
let chr;
|
||||||
|
@ -26,7 +16,12 @@ function hashCode(str) {
|
||||||
}
|
}
|
||||||
return Math.abs(hash);
|
return Math.abs(hash);
|
||||||
}
|
}
|
||||||
export default function colorMXID(userId) {
|
|
||||||
|
export function cssColorMXID(userId) {
|
||||||
const colorNumber = hashCode(userId) % 8;
|
const colorNumber = hashCode(userId) % 8;
|
||||||
return colors[colorNumber];
|
return `--mx-uc-${colorNumber + 1}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function colorMXID(userId) {
|
||||||
|
return `var(${cssColorMXID(userId)})`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,3 +110,7 @@ export function getScrollInfo(target) {
|
||||||
scroll.isScrollable = scroll.height > scroll.viewHeight;
|
scroll.isScrollable = scroll.height > scroll.viewHeight;
|
||||||
return scroll;
|
return scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function avatarInitials(text) {
|
||||||
|
return [...text][0];
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue