diff options
Diffstat (limited to 'recipes/pleroma')
-rw-r--r-- | recipes/pleroma/package.json | 2 | ||||
-rw-r--r-- | recipes/pleroma/webview.js | 158 |
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 @@ | |||
1 | module.exports = (Ferdi) => { | 1 | const { ipcRenderer } = require('electron'); |
2 | const titleRegex = /^\((\d+)\)/; | 2 | |
3 | const titleRegex = /^\((\d+)\)/; | ||
4 | |||
5 | const getJson = async (relativeUri) => { | ||
6 | const req = await window.fetch(`${window.origin}${relativeUri}`, { | ||
7 | 'Accept': 'application/json' | ||
8 | }); | ||
9 | return req.json(); | ||
10 | } | ||
11 | |||
12 | const 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 | |||
25 | const 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 | |||
45 | const getPropertyValue = (style, property) => { | ||
46 | const value = style.getPropertyValue(property) || ''; | ||
47 | return value.trim(); | ||
48 | }; | ||
49 | |||
50 | const R = 0; | ||
51 | const G = 1; | ||
52 | const B = 2; | ||
53 | const A = 3; | ||
54 | const SCALE = 255; | ||
55 | |||
56 | const clamp = b => Math.min(Math.max(Math.round(b), 0), SCALE); | ||
57 | const scalePixel = b => clamp(b * SCALE); | ||
58 | const unscalePixel = b => b / SCALE; | ||
59 | const blend = (bgValue, bgWeight, fgValue, fgWeight) => { | ||
60 | const sum = bgValue * bgWeight + fgValue * fgWeight; | ||
61 | return clamp(sum / (bgWeight + fgWeight)); | ||
62 | } | ||
63 | |||
64 | class 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 | |||
143 | module.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 | }; |