aboutsummaryrefslogtreecommitdiffstats
path: root/recipes
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2021-05-30 18:30:40 +0200
committerLibravatar Vijay Raghavan Aravamudhan <vraravam@users.noreply.github.com>2021-05-30 22:41:50 +0530
commit19a224623d8b4b2302994ccab050b463d0ee817d (patch)
treee768910bbea56c21158f1a0ad3a0057f40decd10 /recipes
parentAdd Pleroma recipe (diff)
downloadferdium-recipes-19a224623d8b4b2302994ccab050b463d0ee817d.tar.gz
ferdium-recipes-19a224623d8b4b2302994ccab050b463d0ee817d.tar.zst
ferdium-recipes-19a224623d8b4b2302994ccab050b463d0ee817d.zip
Instance logo for Pleroma
Fetch instance logo from the instance configuration and the instance colors from the pleroma-fe css to draw the instance logo. If the instance logo is in full color, it will be shown on the instance top bar background color. If it is configured to display with a single color, alpha blending on a <canvas> is used to render the logo in the top bar text color on top of the top bar background color, as it appears on the webpage.
Diffstat (limited to 'recipes')
-rw-r--r--recipes/pleroma/package.json2
-rw-r--r--recipes/pleroma/webview.js158
2 files changed, 156 insertions, 4 deletions
diff --git a/recipes/pleroma/package.json b/recipes/pleroma/package.json
index 31fa06f..2fedd64 100644
--- a/recipes/pleroma/package.json
+++ b/recipes/pleroma/package.json
@@ -1,7 +1,7 @@
1{ 1{
2 "id": "pleroma", 2 "id": "pleroma",
3 "name": "Pleroma", 3 "name": "Pleroma",
4 "version": "1.0.0", 4 "version": "1.1.0",
5 "description": "Pleroma — a lightweight fediverse server", 5 "description": "Pleroma — a lightweight fediverse server",
6 "main": "index.js", 6 "main": "index.js",
7 "author": "Ferdi <hello@getferdi.com>", 7 "author": "Ferdi <hello@getferdi.com>",
diff --git a/recipes/pleroma/webview.js b/recipes/pleroma/webview.js
index d08216d..d9fc9b1 100644
--- a/recipes/pleroma/webview.js
+++ b/recipes/pleroma/webview.js
@@ -1,5 +1,146 @@
1module.exports = (Ferdi) => { 1const { ipcRenderer } = require('electron');
2 const titleRegex = /^\((\d+)\)/; 2
3const titleRegex = /^\((\d+)\)/;
4
5const getJson = async (relativeUri) => {
6 const req = await window.fetch(`${window.origin}${relativeUri}`, {
7 'Accept': 'application/json'
8 });
9 return req.json();
10}
11
12const getInstanceConfig = async () => {
13 const origin = window.origin;
14 const staticConfig = await getJson('/static/config.json');
15 try {
16 const frontendConfig = await getJson('/api/pleroma/frontend_configurations');
17 const pleromaFeConfig = frontendConfig.pleroma_fe || {};
18 return { ...staticConfig, ...pleromaFeConfig };
19 } catch (e) {
20 console.log('Failed to load dynamic frontend configuration', e);
21 return staticConfig;
22 }
23};
24
25const getInstanceLogo = async () => {
26 const config = await getInstanceConfig();
27 if (!config.logo) {
28 throw new Error('Instance has no logo');
29 }
30 return new Promise((resolve, reject) => {
31 const img = document.createElement('img');
32 img.addEventListener('load', () => {
33 resolve({
34 logo: img,
35 logoMask: config.logoMask,
36 });
37 });
38 img.addEventListener('error', (event) => {
39 reject(new Error(`${event.type} error loading ${config.logo}: ${event.message}`));
40 });
41 img.src = `${origin}${config.logo}`;
42 });
43};
44
45const getPropertyValue = (style, property) => {
46 const value = style.getPropertyValue(property) || '';
47 return value.trim();
48};
49
50const R = 0;
51const G = 1;
52const B = 2;
53const A = 3;
54const SCALE = 255;
55
56const clamp = b => Math.min(Math.max(Math.round(b), 0), SCALE);
57const scalePixel = b => clamp(b * SCALE);
58const unscalePixel = b => b / SCALE;
59const blend = (bgValue, bgWeight, fgValue, fgWeight) => {
60 const sum = bgValue * bgWeight + fgValue * fgWeight;
61 return clamp(sum / (bgWeight + fgWeight));
62}
63
64class LogoUpdater {
65 constructor(img, mask) {
66 this._img = img;
67 this._mask = mask;
68 this._size = Math.max(img.width, img.height);
69 this._canvas = document.createElement('canvas');
70 this._canvas.width = this._size;
71 this._canvas.height = this._size;
72 this._ctx = this._canvas.getContext('2d');
73 this._dx = Math.floor((this._size - img.width) / 2);
74 this._dy = Math.floor((this._size - img.height) / 2);
75 this._previousBg = '';
76 this._previousFg = '';
77 }
78
79 update() {
80 const style = window.getComputedStyle(document.body);
81 const bg = getPropertyValue(style, '--topBar');
82 if (this._mask) {
83 const fg = getPropertyValue(style, '--topBarText');
84 if (this._previousBg !== bg || this._previousFg !== fg) {
85 this._updateMask(bg, fg);
86 this._previousBg = bg;
87 this._previousFg = fg;
88 return true;
89 }
90 } else {
91 if (this._previousBg !== bg) {
92 this._updateNoMask(bg);
93 this._previousBg = bg;
94 return true;
95 }
96 }
97 return false;
98 }
99
100 toDataURL() {
101 return this._canvas.toDataURL();
102 }
103
104 _updateNoMask(bg) {
105 this._ctx.fillStyle = bg;
106 this._ctx.fillRect(0, 0, this._size, this._size);
107 this._drawImage();
108 }
109
110 _updateMask(bg, fg) {
111 const bgColor = this._getColorData(bg);
112 const fgColor = this._getColorData(fg);
113 const bgAlpha = unscalePixel(bgColor[A]);
114 const fgAlpha = unscalePixel(fgColor[A]);
115 this._ctx.clearRect(0, 0, this._size, this._size);
116 this._drawImage();
117 const data = this._ctx.getImageData(0, 0, this._size, this._size);
118 const arr = data.data;
119 const length = data.width * data.height * 4;
120 for (let i = 0; i < length; i += 4) {
121 const logoAlpha = unscalePixel(arr[i + A]);
122 const fgWeight = logoAlpha * fgAlpha;
123 const bgWeight = bgAlpha * (1 - fgWeight);
124 arr[i + R] = blend(bgColor[R], bgWeight, fgColor[R], fgWeight);
125 arr[i + G] = blend(bgColor[G], bgWeight, fgColor[G], fgWeight);
126 arr[i + B] = blend(bgColor[B], bgWeight, fgColor[B], fgWeight);
127 arr[i + A] = scalePixel(bgWeight + fgWeight);
128 }
129 this._ctx.putImageData(data, 0, 0);
130 }
131
132 _getColorData(str) {
133 this._ctx.fillStyle = str;
134 this._ctx.fillRect(0, 0, 1, 1);
135 return this._ctx.getImageData(0, 0, 1, 1).data;
136 };
137
138 _drawImage() {
139 this._ctx.drawImage(this._img, this._dx, this._dy);
140 }
141}
142
143module.exports = Ferdi => {
3 144
4 const getMessages = () => { 145 const getMessages = () => {
5 let directCount = 0; 146 let directCount = 0;
@@ -10,5 +151,16 @@ module.exports = (Ferdi) => {
10 Ferdi.setBadge(directCount, 0); 151 Ferdi.setBadge(directCount, 0);
11 }; 152 };
12 153
13 Ferdi.loop(getMessages); 154 getInstanceLogo().then(({ logo, logoMask }) => {
155 const updater = new LogoUpdater(logo, logoMask);
156 Ferdi.loop(() => {
157 getMessages();
158 if (updater.update()) {
159 ipcRenderer.sendToHost('avatar', updater.toDataURL());
160 }
161 });
162 }, (e) => {
163 console.log('Failed to load instance logo', e);
164 Ferdi.loop(getMessages);
165 })
14}; 166};