rewrite/go-templ #1

Merged
schreifuchs merged 9 commits from rewrite/go-templ into main 2026-03-02 22:41:34 +01:00
19 changed files with 363 additions and 60 deletions
Showing only changes of commit 2491d21963 - Show all commits
+2 -1
View File
@@ -1,6 +1,6 @@
module git.schreifuchs.ch/schreifuchs/schreifuchs.ch
go 1.25.7
go 1.26
tool github.com/a-h/templ/cmd/templ
@@ -22,6 +22,7 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/natefinch/atomic v1.0.1 // indirect
github.com/yuin/gopher-lua v1.1.1 // indirect
golang.org/x/image v0.36.0 // indirect
golang.org/x/mod v0.26.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/sync v0.16.0 // indirect
+3
View File
@@ -39,6 +39,8 @@ github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/image v0.36.0 h1:Iknbfm1afbgtwPTmHnS2gTM/6PPZfH+z2EFuOkSbqwc=
golang.org/x/image v0.36.0/go.mod h1:YsWD2TyyGKiIX1kZlu9QfKIsQ4nAAK9bdgdrIsE7xy4=
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
@@ -51,6 +53,7 @@ golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+72 -3
View File
@@ -3,10 +3,20 @@ package images
import (
"bytes"
"context"
"encoding/base64"
"encoding/binary"
"errors"
"fmt"
"hash/fnv"
"image"
"image/jpeg"
_ "image/jpeg"
_ "image/png"
"io"
"strings"
"time"
"github.com/valkey-io/valkey-go"
)
var ErrNotAnImage = errors.New("not an image")
@@ -24,7 +34,7 @@ func (s *Service) getMime(path string) (mimeType string, err error) {
return
}
func (s *Service) GetImage(ctx context.Context, uid string) (img io.Reader, mimeType string, err error) {
func (s *Service) getImage(uid string) (file []byte, mimeType string, err error) {
path, err := PathFromUID(uid)
if err != nil {
err = fmt.Errorf("could not get path: %w", err)
@@ -40,10 +50,69 @@ func (s *Service) GetImage(ctx context.Context, uid string) (img io.Reader, mime
return
}
file, err := s.fs.Read(path)
file, err = s.fs.Read(path)
if err != nil {
err = fmt.Errorf("image file could not be fetched")
}
return bytes.NewBuffer(file), mimeType, err
return
}
func (s *Service) GetImage(ctx context.Context, uid string, options Options) (img io.Reader, mimeType string, err error) {
rawImage, mimeType, err := s.getImage(uid)
if err != nil {
return
}
hash, err := hash(rawImage, options)
if err != nil {
return
}
if imgBytes, err := s.cache.Do(ctx, s.cache.B().Get().Key("img:"+hash).Build()).AsBytes(); err == nil {
return bytes.NewBuffer(imgBytes), mimeType, err
}
decImage, _, err := image.Decode(bytes.NewBuffer(rawImage))
if err != nil {
return
}
decImage = scale(decImage, options)
outBuff := bytes.NewBuffer(make([]byte, 0, 1024))
if options.Quality == 0 {
options.Quality = 80
}
err = jpeg.Encode(outBuff, decImage, &jpeg.Options{Quality: options.Quality})
if err != nil {
return
}
if err = s.cache.Do(ctx, s.cache.B().Set().Key("img:"+hash).Value(valkey.BinaryString(outBuff.Bytes())).Ex(time.Hour*48).Build()).Error(); err == nil {
return outBuff, "image/jpeg", err
}
return outBuff, "image/jpeg", err
}
func hash(bytes []byte, options Options) (hash string, err error) {
h := fnv.New128()
if _, err = h.Write(bytes); err != nil {
return
}
if err = binary.Write(h, binary.LittleEndian, int64(options.Height)); err != nil {
return
}
if err = binary.Write(h, binary.LittleEndian, int64(options.Width)); err != nil {
return
}
if err = binary.Write(h, binary.LittleEndian, int64(options.Quality)); err != nil {
return
}
res := h.Sum(make([]byte, 0, 128/8))
return base64.RawStdEncoding.EncodeToString(res), nil
}
+14 -2
View File
@@ -1,14 +1,26 @@
package images
import "os"
import (
"os"
"github.com/valkey-io/valkey-go"
)
type Options struct {
Width int // max width, 0 is unlimited
Height int // max Height, 0 is unlimited
Quality int // quality of the output jpeg
}
type Service struct {
fs fileSystem
cache valkey.Client
}
func New(fs fileSystem) *Service {
func New(fs fileSystem, cache valkey.Client) *Service {
return &Service{
fs: fs,
cache: cache,
}
}
+55
View File
@@ -0,0 +1,55 @@
package images
import (
"image"
"math"
"golang.org/x/image/draw"
)
func scale(src image.Image, options Options) image.Image {
// Set the expected size that you want:
dst := image.NewRGBA(
calculateFit(src.Bounds(), options),
)
// Resize:
draw.BiLinear.Scale(dst, dst.Rect, src, src.Bounds(), draw.Over, nil)
// Encode to `output`:
return dst
}
// calculateFit returns the new dimensions (width, height) that fit within the
// provided Options without upscaling and while maintaining the aspect ratio.
func calculateFit(bounds image.Rectangle, opt Options) image.Rectangle {
// If no limits are set, or the image is already smaller than the limits,
// return original dimensions (No Upscaling).
if (opt.Width == 0 || bounds.Max.X <= opt.Width) && (opt.Height == 0 || bounds.Max.Y <= opt.Height) {
return bounds
}
// Calculate ratios for width and height
ratioW := float64(opt.Width) / float64(bounds.Max.X)
ratioH := float64(opt.Height) / float64(bounds.Max.Y)
// Determine the scale factor
var scale float64
if opt.Width > 0 && opt.Height > 0 {
// Use the smaller ratio to ensure it fits in both bounds
scale = math.Min(ratioW, ratioH)
} else if opt.Width > 0 {
scale = ratioW
} else if opt.Height > 0 {
scale = ratioH
} else {
return bounds
}
return image.Rect(
0,
0,
int(math.Round(float64(bounds.Max.X)*scale)),
int(math.Round(float64(bounds.Max.Y)*scale)),
)
}
+3 -3
View File
@@ -14,7 +14,7 @@ const keyStat = "webdav:stat:"
// CachedClient wraps a FileSystem implementation with Valkey caching.
type CachedClient struct {
impl FS
client valkey.Client
cache valkey.Client
ttl time.Duration
hits atomic.Int64
misses atomic.Int64
@@ -26,7 +26,7 @@ type CachedClient struct {
func NewCachedClient(impl FS, client valkey.Client, refreshtime time.Duration) *CachedClient {
c := &CachedClient{
impl: impl,
client: client,
cache: client,
ttl: refreshtime * 5,
}
@@ -50,7 +50,7 @@ func (c *CachedClient) revalidate(ctx context.Context) {
func (c *CachedClient) scanAndProcess(ctx context.Context, match string, process func(key string) error) {
cursor := uint64(0)
for {
res, err := c.client.Do(ctx, c.client.B().Scan().Cursor(cursor).Match(match).Build()).AsScanEntry()
res, err := c.cache.Do(ctx, c.cache.B().Scan().Cursor(cursor).Match(match).Build()).AsScanEntry()
if err != nil {
slog.Error("scan failed", "match", match, "err", err)
return
+7 -7
View File
@@ -22,7 +22,7 @@ func (c *CachedClient) Read(path string) ([]byte, error) {
ctx := context.Background()
// Try cache
cached, err := c.client.Do(ctx, c.client.B().Get().Key(key).Build()).AsBytes()
cached, err := c.cache.Do(ctx, c.cache.B().Get().Key(key).Build()).AsBytes()
if err == nil && len(cached) > 0 {
c.readHits.Add(1)
c.hits.Add(1)
@@ -58,7 +58,7 @@ func (c *CachedClient) Read(path string) ([]byte, error) {
}
// Cache
c.client.Do(ctx, c.client.B().Set().Key(key).Value(valkey.BinaryString(cached)).Ex(c.ttl).Build())
c.cache.Do(ctx, c.cache.B().Set().Key(key).Value(valkey.BinaryString(cached)).Ex(c.ttl).Build())
return data, nil
}
@@ -67,7 +67,7 @@ func (c *CachedClient) revalidateRead(ctx context.Context) {
startTime := time.Now()
c.scanAndProcess(ctx, keyRead+"*", func(key string) error {
path := strings.TrimPrefix(key, keyRead)
cached, err := c.client.Do(ctx, c.client.B().Get().Key(key).Build()).AsBytes()
cached, err := c.cache.Do(ctx, c.cache.B().Get().Key(key).Build()).AsBytes()
if err != nil {
// Key might be gone or error fetching
return nil
@@ -76,13 +76,13 @@ func (c *CachedClient) revalidateRead(ctx context.Context) {
var cachedFile readFile
err = json.Unmarshal(cached, &cachedFile)
if err != nil {
c.client.Do(ctx, c.client.B().Del().Key(key).Build())
c.cache.Do(ctx, c.cache.B().Del().Key(key).Build())
return err
}
info, err := c.impl.Stat(path)
if err != nil {
c.client.Do(ctx, c.client.B().Del().Key(key).Build())
c.cache.Do(ctx, c.cache.B().Del().Key(key).Build())
return nil
}
@@ -99,9 +99,9 @@ func (c *CachedClient) revalidateRead(ctx context.Context) {
return err
}
c.client.Do(ctx, c.client.B().Set().Key(key).Value(valkey.BinaryString(newCached)).Ex(c.ttl).Build())
c.cache.Do(ctx, c.cache.B().Set().Key(key).Value(valkey.BinaryString(newCached)).Ex(c.ttl).Build())
} else {
c.client.Do(ctx, c.client.B().Expire().Key(key).Seconds(int64(c.ttl/time.Second)).Build())
c.cache.Do(ctx, c.cache.B().Expire().Key(key).Seconds(int64(c.ttl/time.Second)).Build())
}
return nil
})
+4 -4
View File
@@ -16,7 +16,7 @@ func (c *CachedClient) ReadDir(path string) ([]os.FileInfo, error) {
ctx := context.Background()
// Try cache
val, err := c.client.Do(ctx, c.client.B().Get().Key(key).Build()).ToString()
val, err := c.cache.Do(ctx, c.cache.B().Get().Key(key).Build()).ToString()
if err == nil && val != "" {
var cached []File
if err := json.Unmarshal([]byte(val), &cached); err == nil {
@@ -47,7 +47,7 @@ func (c *CachedClient) ReadDir(path string) ([]os.FileInfo, error) {
bytes, err := json.Marshal(cached)
if err == nil {
c.client.Do(ctx, c.client.B().Set().Key(key).Value(string(bytes)).Ex(c.ttl).Build())
c.cache.Do(ctx, c.cache.B().Set().Key(key).Value(string(bytes)).Ex(c.ttl).Build())
}
return infos, nil
@@ -59,7 +59,7 @@ func (c *CachedClient) revalidateReadDir(ctx context.Context) {
path := strings.TrimPrefix(key, keyReadDir)
infos, err := c.impl.ReadDir(path)
if err != nil {
c.client.Do(ctx, c.client.B().Del().Key(key).Build())
c.cache.Do(ctx, c.cache.B().Del().Key(key).Build())
return err
}
@@ -71,7 +71,7 @@ func (c *CachedClient) revalidateReadDir(ctx context.Context) {
bytes, err := json.Marshal(cached)
if err == nil {
c.client.Do(ctx, c.client.B().Set().Key(key).Value(string(bytes)).Ex(c.ttl).Build())
c.cache.Do(ctx, c.cache.B().Set().Key(key).Value(string(bytes)).Ex(c.ttl).Build())
}
return nil
})
+4 -4
View File
@@ -14,7 +14,7 @@ func (c *CachedClient) Stat(path string) (info os.FileInfo, err error) {
ctx := context.Background()
// Try cache
val, err := c.client.Do(ctx, c.client.B().Get().Key(key).Build()).ToString()
val, err := c.cache.Do(ctx, c.cache.B().Get().Key(key).Build()).ToString()
if err == nil && val != "" {
var f File
if err := json.Unmarshal([]byte(val), &f); err == nil {
@@ -39,7 +39,7 @@ func (c *CachedClient) Stat(path string) (info os.FileInfo, err error) {
bytes, err := json.Marshal(info)
if err == nil {
c.client.Do(ctx, c.client.B().Set().Key(key).Value(string(bytes)).Ex(c.ttl).Build())
c.cache.Do(ctx, c.cache.B().Set().Key(key).Value(string(bytes)).Ex(c.ttl).Build())
}
return
@@ -51,7 +51,7 @@ func (c *CachedClient) revalidateStat(ctx context.Context) {
path := strings.TrimPrefix(key, keyStat)
info, err := c.impl.Stat(path)
if err != nil {
c.client.Do(ctx, c.client.B().Del().Key(key).Build())
c.cache.Do(ctx, c.cache.B().Del().Key(key).Build())
return err
}
@@ -59,7 +59,7 @@ func (c *CachedClient) revalidateStat(ctx context.Context) {
f := ParseToFile(info)
bytes, err := json.Marshal(f)
if err == nil {
c.client.Do(ctx, c.client.B().Set().Key(key).Value(string(bytes)).Ex(c.ttl).Build())
c.cache.Do(ctx, c.cache.B().Set().Key(key).Value(string(bytes)).Ex(c.ttl).Build())
}
return nil
})
-20
View File
@@ -1,20 +0,0 @@
package handleimage
import (
"io"
"log/slog"
"net/http"
)
func (h *Handler) getImage(w http.ResponseWriter, r *http.Request) {
uid := r.PathValue("uid")
img, mime, err := h.img.GetImage(r.Context(), uid)
if err != nil {
slog.Error("error wile serving image", "err", err, "uid", uid)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Add("Content-Type", mime)
io.Copy(w, img)
}
@@ -1,4 +1,4 @@
package handlehome
package resthome
import (
"net/http"
@@ -1,4 +1,4 @@
package handlehome
package resthome
import (
"context"
+43
View File
@@ -0,0 +1,43 @@
package restimage
import (
"io"
"log/slog"
"net/http"
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/components/images"
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/handlers/restutil"
)
func (h *Handler) getImage(w http.ResponseWriter, r *http.Request) {
uid := r.PathValue("uid")
var err error
options := images.Options{}
options.Height, err = restutil.IntParam(r, "h")
if err != nil {
restutil.SendErr(w, err)
return
}
options.Width, err = restutil.IntParam(r, "w")
if err != nil {
restutil.SendErr(w, err)
return
}
options.Quality, err = restutil.IntParam(r, "q")
if err != nil {
restutil.SendErr(w, err)
return
}
img, mime, err := h.img.GetImage(r.Context(), uid, options)
if err != nil {
slog.Error("error wile serving image", "err", err, "uid", uid)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Add("Content-Type", mime)
io.Copy(w, img)
}
@@ -1,9 +1,11 @@
package handleimage
package restimage
import (
"context"
"io"
"net/http"
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/components/images"
)
type Handler struct {
@@ -20,5 +22,5 @@ func New(mux *http.ServeMux, srv imageService) *Handler {
}
type imageService interface {
GetImage(ctx context.Context, uid string) (img io.Reader, mimeType string, err error)
GetImage(ctx context.Context, uid string, options images.Options) (img io.Reader, mimeType string, err error)
}
+68
View File
@@ -0,0 +1,68 @@
package restutil
import (
"encoding/json"
"errors"
"fmt"
"log/slog"
"net/http"
)
type ErrorType string
const (
ErrorTypeUndefinded = ""
ErrorTypeValidation = "VALIDATION"
ErrorTypeNotFound = "NOT_FOUND"
ErrorTypeServerError = "SERVER_ERROR"
)
type Error struct {
Type ErrorType `json:"type"`
Message string `json:"message"`
err error
}
func WrappErr(err error, errorType ErrorType, message string) *Error {
return &Error{
Type: errorType,
Message: message,
err: err,
}
}
func (e Error) Unwrap() error {
return e.err
}
func (e Error) Error() string {
return fmt.Sprintf("%s: %s", e.Message, e.err.Error())
}
func (e Error) StatusCode() int {
switch e.Type {
case ErrorTypeValidation:
return http.StatusBadRequest
case ErrorTypeNotFound:
return http.StatusNotFound
default:
return http.StatusInternalServerError
}
}
func SendErr(w http.ResponseWriter, err error) {
restErr := &Error{}
if !errors.As(err, &restErr) {
slog.Error("internal server error", "err", err)
restErr = &Error{
Type: ErrorTypeServerError,
}
}
w.WriteHeader(restErr.StatusCode())
w.Header().Add("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(restErr)
slog.Error("could not send error", "err", err)
}
+24
View File
@@ -0,0 +1,24 @@
package restutil
import (
"fmt"
"net/http"
"strconv"
)
func IntParam(r *http.Request, key string) (val int, err error) {
str := r.URL.Query().Get(key)
if str == "" {
return
}
val, err = strconv.Atoi(str)
if err != nil {
err = WrappErr(
err,
ErrorTypeValidation,
fmt.Sprintf("failed to parse query parameter '%s': not an int", key),
)
}
return
}
+4 -4
View File
@@ -10,8 +10,8 @@ import (
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/components/page"
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/components/translate"
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/filesystem"
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/handlers/handlehome"
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/handlers/handleimage"
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/handlers/resthome"
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/handlers/restimage"
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/internal/templer"
"git.schreifuchs.ch/schreifuchs/schreifuchs.ch/web"
"github.com/studio-b12/gowebdav"
@@ -54,8 +54,8 @@ func registerOther() (h http.Handler) {
fs = filesystem.NewCachedClient(webdavClient, valkeyClient, 5*time.Hour)
}
_ = handlehome.New(mux, page.New(fs))
_ = handleimage.New(mux, images.New(fs))
_ = resthome.New(mux, page.New(fs))
_ = restimage.New(mux, images.New(fs, valkeyClient))
mux.Handle("/", http.FileServer(http.FS(web.GetStaticFS())))
+17
View File
@@ -1,9 +1,13 @@
package components
import "strings"
import "fmt"
templ ImageTile(src string) {
<div class="grid grid-cols-1 grid-rows-1">
<img
src={ src }
srcset={ makeSrcset(src) }
fetchpriority="high"
class="row-start-1 col-start-1 pointer-events-none w-full h-96 object-cover"
/>
@@ -12,3 +16,16 @@ templ ImageTile(src string) {
</div>
</div>
}
func makeSrcset(src string) string {
sb := strings.Builder{}
for i := range 19 {
sb.WriteString(fmt.Sprintf("%s?w=%d %dw, ", src, 100*(i+1), 50*(i+1)))
}
i := 20
sb.WriteString(fmt.Sprintf("%s?w=%d %dw", src, 100*(i+1), 50*(i+1)))
return sb.String()
}
+32 -3
View File
@@ -8,6 +8,9 @@ package components
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import "strings"
import "fmt"
func ImageTile(src string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
@@ -36,13 +39,26 @@ func ImageTile(src string) templ.Component {
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(src)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/imagetile.templ`, Line: 6, Col: 12}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/imagetile.templ`, Line: 9, Col: 12}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" fetchpriority=\"high\" class=\"row-start-1 col-start-1 pointer-events-none w-full h-96 object-cover\"><div class=\"row-start-1 col-start-1\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" srcset=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(makeSrcset(src))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/imagetile.templ`, Line: 10, Col: 27}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" fetchpriority=\"high\" class=\"row-start-1 col-start-1 pointer-events-none w-full h-96 object-cover\"><div class=\"row-start-1 col-start-1\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -50,7 +66,7 @@ func ImageTile(src string) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -58,4 +74,17 @@ func ImageTile(src string) templ.Component {
})
}
func makeSrcset(src string) string {
sb := strings.Builder{}
for i := range 19 {
sb.WriteString(fmt.Sprintf("%s?w=%d %dw, ", src, 100*(i+1), 50*(i+1)))
}
i := 20
sb.WriteString(fmt.Sprintf("%s?w=%d %dw", src, 100*(i+1), 50*(i+1)))
return sb.String()
}
var _ = templruntime.GeneratedTemplate