aboutsummaryrefslogtreecommitdiffstats
path: root/recipes/pleroma/webview.js
diff options
context:
space:
mode:
Diffstat (limited to 'recipes/pleroma/webview.js')
-rw-r--r--recipes/pleroma/webview.js158
1 files changed, 155 insertions, 3 deletions
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};