Show unread badge in favicon (#251)
This commit is contained in:
parent
392bd158ce
commit
6d5184cc8f
4 changed files with 76 additions and 11 deletions
|
@ -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({
|
||||||
|
|
|
@ -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: () => {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue