refactor(templates): remove duplicate functions and use shared utilities
This commit is contained in:
@@ -1,26 +1,30 @@
|
|||||||
package templates
|
package templates
|
||||||
|
|
||||||
import "mal/internal/jikan"
|
import (
|
||||||
import "mal/internal/shared/ui"
|
"fmt"
|
||||||
import "fmt"
|
|
||||||
|
"mal/integrations/jikan"
|
||||||
|
"mal/web/components"
|
||||||
|
"mal/web/shared"
|
||||||
|
)
|
||||||
|
|
||||||
templ StudioDetails(producer jikan.ProducerResponse, animes []jikan.Anime, hasNext bool, nextPage int) {
|
templ StudioDetails(producer jikan.ProducerResponse, animes []jikan.Anime, hasNext bool, nextPage int) {
|
||||||
@Layout("mal - "+getProducerName(producer), true) {
|
@Layout("mal - "+shared.GetProducerName(producer), true) {
|
||||||
<div class="grid gap-5">
|
<div class="grid gap-5">
|
||||||
<div class="grid gap-4 bg-(--panel) p-4">
|
<div class="grid gap-4 bg-(--panel) p-4">
|
||||||
<div class="flex items-start gap-4">
|
<div class="flex items-start gap-4">
|
||||||
if producer.Data.Images.Jpg.ImageURL != "" {
|
if producer.Data.Images.Jpg.ImageURL != "" {
|
||||||
<img
|
<img
|
||||||
src={ producer.Data.Images.Jpg.ImageURL }
|
src={ producer.Data.Images.Jpg.ImageURL }
|
||||||
alt={ getProducerName(producer) }
|
alt={ shared.GetProducerName(producer) }
|
||||||
class="h-24 w-24 object-contain"
|
class="h-24 w-24 object-contain"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<h1 class="text-xl font-semibold">{ getProducerName(producer) }</h1>
|
<h1 class="text-xl font-semibold">{ shared.GetProducerName(producer) }</h1>
|
||||||
if producer.Data.Established != "" {
|
if producer.Data.Established != "" {
|
||||||
<p class="mt-1 text-sm text-(--text-muted)">
|
<p class="mt-1 text-sm text-(--text-muted)">
|
||||||
Established: { formatEstablishedDate(producer.Data.Established) }
|
Established: { shared.FormatEstablishedDate(producer.Data.Established) }
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
if producer.Data.Count > 0 {
|
if producer.Data.Count > 0 {
|
||||||
@@ -39,7 +43,7 @@ templ StudioDetails(producer jikan.ProducerResponse, animes []jikan.Anime, hasNe
|
|||||||
<div class="grid grid-cols-2 gap-3 sm:grid-cols-3 md:gap-4 lg:grid-cols-4 xl:grid-cols-5" id="studio-anime-content">
|
<div class="grid grid-cols-2 gap-3 sm:grid-cols-3 md:gap-4 lg:grid-cols-4 xl:grid-cols-5" id="studio-anime-content">
|
||||||
for _, anime := range animes {
|
for _, anime := range animes {
|
||||||
<div class="min-w-0" data-id={ fmt.Sprintf("%d", anime.MalID) }>
|
<div class="min-w-0" data-id={ fmt.Sprintf("%d", anime.MalID) }>
|
||||||
@ui.AnimeCard(ui.AnimeCardProps{
|
@components.AnimeCard(components.AnimeCardProps{
|
||||||
ID: anime.MalID,
|
ID: anime.MalID,
|
||||||
Title: anime.DisplayTitle(),
|
Title: anime.DisplayTitle(),
|
||||||
ImageURL: anime.ImageURL(),
|
ImageURL: anime.ImageURL(),
|
||||||
@@ -67,7 +71,7 @@ templ StudioLoadMore(studioID int, nextPage int) {
|
|||||||
templ StudioAnimeItems(animes []jikan.Anime, hasNext bool, studioID int, nextPage int) {
|
templ StudioAnimeItems(animes []jikan.Anime, hasNext bool, studioID int, nextPage int) {
|
||||||
for _, anime := range animes {
|
for _, anime := range animes {
|
||||||
<div class="min-w-0" data-id={ fmt.Sprintf("%d", anime.MalID) }>
|
<div class="min-w-0" data-id={ fmt.Sprintf("%d", anime.MalID) }>
|
||||||
@ui.AnimeCard(ui.AnimeCardProps{
|
@components.AnimeCard(components.AnimeCardProps{
|
||||||
ID: anime.MalID,
|
ID: anime.MalID,
|
||||||
Title: anime.DisplayTitle(),
|
Title: anime.DisplayTitle(),
|
||||||
ImageURL: anime.ImageURL(),
|
ImageURL: anime.ImageURL(),
|
||||||
@@ -98,19 +102,3 @@ templ StudioAnimeItems(animes []jikan.Anime, hasNext bool, studioID int, nextPag
|
|||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
}
|
}
|
||||||
|
|
||||||
func getProducerName(producer jikan.ProducerResponse) string {
|
|
||||||
for _, title := range producer.Data.Titles {
|
|
||||||
if title.Type == "Default" {
|
|
||||||
return title.Title
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "Studio"
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatEstablishedDate(date string) string {
|
|
||||||
if len(date) >= 10 {
|
|
||||||
return date[:10]
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package templates
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"mal/integrations/jikan"
|
"mal/integrations/jikan"
|
||||||
"mal/web/components"
|
"mal/web/components"
|
||||||
@@ -124,79 +123,3 @@ templ WatchPage(anime jikan.Anime, data shared.WatchPageData) {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
templ LoadingIndicatorSmall() {
|
|
||||||
<div class="flex items-center justify-center py-8">
|
|
||||||
<div class="h-5 w-5 animate-spin border-2 border-(--panel-soft) border-t-(--accent)"></div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
templ EpisodeList(episodes []jikan.Episode, currentEpisode string, animeID int) {
|
|
||||||
if len(episodes) == 0 {
|
|
||||||
<p class="py-4 text-center text-sm text-(--text-muted)">No episodes available</p>
|
|
||||||
} else {
|
|
||||||
<div class="flex flex-col">
|
|
||||||
for _, ep := range episodes {
|
|
||||||
@EpisodeItem(ep, currentEpisode, animeID)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
templ EpisodeItem(episode jikan.Episode, currentEpisode string, animeID int) {
|
|
||||||
{{ isCurrent := fmt.Sprintf("%d", episode.MalID) == currentEpisode }}
|
|
||||||
<a
|
|
||||||
href={ templ.URL(fmt.Sprintf("/watch/%d/%d", animeID, episode.MalID)) }
|
|
||||||
class={
|
|
||||||
"flex items-center gap-3 px-3 py-2.5 text-sm no-underline transition-colors border-b border-(--panel-soft) last:border-0",
|
|
||||||
templ.KV("bg-white/5 text-white", isCurrent),
|
|
||||||
templ.KV("text-(--text-muted) hover:bg-white/5 hover:text-(--text)", !isCurrent),
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class={
|
|
||||||
"flex shrink-0 items-center justify-center font-medium w-6",
|
|
||||||
templ.KV("text-(--text)", isCurrent),
|
|
||||||
templ.KV("text-(--text-faint)", !isCurrent),
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{ fmt.Sprintf("%d", episode.MalID) }
|
|
||||||
</span>
|
|
||||||
<span class="min-w-0 truncate font-medium">
|
|
||||||
if episode.Title != "" {
|
|
||||||
{ episode.Title }
|
|
||||||
} else {
|
|
||||||
Episode { fmt.Sprintf("%d", episode.MalID) }
|
|
||||||
}
|
|
||||||
</span>
|
|
||||||
<div class="ml-auto flex items-center gap-2">
|
|
||||||
if episode.Filler {
|
|
||||||
<span class="shrink-0 px-1.5 py-0.5 text-[9px] uppercase tracking-wider bg-yellow-900/50 text-yellow-400">
|
|
||||||
Filler
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
if episode.Recap {
|
|
||||||
<span class="shrink-0 px-1.5 py-0.5 text-[9px] uppercase tracking-wider bg-blue-900/50 text-blue-400">
|
|
||||||
Recap
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
if isCurrent {
|
|
||||||
<svg
|
|
||||||
class="h-4 w-4 shrink-0 text-white"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="currentColor"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<polygon points="5 3 19 12 5 21 5 3"></polygon>
|
|
||||||
</svg>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildStreamURL(mode string, token string) string {
|
|
||||||
if token == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("/watch/proxy/stream?mode=%s&token=%s", url.QueryEscape(mode), url.QueryEscape(token))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ package templates
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"mal/internal/db"
|
"mal/internal/db"
|
||||||
"mal/web/components"
|
"mal/web/components"
|
||||||
"mal/web/components/watchlist"
|
"mal/web/components/watchlist"
|
||||||
"math"
|
"mal/web/shared"
|
||||||
)
|
)
|
||||||
|
|
||||||
templ Watchlist(
|
templ Watchlist(
|
||||||
@@ -59,14 +60,14 @@ templ Watchlist(
|
|||||||
class="flex flex-wrap gap-2 max-md:flex-nowrap max-md:overflow-x-auto max-md:pb-1"
|
class="flex flex-wrap gap-2 max-md:flex-nowrap max-md:overflow-x-auto max-md:pb-1"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href={ templ.URL(watchlistURL("grid", currentStatus, sortBy, sortOrder)) }
|
href={ templ.URL(shared.WatchlistURL("grid", currentStatus, sortBy, sortOrder)) }
|
||||||
class={ tabClass(layout == "grid") }
|
class={ shared.TabClass(layout == "grid") }
|
||||||
>
|
>
|
||||||
Grid
|
Grid
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href={ templ.URL(watchlistURL("table", currentStatus, sortBy, sortOrder)) }
|
href={ templ.URL(shared.WatchlistURL("table", currentStatus, sortBy, sortOrder)) }
|
||||||
class={ tabClass(layout == "table") }
|
class={ shared.TabClass(layout == "table") }
|
||||||
>
|
>
|
||||||
Table
|
Table
|
||||||
</a>
|
</a>
|
||||||
@@ -77,38 +78,38 @@ templ Watchlist(
|
|||||||
class="mb-3 flex flex-wrap gap-2 max-md:flex-nowrap max-md:overflow-x-auto max-md:pb-1"
|
class="mb-3 flex flex-wrap gap-2 max-md:flex-nowrap max-md:overflow-x-auto max-md:pb-1"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href={ templ.URL(watchlistURL(layout, "all", sortBy, sortOrder)) }
|
href={ templ.URL(shared.WatchlistURL(layout, "all", sortBy, sortOrder)) }
|
||||||
class={ tabClass(currentStatus == "all") }
|
class={ shared.TabClass(currentStatus == "all") }
|
||||||
>
|
>
|
||||||
All
|
All
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href={ templ.URL(watchlistURL(layout, "watching", sortBy, sortOrder)) }
|
href={ templ.URL(shared.WatchlistURL(layout, "watching", sortBy, sortOrder)) }
|
||||||
class={ tabClass(currentStatus == "watching") }
|
class={ shared.TabClass(currentStatus == "watching") }
|
||||||
>
|
>
|
||||||
Watching
|
Watching
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href={ templ.URL(watchlistURL(layout, "on_hold", sortBy, sortOrder)) }
|
href={ templ.URL(shared.WatchlistURL(layout, "on_hold", sortBy, sortOrder)) }
|
||||||
class={ tabClass(currentStatus == "on_hold") }
|
class={ shared.TabClass(currentStatus == "on_hold") }
|
||||||
>
|
>
|
||||||
On hold
|
On hold
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href={ templ.URL(watchlistURL(layout, "plan_to_watch", sortBy, sortOrder)) }
|
href={ templ.URL(shared.WatchlistURL(layout, "plan_to_watch", sortBy, sortOrder)) }
|
||||||
class={ tabClass(currentStatus == "plan_to_watch") }
|
class={ shared.TabClass(currentStatus == "plan_to_watch") }
|
||||||
>
|
>
|
||||||
Plan to watch
|
Plan to watch
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href={ templ.URL(watchlistURL(layout, "dropped", sortBy, sortOrder)) }
|
href={ templ.URL(shared.WatchlistURL(layout, "dropped", sortBy, sortOrder)) }
|
||||||
class={ tabClass(currentStatus == "dropped") }
|
class={ shared.TabClass(currentStatus == "dropped") }
|
||||||
>
|
>
|
||||||
Dropped
|
Dropped
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href={ templ.URL(watchlistURL(layout, "completed", sortBy, sortOrder)) }
|
href={ templ.URL(shared.WatchlistURL(layout, "completed", sortBy, sortOrder)) }
|
||||||
class={ tabClass(currentStatus == "completed") }
|
class={ shared.TabClass(currentStatus == "completed") }
|
||||||
>
|
>
|
||||||
Completed
|
Completed
|
||||||
</a>
|
</a>
|
||||||
@@ -138,7 +139,7 @@ templ Watchlist(
|
|||||||
id={ fmt.Sprintf("watchlist-entry-%d", entry.AnimeID) }
|
id={ fmt.Sprintf("watchlist-entry-%d", entry.AnimeID) }
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href={ templ.URL(watchURL(entry)) }
|
href={ templ.URL(shared.AnimeURL(entry.AnimeID)) }
|
||||||
class="flex flex-col bg-transparent text-inherit no-underline"
|
class="flex flex-col bg-transparent text-inherit no-underline"
|
||||||
>
|
>
|
||||||
<div class="flex w-full aspect-2/3 justify-center overflow-hidden">
|
<div class="flex w-full aspect-2/3 justify-center overflow-hidden">
|
||||||
@@ -183,7 +184,7 @@ templ Watchlist(
|
|||||||
id={ fmt.Sprintf("watchlist-entry-%d", entry.AnimeID) }
|
id={ fmt.Sprintf("watchlist-entry-%d", entry.AnimeID) }
|
||||||
>
|
>
|
||||||
<td class="p-2.5">
|
<td class="p-2.5">
|
||||||
<a href={ templ.URL(watchURL(entry)) }>
|
<a href={ templ.URL(shared.AnimeURL(entry.AnimeID)) }>
|
||||||
<img
|
<img
|
||||||
src={ entry.ImageUrl }
|
src={ entry.ImageUrl }
|
||||||
alt={ entry.DisplayTitle() }
|
alt={ entry.DisplayTitle() }
|
||||||
@@ -193,7 +194,7 @@ templ Watchlist(
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="p-2.5 font-medium">
|
<td class="p-2.5 font-medium">
|
||||||
<a href={ templ.URL(watchURL(entry)) }>
|
<a href={ templ.URL(shared.AnimeURL(entry.AnimeID)) }>
|
||||||
{ entry.DisplayTitle() }
|
{ entry.DisplayTitle() }
|
||||||
</a>
|
</a>
|
||||||
@watchlist.Progress(entry)
|
@watchlist.Progress(entry)
|
||||||
@@ -216,41 +217,3 @@ templ Watchlist(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
templ ifHasProgress(entry db.GetUserWatchListRow) {
|
|
||||||
if entry.CurrentEpisode.Valid && entry.CurrentEpisode.Int64 > 0 && entry.Status != "completed" {
|
|
||||||
<p class="m-0 mt-1 text-xs text-(--text-faint)">
|
|
||||||
Continue ep { fmt.Sprintf("%d", entry.CurrentEpisode.Int64) }
|
|
||||||
if entry.CurrentTimeSeconds > 0 {
|
|
||||||
{ fmt.Sprintf(" · %s", formatProgressTime(entry.CurrentTimeSeconds)) }
|
|
||||||
}
|
|
||||||
</p>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func watchURL(entry db.GetUserWatchListRow) string {
|
|
||||||
return fmt.Sprintf("/anime/%d", entry.AnimeID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatProgressTime(seconds float64) string {
|
|
||||||
total := int(math.Round(seconds))
|
|
||||||
if total < 0 {
|
|
||||||
total = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
minutes := total / 60
|
|
||||||
remainingSeconds := total % 60
|
|
||||||
return fmt.Sprintf("%02d:%02d", minutes, remainingSeconds)
|
|
||||||
}
|
|
||||||
|
|
||||||
func tabClass(active bool) string {
|
|
||||||
base := "shrink-0 whitespace-nowrap bg-(--panel-soft) px-2 py-1 text-xs text-(--text-muted) no-underline hover:bg-(--surface-tab-hover) hover:text-(--text) hover:no-underline"
|
|
||||||
if active {
|
|
||||||
return "shrink-0 whitespace-nowrap bg-(--surface-tab-active) px-2 py-1 text-xs text-(--accent) no-underline hover:no-underline"
|
|
||||||
}
|
|
||||||
return base
|
|
||||||
}
|
|
||||||
|
|
||||||
func watchlistURL(view string, status string, sortBy string, sortOrder string) string {
|
|
||||||
return fmt.Sprintf("/watchlist?view=%s&status=%s&sort=%s&order=%s", view, status, sortBy, sortOrder)
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user