Skip to content
Snippets Groups Projects
Unverified Commit 8e7135be authored by Tasso Evangelista's avatar Tasso Evangelista Committed by GitHub
Browse files

Chore: `@rocket.chat/favicon` (#25920)

parent dfd1d776
No related branches found
No related tags found
No related merge requests found
......@@ -5,7 +5,6 @@ packages/autoupdate/
packages/meteor-streams/
packages/meteor-timesync/
app/emoji-emojione/generateEmojiIndex.js
app/favico/favico.js
packages/rocketchat-livechat/assets/rocketchat-livechat.min.js
packages/rocketchat-livechat/assets/rocket-livechat.js
app/theme/client/vendor/
......
This diff is collapsed.
import { Favico } from './favico';
export { Favico };
export * from './client/index';
......@@ -14,7 +14,6 @@ import '../app/drupal/client';
import '../app/emoji/client';
import '../app/emoji-emojione/client';
import '../app/emoji-custom/client';
import '../app/favico';
import '../app/file-upload';
import '../app/github-enterprise/client';
import '../app/gitlab/client';
......
import type { ISubscription } from '@rocket.chat/core-typings';
import { manageFavicon } from '@rocket.chat/favicon';
import { Meteor } from 'meteor/meteor';
import { Session } from 'meteor/session';
import { Tracker } from 'meteor/tracker';
import { Favico } from '../../app/favico/client';
import { ChatSubscription, ChatRoom } from '../../app/models/client';
import { settings } from '../../app/settings/client';
import { getUserPreference } from '../../app/utils/client';
......@@ -75,12 +75,7 @@ Meteor.startup(() => {
});
Meteor.startup(() => {
const favicon = new (Favico as any)({
position: 'up',
animation: 'none',
});
window.favico = favicon;
const updateFavicon = manageFavicon();
Tracker.autorun(() => {
const siteName = settings.get('Site_Name') ?? '';
......@@ -88,11 +83,7 @@ Meteor.startup(() => {
const unread = Session.get('unread');
fireGlobalEvent('unread-changed', unread);
if (favicon) {
favicon.badge(unread, {
bgColor: typeof unread !== 'number' ? '#3d8a3a' : '#ac1b1b',
});
}
updateFavicon(unread);
document.title = unread === '' ? siteName : `(${unread}) ${siteName}`;
});
......
......@@ -194,6 +194,7 @@
"@rocket.chat/core-typings": "workspace:^",
"@rocket.chat/css-in-js": "~0.31.12",
"@rocket.chat/emitter": "~0.31.12",
"@rocket.chat/favicon": "workspace:^",
"@rocket.chat/forked-matrix-appservice-bridge": "^4.0.1",
"@rocket.chat/forked-matrix-bot-sdk": "^0.6.0-beta.2",
"@rocket.chat/fuselage": "0.32.0-dev.49",
......
{
"extends": ["@rocket.chat/eslint-config"],
"ignorePatterns": ["**/dist"],
"rules": {
"@typescript-eslint/interface-name-prefix": "off",
"@typescript-eslint/explicit-function-return-type": "off"
}
}
{
"name": "@rocket.chat/favicon",
"version": "0.0.1",
"private": true,
"devDependencies": {
"eslint": "^8.12.0",
"typescript": "~4.3.4"
},
"scripts": {
"lint": "eslint --ext .js,.jsx,.ts,.tsx .",
"lint:fix": "eslint --ext .js,.jsx,.ts,.tsx . --fix",
"build": "rm -rf dist && tsc -p tsconfig.json",
"dev": "tsc -p tsconfig.json --watch --preserveWatchOutput"
},
"main": "./dist/index.js",
"typings": "./dist/index.d.ts",
"files": [
"/dist"
]
}
export type Badge = number | string | null | undefined;
const getBadgeText = (badge: NonNullable<Badge>) => {
if (typeof badge === 'number') {
badge = Math.abs(badge | 0);
if (badge > 999) {
return `${badge > 9999 ? 9 : Math.floor(badge / 1000)}k+`;
}
return String(badge);
}
return String(badge);
};
const getBadgeStyle = (badge: NonNullable<Badge>) => {
if (typeof badge === 'number' && badge > 0) {
return { bgColor: '#ac1b1b', textColor: '#fff', fontFamily: 'sans-serif', fontStyle: 'bold' };
}
if (typeof badge === 'string' && badge !== '') {
return { bgColor: '#3d8a3a', textColor: '#fff', fontFamily: 'sans-serif', fontStyle: 'bold' };
}
throw new Error('Invalid badge type');
};
export const drawBadge = (badge: Badge, context: CanvasRenderingContext2D) => {
if (!badge) {
return;
}
const text = getBadgeText(badge);
const { fontFamily, fontStyle, bgColor, textColor } = getBadgeStyle(badge);
let w = 0.6;
const h = 0.6;
let x = 0.4;
const y = 0;
if (text.length === 2) {
x -= w * 0.4;
w *= 1.4;
} else if (text.length >= 3) {
x -= w * 0.65;
w *= 1.65;
}
context.beginPath();
if (text.length > 1) {
context.moveTo(x + w / 2, y);
context.lineTo(x + w - h / 2, y);
context.quadraticCurveTo(x + w, y, x + w, y + h / 2);
context.lineTo(x + w, y + h - h / 2);
context.quadraticCurveTo(x + w, y + h, x + w - h / 2, y + h);
context.lineTo(x + h / 2, y + h);
context.quadraticCurveTo(x, y + h, x, y + h - h / 2);
context.lineTo(x, y + h / 2);
context.quadraticCurveTo(x, y, x + h / 2, y);
} else {
context.arc(x + w / 2, y + h / 2, h / 2, 0, 2 * Math.PI);
}
context.fillStyle = bgColor;
context.fill();
context.closePath();
context.font = `${fontStyle} ${h * (text.length > 2 ? 0.85 : 1)}px ${fontFamily}`;
context.textAlign = 'center';
context.fillStyle = textColor;
context.beginPath();
context.stroke();
if (text.length > 3) {
context.fillText(text, x + w / 2, y + h - h * 0.2);
} else {
context.fillText(text, x + w / 2, y + h - h * 0.15);
}
context.closePath();
};
import { Badge, drawBadge } from './badge';
const getFavicons = () => {
const favicons = Array.from(document.head.getElementsByTagName('link')).filter((link) =>
/(^|\s)icon(\s|$)/i.test(link.getAttribute('rel') ?? ''),
);
if (favicons.length === 0) {
const link = document.createElement('link');
link.setAttribute('rel', 'icon');
document.head.appendChild(link);
favicons.push(link);
}
for (const favicon of favicons) {
favicon.setAttribute('type', 'image/png');
}
return favicons;
};
const fetchFaviconImage = async (url: string | undefined) => {
const img = new Image();
if (url) {
img.crossOrigin = 'anonymous';
img.src = url;
} else {
img.src = '';
img.width = 32;
img.height = 32;
}
return new Promise<HTMLImageElement>((resolve, reject) => {
img.onload = () => {
resolve(img);
};
img.onerror = () => {
reject(new Error('Failed to load image'));
};
});
};
const renderAndUpdate = ({
badge,
canvas,
favicons,
context,
img,
}: {
badge: Badge;
canvas: HTMLCanvasElement;
favicons: HTMLLinkElement[];
context: CanvasRenderingContext2D;
img: HTMLImageElement;
}) => {
context.scale(canvas.width, canvas.height);
context.clearRect(0, 0, 1, 1);
context.drawImage(img, 0, 0, 1, 1);
drawBadge(badge, context);
context.setTransform(1, 0, 0, 1, 0, 0);
const url = canvas.toDataURL('image/png');
for (const icon of favicons) {
icon.setAttribute('href', url);
}
};
export const manageFavicon = () => {
let pendingBadge: Badge;
let updateOrCollect = (badge: Badge) => {
pendingBadge = badge;
};
const init = async () => {
const favicons = getFavicons();
const lastFavicon = favicons[favicons.length - 1];
const faviconURL = lastFavicon.getAttribute('href') ?? undefined;
const img = await fetchFaviconImage(faviconURL);
const canvas = document.createElement('canvas');
canvas.width = img.width > 0 ? img.width : 32;
canvas.height = img.height > 0 ? img.height : 32;
const context = canvas.getContext('2d');
if (!context) {
throw new Error('Failed to create canvas context');
}
updateOrCollect = (badge) => {
renderAndUpdate({ badge, canvas, favicons, context, img });
};
if (pendingBadge) {
updateOrCollect(pendingBadge);
pendingBadge = undefined;
}
};
init();
return (badge: Badge) => {
updateOrCollect(badge);
};
};
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"lib": ["DOM"],
},
"include": ["./src/**/*"]
}
......@@ -3443,6 +3443,15 @@ __metadata:
languageName: unknown
linkType: soft
 
"@rocket.chat/favicon@workspace:^, @rocket.chat/favicon@workspace:packages/favicon":
version: 0.0.0-use.local
resolution: "@rocket.chat/favicon@workspace:packages/favicon"
dependencies:
eslint: ^8.12.0
typescript: ~4.3.4
languageName: unknown
linkType: soft
"@rocket.chat/forked-matrix-appservice-bridge@npm:^4.0.1":
version: 4.0.1
resolution: "@rocket.chat/forked-matrix-appservice-bridge@npm:4.0.1"
......@@ -3816,6 +3825,7 @@ __metadata:
"@rocket.chat/css-in-js": ~0.31.12
"@rocket.chat/emitter": ~0.31.12
"@rocket.chat/eslint-config": "workspace:^"
"@rocket.chat/favicon": "workspace:^"
"@rocket.chat/forked-matrix-appservice-bridge": ^4.0.1
"@rocket.chat/forked-matrix-bot-sdk": ^0.6.0-beta.2
"@rocket.chat/fuselage": 0.32.0-dev.49
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment