Show unread badge in favicon (#251)

This commit is contained in:
Ajay Bura 2022-09-04 10:58:46 +05:30
parent 392bd158ce
commit 6d5184cc8f
4 changed files with 76 additions and 11 deletions

View file

@ -1,8 +1,4 @@
import { avatarInitials } from '../../../util/common'; import { avatarInitials, cssVar } from '../../../util/common';
function cssVar(name) {
return getComputedStyle(document.body).getPropertyValue(name);
}
// renders the avatar and returns it as an URL // renders the avatar and returns it as an URL
export default async function renderAvatar({ export default async function renderAvatar({

View file

@ -67,7 +67,7 @@ class InitMatrix extends EventEmitter {
}, },
PREPARED: (prevState) => { PREPARED: (prevState) => {
console.log('PREPARED state'); console.log('PREPARED state');
console.log('previous state: ', prevState); console.log('Previous state: ', prevState);
// TODO: remove global.initMatrix at end // TODO: remove global.initMatrix at end
global.initMatrix = this; global.initMatrix = this;
if (prevState === null) { if (prevState === null) {
@ -76,6 +76,8 @@ class InitMatrix extends EventEmitter {
this.roomsInput = new RoomsInput(this.matrixClient, this.roomList); this.roomsInput = new RoomsInput(this.matrixClient, this.roomList);
this.notifications = new Notifications(this.roomList); this.notifications = new Notifications(this.roomList);
this.emit('init_loading_finished'); this.emit('init_loading_finished');
} else {
this.notifications._initNoti();
} }
}, },
RECONNECTING: () => { RECONNECTING: () => {

View file

@ -5,6 +5,9 @@ import { selectRoom } from '../action/navigation';
import cons from './cons'; import cons from './cons';
import navigation from './navigation'; import navigation from './navigation';
import settings from './settings'; import settings from './settings';
import { getBadgedFavicon, setFavicon, cssVar } from '../../util/common';
import LogoSVG from '../../../public/res/svg/cinny.svg';
function isNotifEvent(mEvent) { function isNotifEvent(mEvent) {
const eType = mEvent.getType(); const eType = mEvent.getType();
@ -32,22 +35,25 @@ class Notifications extends EventEmitter {
constructor(roomList) { constructor(roomList) {
super(); super();
this.LOGO_HIGHLIGH = LogoSVG;
this.LOGO_UNREAD = LogoSVG;
this.matrixClient = roomList.matrixClient; this.matrixClient = roomList.matrixClient;
this.roomList = roomList; this.roomList = roomList;
this.roomIdToNoti = new Map(); this.roomIdToNoti = new Map();
this._initNoti(); // this._initNoti();
this._listenEvents(); this._listenEvents();
// Ask for permission by default after loading // Ask for permission by default after loading
window.Notification?.requestPermission(); window.Notification?.requestPermission();
// TODO:
window.notifications = this;
} }
_initNoti() { async _initNoti() {
this.LOGO_HIGHLIGH = await getBadgedFavicon(LogoSVG, cssVar('--bg-positive')) ?? LogoSVG;
this.LOGO_UNREAD = await getBadgedFavicon(LogoSVG, cssVar('--bg-badge')) ?? LogoSVG;
const addNoti = (roomId) => { const addNoti = (roomId) => {
const room = this.matrixClient.getRoom(roomId); const room = this.matrixClient.getRoom(roomId);
if (this.getNotiType(room.roomId) === cons.notifs.MUTE) return; if (this.getNotiType(room.roomId) === cons.notifs.MUTE) return;
@ -59,6 +65,7 @@ class Notifications extends EventEmitter {
}; };
[...this.roomList.rooms].forEach(addNoti); [...this.roomList.rooms].forEach(addNoti);
[...this.roomList.directs].forEach(addNoti); [...this.roomList.directs].forEach(addNoti);
this._updateFavicon();
} }
doesRoomHaveUnread(room) { doesRoomHaveUnread(room) {
@ -130,6 +137,24 @@ class Notifications extends EventEmitter {
} }
} }
async _updateFavicon() {
let unread = false;
let highlight = false;
[...this.roomIdToNoti.values()].find((noti) => {
if (!unread) {
unread = noti.total > 0 || noti.highlight > 0;
}
highlight = noti.highlight > 0;
if (unread && highlight) return true;
return false;
});
if (!unread) {
setFavicon(LogoSVG);
return;
}
setFavicon(highlight ? this.LOGO_HIGHLIGH : this.LOGO_UNREAD);
}
_setNoti(roomId, total, highlight) { _setNoti(roomId, total, highlight) {
const addNoti = (id, t, h, fromId) => { const addNoti = (id, t, h, fromId) => {
const prevTotal = this.roomIdToNoti.get(id)?.total ?? null; const prevTotal = this.roomIdToNoti.get(id)?.total ?? null;
@ -156,6 +181,7 @@ class Notifications extends EventEmitter {
allParentSpaces.forEach((spaceId) => { allParentSpaces.forEach((spaceId) => {
addNoti(spaceId, addT, addH, roomId); addNoti(spaceId, addT, addH, roomId);
}); });
this._updateFavicon();
} }
_deleteNoti(roomId, total, highlight) { _deleteNoti(roomId, total, highlight) {
@ -188,6 +214,7 @@ class Notifications extends EventEmitter {
allParentSpaces.forEach((spaceId) => { allParentSpaces.forEach((spaceId) => {
removeNoti(spaceId, total, highlight, roomId); removeNoti(spaceId, total, highlight, roomId);
}); });
this._updateFavicon();
} }
async _displayPopupNoti(mEvent, room) { async _displayPopupNoti(mEvent, room) {

View file

@ -115,6 +115,46 @@ export function avatarInitials(text) {
return [...text][0]; return [...text][0];
} }
export function cssVar(name) {
return getComputedStyle(document.body).getPropertyValue(name);
}
export function setFavicon(url) {
document.querySelector('[rel=icon]').href = url;
}
export async function getBadgedFavicon(favUrl, color) {
try {
const canvas = document.createElement('canvas');
canvas.width = 48;
canvas.height = 48;
const ctx = canvas.getContext('2d');
const img = new Image();
img.crossOrigin = 'anonymous';
const imgPromise = new Promise((resolve, reject) => {
img.onerror = reject;
img.onload = resolve;
});
img.src = favUrl;
await imgPromise;
ctx.drawImage(img, 0, 0, 48, 48);
ctx.beginPath();
ctx.fillStyle = color;
ctx.arc(40, 8, 8, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
return canvas.toDataURL();
} catch (e) {
console.error(e);
return undefined;
}
}
export function copyToClipboard(text) { export function copyToClipboard(text) {
if (navigator.clipboard) { if (navigator.clipboard) {
navigator.clipboard.writeText(text); navigator.clipboard.writeText(text);