From 795caa5d9de49ce04878de2687079e898993e018 Mon Sep 17 00:00:00 2001 From: Svilen Markov <7613769+svilenmarkov@users.noreply.github.com> Date: Thu, 1 Aug 2024 18:26:38 +0100 Subject: [PATCH] Allow widgets to handle HTTP requests --- internal/glance/glance.go | 41 ++++++++++++++++++++--- internal/widget/widget.go | 68 +++++++++++++++++++++++++++------------ 2 files changed, 84 insertions(+), 25 deletions(-) diff --git a/internal/glance/glance.go b/internal/glance/glance.go index 87fbad6..7187023 100644 --- a/internal/glance/glance.go +++ b/internal/glance/glance.go @@ -8,6 +8,7 @@ import ( "net/http" "path/filepath" "regexp" + "strconv" "strings" "sync" "time" @@ -24,6 +25,7 @@ type Application struct { Version string Config Config slugToPage map[string]*Page + widgetByID map[uint64]widget.Widget } type Theme struct { @@ -106,16 +108,24 @@ func NewApplication(config *Config) (*Application, error) { Version: buildVersion, Config: *config, slugToPage: make(map[string]*Page), + widgetByID: make(map[uint64]widget.Widget), } app.slugToPage[""] = &config.Pages[0] - for i := range config.Pages { - if config.Pages[i].Slug == "" { - config.Pages[i].Slug = titleToSlug(config.Pages[i].Title) + for p := range config.Pages { + if config.Pages[p].Slug == "" { + 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 @@ -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 { return "/static/" + a.Config.Server.AssetsHash + "/" + asset } @@ -203,7 +233,10 @@ func (a *Application) Serve() error { mux.HandleFunc("GET /{$}", a.HandlePageRequest) mux.HandleFunc("GET /{page}", a.HandlePageRequest) + mux.HandleFunc("GET /api/pages/{page}/content/{$}", a.HandlePageContentRequest) + mux.HandleFunc("/api/widgets/{widget}/{path...}", a.HandleWidgetRequest) + mux.Handle( 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)), diff --git a/internal/widget/widget.go b/internal/widget/widget.go index 324477f..4ede5bc 100644 --- a/internal/widget/widget.go +++ b/internal/widget/widget.go @@ -8,6 +8,8 @@ import ( "html/template" "log/slog" "math" + "net/http" + "sync/atomic" "time" "github.com/glanceapp/glance/internal/feed" @@ -15,51 +17,59 @@ import ( "gopkg.in/yaml.v3" ) +var uniqueID atomic.Uint64 + func New(widgetType string) (Widget, error) { + var widget Widget + switch widgetType { case "calendar": - return &Calendar{}, nil + widget = &Calendar{} case "clock": - return &Clock{}, nil + widget = &Clock{} case "weather": - return &Weather{}, nil + widget = &Weather{} case "bookmarks": - return &Bookmarks{}, nil + widget = &Bookmarks{} case "iframe": - return &IFrame{}, nil + widget = &IFrame{} case "html": - return &HTML{}, nil + widget = &HTML{} case "hacker-news": - return &HackerNews{}, nil + widget = &HackerNews{} case "releases": - return &Releases{}, nil + widget = &Releases{} case "videos": - return &Videos{}, nil + widget = &Videos{} case "markets", "stocks": - return &Markets{}, nil + widget = &Markets{} case "reddit": - return &Reddit{}, nil + widget = &Reddit{} case "rss": - return &RSS{}, nil + widget = &RSS{} case "monitor": - return &Monitor{}, nil + widget = &Monitor{} case "twitch-top-games": - return &TwitchGames{}, nil + widget = &TwitchGames{} case "twitch-channels": - return &TwitchChannels{}, nil + widget = &TwitchChannels{} case "lobsters": - return &Lobsters{}, nil + widget = &Lobsters{} case "change-detection": - return &ChangeDetection{}, nil + widget = &ChangeDetection{} case "repository": - return &Repository{}, nil + widget = &Repository{} case "search": - return &Search{}, nil + widget = &Search{} case "extension": - return &Extension{}, nil + widget = &Extension{} default: return nil, fmt.Errorf("unknown widget type: %s", widgetType) } + + widget.SetID(uniqueID.Add(1)) + + return widget, nil } type Widgets []Widget @@ -90,7 +100,7 @@ func (w *Widgets) UnmarshalYAML(node *yaml.Node) error { return err } - if err = widget.Initialize(); err != nil { + if err := widget.Initialize(); err != nil { return err } @@ -106,6 +116,9 @@ type Widget interface { Update(context.Context) Render() template.HTML GetType() string + GetID() uint64 + SetID(uint64) + HandleRequest(w http.ResponseWriter, r *http.Request) } type cacheType int @@ -117,6 +130,7 @@ const ( ) type widgetBase struct { + ID uint64 `yaml:"-"` Type string `yaml:"type"` Title string `yaml:"title"` 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 { return w.Type }