feat: sync watchlist state across quick add and dropdown
This commit is contained in:
@@ -182,6 +182,7 @@ func (h *Handler) HandleAnimeDetails(w http.ResponseWriter, r *http.Request) {
|
||||
user, _ := r.Context().Value(ctxpkg.UserKey).(*database.User)
|
||||
|
||||
var status string
|
||||
var watchlistIDs []int64
|
||||
if user != nil {
|
||||
entry, err := h.db.GetWatchListEntry(r.Context(), database.GetWatchListEntryParams{
|
||||
UserID: user.ID,
|
||||
@@ -190,13 +191,19 @@ func (h *Handler) HandleAnimeDetails(w http.ResponseWriter, r *http.Request) {
|
||||
if err == nil {
|
||||
status = entry.Status
|
||||
}
|
||||
watchlist, _ := h.db.GetUserWatchList(r.Context(), user.ID)
|
||||
watchlistIDs = make([]int64, len(watchlist))
|
||||
for i, e := range watchlist {
|
||||
watchlistIDs[i] = e.AnimeID
|
||||
}
|
||||
}
|
||||
|
||||
if err := templates.GetRenderer().ExecuteTemplate(w, "anime.gohtml", map[string]any{
|
||||
"Anime": anime,
|
||||
"User": user,
|
||||
"Status": status,
|
||||
"CurrentPath": r.URL.Path,
|
||||
"Anime": anime,
|
||||
"User": user,
|
||||
"Status": status,
|
||||
"CurrentPath": r.URL.Path,
|
||||
"WatchlistIDs": watchlistIDs,
|
||||
}); err != nil {
|
||||
log.Printf("render error: %v", err)
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
@@ -218,9 +225,19 @@ func (h *Handler) HandleHTMLWatchOrder(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
user, _ := r.Context().Value(ctxpkg.UserKey).(*database.User)
|
||||
watchlistMap := make(map[int64]bool)
|
||||
if user != nil {
|
||||
watchlist, _ := h.db.GetUserWatchList(r.Context(), user.ID)
|
||||
for _, entry := range watchlist {
|
||||
watchlistMap[entry.AnimeID] = true
|
||||
}
|
||||
}
|
||||
|
||||
if err := templates.GetRenderer().ExecuteFragment(w, "anime.gohtml", "watch_order", map[string]any{
|
||||
"Relations": relations,
|
||||
"AnimeID": id,
|
||||
"Relations": relations,
|
||||
"AnimeID": id,
|
||||
"WatchlistMap": watchlistMap,
|
||||
}); err != nil {
|
||||
log.Printf("render error: %v", err)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{{define "title"}}{{.Anime.DisplayTitle}}{{end}}
|
||||
{{define "content"}}
|
||||
{{if .WatchlistIDs}}<script>initWatchlist({{.WatchlistIDs}})</script>{{end}}
|
||||
{{$anime := .Anime}}
|
||||
|
||||
<div class="flex flex-col gap-10">
|
||||
|
||||
@@ -48,15 +48,47 @@
|
||||
watchlistIds.delete(id)
|
||||
btn.classList.remove('in-watchlist')
|
||||
btn.setAttribute('aria-label', 'Add to Watchlist')
|
||||
|
||||
// Update dropdown status if on anime page
|
||||
syncWatchlistDropdown(id, false)
|
||||
} else {
|
||||
watchlistIds.add(id)
|
||||
btn.classList.add('in-watchlist')
|
||||
btn.setAttribute('aria-label', 'Remove from Watchlist')
|
||||
|
||||
// Update dropdown status if on anime page
|
||||
syncWatchlistDropdown(id, true)
|
||||
}
|
||||
|
||||
// Update all other watchlist icons on the page for this anime
|
||||
document.querySelectorAll('.watchlist-icon').forEach(function(icon) {
|
||||
const button = icon.closest('button')
|
||||
if (button && button !== btn) {
|
||||
const malId = button.dataset.malId
|
||||
if (malId && parseInt(malId) === id) {
|
||||
if (watchlistIds.has(id)) {
|
||||
button.classList.add('in-watchlist')
|
||||
} else {
|
||||
button.classList.remove('in-watchlist')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function syncWatchlistDropdown(id, inWatchlist) {
|
||||
const statusDisplay = document.getElementById('watchlist-status-display-' + id)
|
||||
if (statusDisplay) {
|
||||
statusDisplay.textContent = inWatchlist ? 'Plan to Watch' : 'Add to Watchlist'
|
||||
const removeContainer = document.getElementById('remove-watchlist-container-' + id)
|
||||
if (removeContainer) {
|
||||
removeContainer.classList.toggle('hidden', !inWatchlist)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeFromWatchlist(id, btn) {
|
||||
fetch(`/api/watchlist/${id}`, { method: 'DELETE' }).then(res => {
|
||||
if (res.ok) {
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
{{end}}
|
||||
|
||||
<div class="mt-auto flex items-center justify-start pb-2 pl-2">
|
||||
<button type="button" onclick="event.preventDefault(); event.stopPropagation(); toggleWatchlist({{$anime.MalID}}, this)" class="text-accent hover:text-accent/80 transition-colors focus:outline-none {{if $isWatchlist}}in-watchlist{{end}}" aria-label="{{if $isWatchlist}}Remove from Watchlist{{else}}Add to Watchlist{{end}}">
|
||||
<button type="button" data-mal-id="{{$anime.MalID}}" onclick="event.preventDefault(); event.stopPropagation(); toggleWatchlist({{$anime.MalID}}, this)" class="text-accent hover:text-accent/80 transition-colors focus:outline-none {{if $isWatchlist}}in-watchlist{{end}}" aria-label="{{if $isWatchlist}}Remove from Watchlist{{else}}Add to Watchlist{{end}}">
|
||||
<svg class="size-6 shadow-black drop-shadow-md watchlist-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4 2xl:grid-cols-6">
|
||||
{{range .Relations}}
|
||||
<div class="group relative">
|
||||
{{template "anime_card" dict "Anime" .Anime "WithActions" true "Compact" true "HasTopBadge" true}}
|
||||
{{template "anime_card" dict "Anime" .Anime "WithActions" true "Compact" true "HasTopBadge" true "IsWatchlist" (index $.WatchlistMap .Anime.MalID)}}
|
||||
{{if eq .Anime.MalID $.AnimeID}}
|
||||
<div class="bg-accent absolute -top-2 -right-2 z-20 px-2 py-0.5 text-[10px] font-bold text-white shadow-md">
|
||||
CURRENT
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
{{if $status}}
|
||||
{{if eq $status "watching"}}Watching{{end}}
|
||||
{{if eq $status "completed"}}Completed{{end}}
|
||||
{{if eq $status "plan to watch"}}Plan to Watch{{end}}
|
||||
{{if eq $status "plan_to_watch"}}Plan to Watch{{end}}
|
||||
{{if eq $status "dropped"}}Dropped{{end}}
|
||||
{{else}}
|
||||
Add to Watchlist
|
||||
@@ -63,6 +63,17 @@
|
||||
document.getElementById('watchlist-status-display-' + id).textContent = display;
|
||||
document.getElementById('remove-watchlist-container-' + id).classList.remove('hidden');
|
||||
|
||||
// Update all watchlist icons on the page
|
||||
document.querySelectorAll('.watchlist-icon').forEach(function(icon) {
|
||||
const button = icon.closest('button')
|
||||
if (button) {
|
||||
const malId = button.dataset.malId
|
||||
if (malId && parseInt(malId) === id) {
|
||||
button.classList.add('in-watchlist')
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Close dropdown after a small delay to let click event finish
|
||||
requestAnimationFrame(() => {
|
||||
const dropdown = btn.closest('ui-dropdown');
|
||||
@@ -79,6 +90,17 @@
|
||||
document.getElementById('watchlist-status-display-' + id).textContent = 'Add to Watchlist';
|
||||
document.getElementById('remove-watchlist-container-' + id).classList.add('hidden');
|
||||
|
||||
// Update all watchlist icons on the page
|
||||
document.querySelectorAll('.watchlist-icon').forEach(function(icon) {
|
||||
const button = icon.closest('button')
|
||||
if (button) {
|
||||
const malId = button.dataset.malId
|
||||
if (malId && parseInt(malId) === id) {
|
||||
button.classList.remove('in-watchlist')
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Close dropdown
|
||||
const btn = document.getElementById('watchlist-status-display-' + id);
|
||||
if (btn) {
|
||||
|
||||
Reference in New Issue
Block a user