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)
|
user, _ := r.Context().Value(ctxpkg.UserKey).(*database.User)
|
||||||
|
|
||||||
var status string
|
var status string
|
||||||
|
var watchlistIDs []int64
|
||||||
if user != nil {
|
if user != nil {
|
||||||
entry, err := h.db.GetWatchListEntry(r.Context(), database.GetWatchListEntryParams{
|
entry, err := h.db.GetWatchListEntry(r.Context(), database.GetWatchListEntryParams{
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
@@ -190,6 +191,11 @@ func (h *Handler) HandleAnimeDetails(w http.ResponseWriter, r *http.Request) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
status = entry.Status
|
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{
|
if err := templates.GetRenderer().ExecuteTemplate(w, "anime.gohtml", map[string]any{
|
||||||
@@ -197,6 +203,7 @@ func (h *Handler) HandleAnimeDetails(w http.ResponseWriter, r *http.Request) {
|
|||||||
"User": user,
|
"User": user,
|
||||||
"Status": status,
|
"Status": status,
|
||||||
"CurrentPath": r.URL.Path,
|
"CurrentPath": r.URL.Path,
|
||||||
|
"WatchlistIDs": watchlistIDs,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Printf("render error: %v", err)
|
log.Printf("render error: %v", err)
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
@@ -218,9 +225,19 @@ func (h *Handler) HandleHTMLWatchOrder(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
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{
|
if err := templates.GetRenderer().ExecuteFragment(w, "anime.gohtml", "watch_order", map[string]any{
|
||||||
"Relations": relations,
|
"Relations": relations,
|
||||||
"AnimeID": id,
|
"AnimeID": id,
|
||||||
|
"WatchlistMap": watchlistMap,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Printf("render error: %v", err)
|
log.Printf("render error: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{{define "title"}}{{.Anime.DisplayTitle}}{{end}}
|
{{define "title"}}{{.Anime.DisplayTitle}}{{end}}
|
||||||
{{define "content"}}
|
{{define "content"}}
|
||||||
|
{{if .WatchlistIDs}}<script>initWatchlist({{.WatchlistIDs}})</script>{{end}}
|
||||||
{{$anime := .Anime}}
|
{{$anime := .Anime}}
|
||||||
|
|
||||||
<div class="flex flex-col gap-10">
|
<div class="flex flex-col gap-10">
|
||||||
|
|||||||
@@ -48,14 +48,46 @@
|
|||||||
watchlistIds.delete(id)
|
watchlistIds.delete(id)
|
||||||
btn.classList.remove('in-watchlist')
|
btn.classList.remove('in-watchlist')
|
||||||
btn.setAttribute('aria-label', 'Add to Watchlist')
|
btn.setAttribute('aria-label', 'Add to Watchlist')
|
||||||
|
|
||||||
|
// Update dropdown status if on anime page
|
||||||
|
syncWatchlistDropdown(id, false)
|
||||||
} else {
|
} else {
|
||||||
watchlistIds.add(id)
|
watchlistIds.add(id)
|
||||||
btn.classList.add('in-watchlist')
|
btn.classList.add('in-watchlist')
|
||||||
btn.setAttribute('aria-label', 'Remove from 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) {
|
function removeFromWatchlist(id, btn) {
|
||||||
fetch(`/api/watchlist/${id}`, { method: 'DELETE' }).then(res => {
|
fetch(`/api/watchlist/${id}`, { method: 'DELETE' }).then(res => {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<div class="mt-auto flex items-center justify-start pb-2 pl-2">
|
<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>
|
<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>
|
</button>
|
||||||
</div>
|
</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">
|
<div class="grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4 2xl:grid-cols-6">
|
||||||
{{range .Relations}}
|
{{range .Relations}}
|
||||||
<div class="group relative">
|
<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}}
|
{{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">
|
<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
|
CURRENT
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
{{if $status}}
|
{{if $status}}
|
||||||
{{if eq $status "watching"}}Watching{{end}}
|
{{if eq $status "watching"}}Watching{{end}}
|
||||||
{{if eq $status "completed"}}Completed{{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}}
|
{{if eq $status "dropped"}}Dropped{{end}}
|
||||||
{{else}}
|
{{else}}
|
||||||
Add to Watchlist
|
Add to Watchlist
|
||||||
@@ -63,6 +63,17 @@
|
|||||||
document.getElementById('watchlist-status-display-' + id).textContent = display;
|
document.getElementById('watchlist-status-display-' + id).textContent = display;
|
||||||
document.getElementById('remove-watchlist-container-' + id).classList.remove('hidden');
|
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
|
// Close dropdown after a small delay to let click event finish
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
const dropdown = btn.closest('ui-dropdown');
|
const dropdown = btn.closest('ui-dropdown');
|
||||||
@@ -79,6 +90,17 @@
|
|||||||
document.getElementById('watchlist-status-display-' + id).textContent = 'Add to Watchlist';
|
document.getElementById('watchlist-status-display-' + id).textContent = 'Add to Watchlist';
|
||||||
document.getElementById('remove-watchlist-container-' + id).classList.add('hidden');
|
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
|
// Close dropdown
|
||||||
const btn = document.getElementById('watchlist-status-display-' + id);
|
const btn = document.getElementById('watchlist-status-display-' + id);
|
||||||
if (btn) {
|
if (btn) {
|
||||||
|
|||||||
Reference in New Issue
Block a user