Allow widgets to handle HTTP requests

This commit is contained in:
Svilen Markov 2024-08-01 18:26:38 +01:00
parent c041197f3f
commit 795caa5d9d
2 changed files with 84 additions and 25 deletions

View File

@ -8,6 +8,7 @@ import (
"net/http" "net/http"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -24,6 +25,7 @@ type Application struct {
Version string Version string
Config Config Config Config
slugToPage map[string]*Page slugToPage map[string]*Page
widgetByID map[uint64]widget.Widget
} }
type Theme struct { type Theme struct {
@ -106,16 +108,24 @@ func NewApplication(config *Config) (*Application, error) {
Version: buildVersion, Version: buildVersion,
Config: *config, Config: *config,
slugToPage: make(map[string]*Page), slugToPage: make(map[string]*Page),
widgetByID: make(map[uint64]widget.Widget),
} }
app.slugToPage[""] = &config.Pages[0] app.slugToPage[""] = &config.Pages[0]
for i := range config.Pages { for p := range config.Pages {
if config.Pages[i].Slug == "" { if config.Pages[p].Slug == "" {
config.Pages[i].Slug = titleToSlug(config.Pages[i].Title) config.Pages[p].Slug = titleToSlug(config.Pages[p].Title)
} }
app.slugToPage[config.Pages[i].Slug] = &config.Pages[i] app.slugToPage[config.Pages[p].Slug] = &config.Pages[p]
for c := range config.Pages[p].Columns {
for w := range config.Pages[p].Columns[c].Widgets {
widget := config.Pages[p].Columns[c].Widgets[w]
app.widgetByID[widget.GetID()] = widget
}
}
} }
return app, nil return app, nil
@ -190,6 +200,26 @@ func FileServerWithCache(fs http.FileSystem, cacheDuration time.Duration) http.H
}) })
} }
func (a *Application) HandleWidgetRequest(w http.ResponseWriter, r *http.Request) {
widgetValue := r.PathValue("widget")
widgetID, err := strconv.ParseUint(widgetValue, 10, 64)
if err != nil {
a.HandleNotFound(w, r)
return
}
widget, exists := a.widgetByID[widgetID]
if !exists {
a.HandleNotFound(w, r)
return
}
widget.HandleRequest(w, r)
}
func (a *Application) AssetPath(asset string) string { func (a *Application) AssetPath(asset string) string {
return "/static/" + a.Config.Server.AssetsHash + "/" + asset return "/static/" + a.Config.Server.AssetsHash + "/" + asset
} }
@ -203,7 +233,10 @@ func (a *Application) Serve() error {
mux.HandleFunc("GET /{$}", a.HandlePageRequest) mux.HandleFunc("GET /{$}", a.HandlePageRequest)
mux.HandleFunc("GET /{page}", a.HandlePageRequest) mux.HandleFunc("GET /{page}", a.HandlePageRequest)
mux.HandleFunc("GET /api/pages/{page}/content/{$}", a.HandlePageContentRequest) mux.HandleFunc("GET /api/pages/{page}/content/{$}", a.HandlePageContentRequest)
mux.HandleFunc("/api/widgets/{widget}/{path...}", a.HandleWidgetRequest)
mux.Handle( mux.Handle(
fmt.Sprintf("GET /static/%s/{path...}", a.Config.Server.AssetsHash), fmt.Sprintf("GET /static/%s/{path...}", a.Config.Server.AssetsHash),
http.StripPrefix("/static/"+a.Config.Server.AssetsHash, FileServerWithCache(http.FS(assets.PublicFS), 8*time.Hour)), http.StripPrefix("/static/"+a.Config.Server.AssetsHash, FileServerWithCache(http.FS(assets.PublicFS), 8*time.Hour)),

View File

@ -8,6 +8,8 @@ import (
"html/template" "html/template"
"log/slog" "log/slog"
"math" "math"
"net/http"
"sync/atomic"
"time" "time"
"github.com/glanceapp/glance/internal/feed" "github.com/glanceapp/glance/internal/feed"
@ -15,51 +17,59 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
var uniqueID atomic.Uint64
func New(widgetType string) (Widget, error) { func New(widgetType string) (Widget, error) {
var widget Widget
switch widgetType { switch widgetType {
case "calendar": case "calendar":
return &Calendar{}, nil widget = &Calendar{}
case "clock": case "clock":
return &Clock{}, nil widget = &Clock{}
case "weather": case "weather":
return &Weather{}, nil widget = &Weather{}
case "bookmarks": case "bookmarks":
return &Bookmarks{}, nil widget = &Bookmarks{}
case "iframe": case "iframe":
return &IFrame{}, nil widget = &IFrame{}
case "html": case "html":
return &HTML{}, nil widget = &HTML{}
case "hacker-news": case "hacker-news":
return &HackerNews{}, nil widget = &HackerNews{}
case "releases": case "releases":
return &Releases{}, nil widget = &Releases{}
case "videos": case "videos":
return &Videos{}, nil widget = &Videos{}
case "markets", "stocks": case "markets", "stocks":
return &Markets{}, nil widget = &Markets{}
case "reddit": case "reddit":
return &Reddit{}, nil widget = &Reddit{}
case "rss": case "rss":
return &RSS{}, nil widget = &RSS{}
case "monitor": case "monitor":
return &Monitor{}, nil widget = &Monitor{}
case "twitch-top-games": case "twitch-top-games":
return &TwitchGames{}, nil widget = &TwitchGames{}
case "twitch-channels": case "twitch-channels":
return &TwitchChannels{}, nil widget = &TwitchChannels{}
case "lobsters": case "lobsters":
return &Lobsters{}, nil widget = &Lobsters{}
case "change-detection": case "change-detection":
return &ChangeDetection{}, nil widget = &ChangeDetection{}
case "repository": case "repository":
return &Repository{}, nil widget = &Repository{}
case "search": case "search":
return &Search{}, nil widget = &Search{}
case "extension": case "extension":
return &Extension{}, nil widget = &Extension{}
default: default:
return nil, fmt.Errorf("unknown widget type: %s", widgetType) return nil, fmt.Errorf("unknown widget type: %s", widgetType)
} }
widget.SetID(uniqueID.Add(1))
return widget, nil
} }
type Widgets []Widget type Widgets []Widget
@ -90,7 +100,7 @@ func (w *Widgets) UnmarshalYAML(node *yaml.Node) error {
return err return err
} }
if err = widget.Initialize(); err != nil { if err := widget.Initialize(); err != nil {
return err return err
} }
@ -106,6 +116,9 @@ type Widget interface {
Update(context.Context) Update(context.Context)
Render() template.HTML Render() template.HTML
GetType() string GetType() string
GetID() uint64
SetID(uint64)
HandleRequest(w http.ResponseWriter, r *http.Request)
} }
type cacheType int type cacheType int
@ -117,6 +130,7 @@ const (
) )
type widgetBase struct { type widgetBase struct {
ID uint64 `yaml:"-"`
Type string `yaml:"type"` Type string `yaml:"type"`
Title string `yaml:"title"` Title string `yaml:"title"`
TitleURL string `yaml:"title-url"` TitleURL string `yaml:"title-url"`
@ -148,6 +162,18 @@ func (w *widgetBase) Update(ctx context.Context) {
} }
func (w *widgetBase) GetID() uint64 {
return w.ID
}
func (w *widgetBase) SetID(id uint64) {
w.ID = id
}
func (widget *widgetBase) HandleRequest(w http.ResponseWriter, r *http.Request) {
http.Error(w, "not implemented", http.StatusNotImplemented)
}
func (w *widgetBase) GetType() string { func (w *widgetBase) GetType() string {
return w.Type return w.Type
} }