Enable adding toasts in full page reloads (and HTMX where it's hx-boost or target = body)

- use toasts to display non-fatal scraping errors for Tweets
This commit is contained in:
Alessio 2024-08-18 16:36:22 -07:00
parent 91f722b7fa
commit ee2b287fd9
5 changed files with 45 additions and 8 deletions

View File

@ -50,13 +50,16 @@ func (app *Application) ensure_tweet(id scraper.TweetID, is_forced bool, is_conv
if is_needing_scrape && !app.IsScrapingDisabled {
trove, err := scraper.GetTweetFullAPIV2(id, 50) // TODO: parameterizable
// Save the trove unless there was an unrecoverable error
if err == nil || errors.Is(err, scraper.END_OF_FEED) || errors.Is(err, scraper.ErrRateLimited) {
app.Profile.SaveTweetTrove(trove, false)
go app.Profile.SaveTweetTrove(trove, true) // Download the content in the background
_, is_available = trove.Tweets[id]
} else {
app.ErrorLog.Print(err)
// TODO: show error in UI
}
if err != nil && !errors.Is(err, scraper.END_OF_FEED) {
return scraper.Tweet{}, fmt.Errorf("scraper error: %w", err)
}
} else if is_needing_scrape {
app.InfoLog.Printf("Would have scraped Tweet: %d", id)
@ -115,10 +118,31 @@ func (app *Application) TweetDetail(w http.ResponseWriter, r *http.Request) {
is_conversation_required := len(parts) <= 2 || (parts[2] != "like" && parts[2] != "unlike")
tweet, err := app.ensure_tweet(tweet_id, is_scrape_required, is_conversation_required)
var toasts []Toast
if err != nil {
app.ErrorLog.Print(fmt.Errorf("TweetDetail (%d): %w", tweet_id, err))
if errors.Is(err, ErrNotFound) {
app.error_404(w)
// Can't find the tweet; abort
app.toast(w, r, Toast{Title: "Not found", Message: "Tweet not found in database", Type: "error"})
return
} else if errors.Is(err, scraper.ErrSessionInvalidated) {
toasts = append(toasts, Toast{
Title: "Session invalidated",
Message: "Your session has been invalidated by Twitter. You'll have to log in again.",
Type: "error",
})
// TODO: delete the invalidated session
} else if errors.Is(err, scraper.ErrRateLimited) {
toasts = append(toasts, Toast{
Title: "Rate limited",
Message: "While scraping, a rate-limit was hit. Results may be incomplete.",
Type: "warning",
})
} else {
panic(err) // Let the 500 handler deal with it
}
}
req_with_tweet := r.WithContext(add_tweet_to_context(r.Context(), tweet))
if len(parts) > 2 && parts[2] == "like" {
@ -137,7 +161,7 @@ func (app *Application) TweetDetail(w http.ResponseWriter, r *http.Request) {
app.buffered_render_page(
w,
"tpl/tweet_detail.tpl",
PageGlobalData{TweetTrove: twt_detail.TweetTrove, FocusedTweetID: data.MainTweetID},
PageGlobalData{TweetTrove: twt_detail.TweetTrove, FocusedTweetID: data.MainTweetID, Toasts: toasts},
data,
)
}

View File

@ -25,6 +25,7 @@ type PageGlobalData struct {
SearchText string
FocusedTweetID scraper.TweetID
Notifications
Toasts []Toast
}
func (d PageGlobalData) Tweet(id scraper.TweetID) scraper.Tweet {

View File

@ -38,7 +38,7 @@ func (app *Application) error_500(w http.ResponseWriter, r *http.Request, err er
}
func (app *Application) toast(w http.ResponseWriter, r *http.Request, t Toast) {
// Reset the HTMX response to return an error toast and put it in the
// Reset the HTMX response to return an error toast and append it to the Toasts container
w.Header().Set("HX-Reswap", "beforeend")
w.Header().Set("HX-Retarget", "#toasts")
w.Header().Set("HX-Push-Url", "false")
@ -46,6 +46,12 @@ func (app *Application) toast(w http.ResponseWriter, r *http.Request, t Toast) {
app.buffered_render_htmx(w, "toast", PageGlobalData{}, t)
}
// `Type` can be:
// - "success" (default)
// - "warning"
// - "error"
//
// If "AutoCloseDelay" is not 0, the toast will auto-disappear after that many milliseconds.
type Toast struct {
Title string
Message string

View File

@ -50,6 +50,9 @@
</div>
</dialog>
<div class="toasts" id="toasts">
{{range (global_data).Toasts}}
{{template "toast" .}}
{{end}}
</div>
</body>
</html>

View File

@ -5,6 +5,9 @@
hx-on::load="setTimeout(() => this.remove(), {{.AutoCloseDelay}} + 2000); setTimeout(() => this.classList.add('disappearing'), {{.AutoCloseDelay}})"
{{end}}
>
{{if .Title}}
<h2 class="toast__title">{{.Title}}</h2>
{{end}}
<span class="toast__message">{{.Message}}</span>
{{if not .AutoCloseDelay}}
<button class="suicide" onclick="this.parentElement.remove()">X</button>