the images could be generated from the dolly svg but that would probably require bringing in a large dependency like inkscape and wouldn't bring huge benifits imo (&& just sticking it in tree was mentioned as best in the discord)
-44
appview/pages/templates/fragments/dolly/silhouette.svg
-44
appview/pages/templates/fragments/dolly/silhouette.svg
···
1
-
<svg
2
-
version="1.1"
3
-
id="svg1"
4
-
width="32"
5
-
height="32"
6
-
viewBox="0 0 25 25"
7
-
sodipodi:docname="tangled_dolly_silhouette.png"
8
-
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
9
-
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10
-
xmlns="http://www.w3.org/2000/svg"
11
-
xmlns:svg="http://www.w3.org/2000/svg">
12
-
<title>Dolly</title>
13
-
<defs
14
-
id="defs1" />
15
-
<sodipodi:namedview
16
-
id="namedview1"
17
-
pagecolor="#ffffff"
18
-
bordercolor="#000000"
19
-
borderopacity="0.25"
20
-
inkscape:showpageshadow="2"
21
-
inkscape:pageopacity="0.0"
22
-
inkscape:pagecheckerboard="true"
23
-
inkscape:deskcolor="#d1d1d1">
24
-
<inkscape:page
25
-
x="0"
26
-
y="0"
27
-
width="25"
28
-
height="25"
29
-
id="page2"
30
-
margin="0"
31
-
bleed="0" />
32
-
</sodipodi:namedview>
33
-
<g
34
-
inkscape:groupmode="layer"
35
-
inkscape:label="Image"
36
-
id="g1">
37
-
<path
38
-
class="dolly"
39
-
fill="currentColor"
40
-
style="stroke-width:1.12248"
41
-
d="m 16.208435,23.914069 c -0.06147,-0.02273 -0.147027,-0.03034 -0.190158,-0.01691 -0.197279,0.06145 -1.31068,-0.230493 -1.388819,-0.364153 -0.01956,-0.03344 -0.163274,-0.134049 -0.319377,-0.223561 -0.550395,-0.315603 -1.010951,-0.696643 -1.428383,-1.181771 -0.264598,-0.307509 -0.597257,-0.785384 -0.597257,-0.857979 0,-0.0216 -0.02841,-0.06243 -0.06313,-0.0907 -0.04977,-0.04053 -0.160873,0.0436 -0.52488,0.397463 -0.479803,0.466432 -0.78924,0.689475 -1.355603,0.977118 -0.183693,0.0933 -0.323426,0.179989 -0.310516,0.192658 0.02801,0.02748 -0.7656391,0.270031 -1.209129,0.369517 -0.5378332,0.120647 -1.6341809,0.08626 -1.9721503,-0.06186 C 6.7977157,23.031391 6.56735,22.957551 6.3371134,22.889782 4.9717169,22.487902 3.7511914,21.481518 3.1172396,20.234838 2.6890391,19.392772 2.5582276,18.827446 2.5610489,17.831154 2.5639589,16.802192 2.7366641,16.125844 3.2142117,15.273187 3.3040457,15.112788 3.3713143,14.976533 3.3636956,14.9704 3.3560756,14.9643 3.2459634,14.90305 3.1189994,14.834381 1.7582586,14.098312 0.77760984,12.777439 0.44909837,11.23818 0.33531456,10.705039 0.33670119,9.7067968 0.45195381,9.1778795 0.72259241,7.9359287 1.3827188,6.8888436 2.4297498,6.0407205 2.6856126,5.8334648 3.2975489,5.4910878 3.6885849,5.3364049 L 4.0584319,5.190106 4.2333984,4.860432 C 4.8393906,3.7186139 5.8908314,2.7968028 7.1056396,2.3423025 7.7690673,2.0940921 8.2290216,2.0150935 9.01853,2.0137575 c 0.9625627,-0.00163 1.629181,0.1532762 2.485864,0.5776514 l 0.271744,0.1346134 0.42911,-0.3607688 c 1.082666,-0.9102346 2.185531,-1.3136811 3.578383,-1.3090327 0.916696,0.00306 1.573918,0.1517893 2.356121,0.5331927 1.465948,0.7148 2.54506,2.0625628 2.865177,3.57848 l 0.07653,0.362429 0.515095,0.2556611 c 1.022872,0.5076874 1.756122,1.1690944 2.288361,2.0641468 0.401896,0.6758594 0.537303,1.0442682 0.675505,1.8378683 0.288575,1.6570823 -0.266229,3.3548023 -1.490464,4.5608743 -0.371074,0.36557 -0.840205,0.718265 -1.203442,0.904754 -0.144112,0.07398 -0.271303,0.15826 -0.282647,0.187269 -0.01134,0.02901 0.02121,0.142764 0.07234,0.25279 0.184248,0.396467 0.451371,1.331823 0.619371,2.168779 0.463493,2.30908 -0.754646,4.693707 -2.92278,5.721632 -0.479538,0.227352 -0.717629,0.309322 -1.144194,0.39393 -0.321869,0.06383 -1.850573,0.09139 -2.000174,0.03604 z M 12.25443,18.636956 c 0.739923,-0.24652 1.382521,-0.718922 1.874623,-1.37812 0.0752,-0.100718 0.213883,-0.275851 0.308198,-0.389167 0.09432,-0.113318 0.210136,-0.271056 0.257381,-0.350531 0.416347,-0.700389 0.680936,-1.176102 0.766454,-1.378041 0.05594,-0.132087 0.114653,-0.239607 0.130477,-0.238929 0.01583,6.79e-4 0.08126,0.08531 0.145412,0.188069 0.178029,0.285173 0.614305,0.658998 0.868158,0.743878 0.259802,0.08686 0.656158,0.09598 0.911369,0.02095 0.213812,-0.06285 0.507296,-0.298016 0.645179,-0.516947 0.155165,-0.246374 0.327989,-0.989595 0.327989,-1.410501 0,-1.26718 -0.610975,-3.143405 -1.237774,-3.801045 -0.198483,-0.2082486 -0.208557,-0.2319396 -0.208557,-0.4904655 0,-0.2517771 -0.08774,-0.5704927 -0.258476,-0.938956 C 16.694963,8.50313 16.375697,8.1377479 16.135846,7.9543702 L 15.932296,7.7987471 15.683004,7.9356529 C 15.131767,8.2383821 14.435638,8.1945733 13.943459,7.8261812 L 13.782862,7.7059758 13.686773,7.8908012 C 13.338849,8.5600578 12.487087,8.8811064 11.743178,8.6233891 11.487199,8.5347109 11.358897,8.4505994 11.063189,8.1776138 L 10.69871,7.8411436 10.453484,8.0579255 C 10.318608,8.1771557 10.113778,8.3156283 9.9983037,8.3656417 9.7041488,8.4930449 9.1808299,8.5227884 8.8979004,8.4281886 8.7754792,8.3872574 8.6687415,8.3537661 8.6607053,8.3537661 c -0.03426,0 -0.3092864,0.3066098 -0.3791974,0.42275 -0.041935,0.069664 -0.1040482,0.1266636 -0.1380294,0.1266636 -0.1316419,0 -0.4197402,0.1843928 -0.6257041,0.4004735 -0.1923125,0.2017571 -0.6853701,0.9036038 -0.8926582,1.2706578 -0.042662,0.07554 -0.1803555,0.353687 -0.3059848,0.618091 -0.1256293,0.264406 -0.3270073,0.686768 -0.4475067,0.938581 -0.1204992,0.251816 -0.2469926,0.519654 -0.2810961,0.595199 -0.2592829,0.574347 -0.285919,1.391094 -0.057822,1.77304 0.1690683,0.283105 0.4224039,0.480895 0.7285507,0.568809 0.487122,0.139885 0.9109638,-0.004 1.6013422,-0.543768 l 0.4560939,-0.356568 0.0036,0.172041 c 0.01635,0.781837 0.1831084,1.813183 0.4016641,2.484154 0.1160449,0.356262 0.3781448,0.83968 0.5614081,1.035462 0.2171883,0.232025 0.7140951,0.577268 1.0100284,0.701749 0.121485,0.0511 0.351032,0.110795 0.510105,0.132647 0.396966,0.05452 1.2105,0.02265 1.448934,-0.05679 z"
42
-
id="path1" />
43
-
</g>
44
-
</svg>
+1
appview/state/router.go
+1
appview/state/router.go
+110
-15
appview/state/state.go
+110
-15
appview/state/state.go
···
1
1
package state
2
2
3
3
import (
4
+
"bytes"
4
5
"context"
5
6
"database/sql"
6
7
"errors"
7
8
"fmt"
9
+
"image"
10
+
"image/color"
11
+
"image/draw"
12
+
"image/png"
8
13
"log/slog"
9
14
"net/http"
15
+
"strconv"
10
16
"strings"
11
17
"time"
12
18
···
20
26
dbnotify "tangled.org/core/appview/notify/db"
21
27
phnotify "tangled.org/core/appview/notify/posthog"
22
28
"tangled.org/core/appview/oauth"
29
+
"tangled.org/core/appview/ogcard"
23
30
"tangled.org/core/appview/pages"
24
31
"tangled.org/core/appview/reporesolver"
25
32
"tangled.org/core/appview/validator"
···
39
46
securejoin "github.com/cyphar/filepath-securejoin"
40
47
"github.com/go-chi/chi/v5"
41
48
"github.com/posthog/posthog-go"
49
+
"github.com/srwiley/rasterx"
42
50
)
43
51
44
52
type State struct {
···
221
229
222
230
// https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Manifest
223
231
const manifestJson = `{
224
-
"name": "tangled",
225
-
"description": "tightly-knit social coding.",
226
-
"icons": [
227
-
{
228
-
"src": "/favicon.svg",
229
-
"sizes": "144x144"
230
-
}
231
-
],
232
-
"start_url": "/",
233
-
"id": "org.tangled",
234
-
235
-
"display": "standalone",
236
-
"background_color": "#111827",
237
-
"theme_color": "#111827"
232
+
"name": "tangled",
233
+
"description": "tightly-knit social coding.",
234
+
"start_url": "/",
235
+
"id": "org.tangled",
236
+
"display": "standalone",
237
+
"background_color": "#111827",
238
+
"theme_color": "#111827",
239
+
"icons": [
240
+
{
241
+
"src": "/pwa-icon.png?res=512&transparent=true",
242
+
"type": "image/png",
243
+
"sizes": "512x512",
244
+
"purpose": "any monchrome"
245
+
},
246
+
{
247
+
"src": "/pwa-icon.png?res=512",
248
+
"type": "image/png",
249
+
"sizes": "512x512",
250
+
"purpose": "maskable"
251
+
},
252
+
{
253
+
"src": "/pwa-icon.png?res=192",
254
+
"type": "image/png",
255
+
"sizes": "192x192",
256
+
"purpose": "maskable"
257
+
},
258
+
{
259
+
"src": "/pwa-icon.png?res=144",
260
+
"type": "image/png",
261
+
"sizes": "144x144",
262
+
"purpose": "maskable"
263
+
},
264
+
{
265
+
"src": "/pwa-icon.png?res=96",
266
+
"type": "image/png",
267
+
"sizes": "96x96",
268
+
"purpose": "maskable"
269
+
},
270
+
{
271
+
"src": "/pwa-icon.png?res=72",
272
+
"type": "image/png",
273
+
"sizes": "72x72",
274
+
"purpose": "maskable"
275
+
},
276
+
{
277
+
"src": "/pwa-icon.png?res=48",
278
+
"type": "image/png",
279
+
"sizes": "48x48",
280
+
"purpose": "maskable"
281
+
}
282
+
]
238
283
}`
239
284
240
-
func (p *State) PWAManifest(w http.ResponseWriter, r *http.Request) {
285
+
func (s *State) PWAManifest(w http.ResponseWriter, r *http.Request) {
241
286
w.Header().Set("Content-Type", "application/json")
242
287
w.Write([]byte(manifestJson))
243
288
}
244
289
290
+
291
+
func (s *State) PWAIcon(w http.ResponseWriter, r *http.Request) {
292
+
tangledBgColour := color.RGBA{0x11, 0x18, 0x27, 0xff}
293
+
etag := "W/\"dolly-logo-v1 " + r.URL.Query().Get("res") + r.URL.Query().Get("transparent") + "\""
294
+
295
+
w.Header().Set("Content-Type", "image/png")
296
+
w.Header().Set("Cache-Control", "public, max-age=604800, stale-while-revalidate=86400, stale-if-error=86400")
297
+
w.Header().Set("Etag", etag)
298
+
// if the client already has the same logo dont bother recreating it
299
+
if len(r.Header["If-None-Match"]) == 1 && r.Header["If-None-Match"][0] == etag {
300
+
w.WriteHeader(304)
301
+
return
302
+
}
303
+
304
+
icon, err := ogcard.BuildSVGIconFromPath("templates/fragments/dolly/logo.svg", tangledBgColour)
305
+
// ignore error as icon is default icon on error
306
+
// also this should not fail
307
+
308
+
transparent := r.URL.Query().Get("transparent") == "true";
309
+
size, err := strconv.Atoi(r.URL.Query().Get("res"))
310
+
if err != nil {
311
+
size = 512
312
+
}
313
+
314
+
// maskable safe area is centered circle with radius 40%
315
+
// area outside may be cropped so make sure the icon fills it
316
+
// bc trig, safe square is just over 70% of screen
317
+
icon.SetTarget(float64(size) * 0.15, float64(size) * 0.15, float64(size) * 0.7, float64(size) * 0.7)
318
+
rgba := image.NewRGBA(image.Rect(0, 0, size, size))
319
+
320
+
// set image bg
321
+
var bgColour color.Color = tangledBgColour
322
+
if transparent {
323
+
bgColour = color.Transparent
324
+
}
325
+
draw.Draw(rgba, rgba.Bounds(), &image.Uniform{ bgColour }, image.Point{}, draw.Src)
326
+
327
+
// Create a scanner and rasterize the SVG
328
+
scanner := rasterx.NewScannerGV(size, size, rgba, rgba.Bounds())
329
+
raster := rasterx.NewDasher(size, size, scanner)
330
+
331
+
icon.Draw(raster, 1.0)
332
+
333
+
var img_buff bytes.Buffer
334
+
err = png.Encode(&img_buff, rgba)
335
+
// ignore error as encoding shouldnt fail
336
+
// if it fails itll return no bytes which is fine
337
+
w.Write(img_buff.Bytes())
338
+
}
339
+
245
340
func (s *State) TermsOfService(w http.ResponseWriter, r *http.Request) {
246
341
user := s.oauth.GetUser(r)
247
342
s.pages.TermsOfService(w, pages.TermsOfServiceParams{