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)
+1
appview/state/router.go
+1
appview/state/router.go
+110
-15
appview/state/state.go
+110
-15
appview/state/state.go
···
1
package state
2
3
import (
4
"context"
5
"database/sql"
6
"errors"
7
"fmt"
8
"log/slog"
9
"net/http"
10
"strings"
11
"time"
12
···
20
dbnotify "tangled.org/core/appview/notify/db"
21
phnotify "tangled.org/core/appview/notify/posthog"
22
"tangled.org/core/appview/oauth"
23
"tangled.org/core/appview/pages"
24
"tangled.org/core/appview/reporesolver"
25
"tangled.org/core/appview/validator"
···
39
securejoin "github.com/cyphar/filepath-securejoin"
40
"github.com/go-chi/chi/v5"
41
"github.com/posthog/posthog-go"
42
)
43
44
type State struct {
···
221
222
// https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Manifest
223
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"
238
}`
239
240
-
func (p *State) PWAManifest(w http.ResponseWriter, r *http.Request) {
241
w.Header().Set("Content-Type", "application/json")
242
w.Write([]byte(manifestJson))
243
}
244
245
func (s *State) TermsOfService(w http.ResponseWriter, r *http.Request) {
246
user := s.oauth.GetUser(r)
247
s.pages.TermsOfService(w, pages.TermsOfServiceParams{
···
1
package state
2
3
import (
4
+
"bytes"
5
"context"
6
"database/sql"
7
"errors"
8
"fmt"
9
+
"image"
10
+
"image/color"
11
+
"image/draw"
12
+
"image/png"
13
"log/slog"
14
"net/http"
15
+
"strconv"
16
"strings"
17
"time"
18
···
26
dbnotify "tangled.org/core/appview/notify/db"
27
phnotify "tangled.org/core/appview/notify/posthog"
28
"tangled.org/core/appview/oauth"
29
+
"tangled.org/core/appview/ogcard"
30
"tangled.org/core/appview/pages"
31
"tangled.org/core/appview/reporesolver"
32
"tangled.org/core/appview/validator"
···
46
securejoin "github.com/cyphar/filepath-securejoin"
47
"github.com/go-chi/chi/v5"
48
"github.com/posthog/posthog-go"
49
+
"github.com/srwiley/rasterx"
50
)
51
52
type State struct {
···
229
230
// https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Manifest
231
const manifestJson = `{
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
+
]
283
}`
284
285
+
func (s *State) PWAManifest(w http.ResponseWriter, r *http.Request) {
286
w.Header().Set("Content-Type", "application/json")
287
w.Write([]byte(manifestJson))
288
}
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
+
340
func (s *State) TermsOfService(w http.ResponseWriter, r *http.Request) {
341
user := s.oauth.GetUser(r)
342
s.pages.TermsOfService(w, pages.TermsOfServiceParams{
+1
nix/pkgs/appview-static-files.nix
+1
nix/pkgs/appview-static-files.nix