Added sorting options for anime lists
This commit is contained in:
parent
cdfb66657a
commit
e28618029a
@ -156,63 +156,6 @@ func (list *AnimeList) User() *User {
|
|||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort ...
|
|
||||||
func (list *AnimeList) Sort() {
|
|
||||||
list.Lock()
|
|
||||||
defer list.Unlock()
|
|
||||||
|
|
||||||
sort.Slice(list.Items, func(i, j int) bool {
|
|
||||||
a := list.Items[i]
|
|
||||||
b := list.Items[j]
|
|
||||||
|
|
||||||
if (a.Status != AnimeListStatusWatching && a.Status != AnimeListStatusPlanned) && (b.Status != AnimeListStatusWatching && b.Status != AnimeListStatusPlanned) {
|
|
||||||
if a.Rating.Overall == b.Rating.Overall {
|
|
||||||
return a.Anime().Title.Canonical < b.Anime().Title.Canonical
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.Rating.Overall > b.Rating.Overall
|
|
||||||
}
|
|
||||||
|
|
||||||
epsA := a.Anime().UpcomingEpisode()
|
|
||||||
epsB := b.Anime().UpcomingEpisode()
|
|
||||||
|
|
||||||
if epsA == nil && epsB == nil {
|
|
||||||
if a.Rating.Overall == b.Rating.Overall {
|
|
||||||
return a.Anime().Title.Canonical < b.Anime().Title.Canonical
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.Rating.Overall > b.Rating.Overall
|
|
||||||
}
|
|
||||||
|
|
||||||
if epsA == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if epsB == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return epsA.Episode.AiringDate.Start < epsB.Episode.AiringDate.Start
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// SortByRating sorts the anime list by overall rating.
|
|
||||||
func (list *AnimeList) SortByRating() {
|
|
||||||
list.Lock()
|
|
||||||
defer list.Unlock()
|
|
||||||
|
|
||||||
sort.Slice(list.Items, func(i, j int) bool {
|
|
||||||
a := list.Items[i]
|
|
||||||
b := list.Items[j]
|
|
||||||
|
|
||||||
if a.Rating.Overall == b.Rating.Overall {
|
|
||||||
return a.Anime().Title.Canonical < b.Anime().Title.Canonical
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.Rating.Overall > b.Rating.Overall
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Top returns the top entries.
|
// Top returns the top entries.
|
||||||
func (list *AnimeList) Top(count int) []*AnimeListItem {
|
func (list *AnimeList) Top(count int) []*AnimeListItem {
|
||||||
list.Lock()
|
list.Lock()
|
||||||
|
58
arn/AnimeListSort.go
Normal file
58
arn/AnimeListSort.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package arn
|
||||||
|
|
||||||
|
import "sort"
|
||||||
|
|
||||||
|
// Sort sorts the anime list by the given algorithm.
|
||||||
|
func (list *AnimeList) Sort(algorithm string) {
|
||||||
|
list.Lock()
|
||||||
|
defer list.Unlock()
|
||||||
|
|
||||||
|
switch algorithm {
|
||||||
|
case SortByTitle:
|
||||||
|
sort.Slice(list.Items, func(i, j int) bool {
|
||||||
|
a := list.Items[i]
|
||||||
|
b := list.Items[j]
|
||||||
|
|
||||||
|
return a.Anime().Title.Canonical < b.Anime().Title.Canonical
|
||||||
|
})
|
||||||
|
|
||||||
|
case SortByRating:
|
||||||
|
sort.Slice(list.Items, func(i, j int) bool {
|
||||||
|
a := list.Items[i]
|
||||||
|
b := list.Items[j]
|
||||||
|
|
||||||
|
if a.Rating.Overall == b.Rating.Overall {
|
||||||
|
return a.Anime().Title.Canonical < b.Anime().Title.Canonical
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.Rating.Overall > b.Rating.Overall
|
||||||
|
})
|
||||||
|
|
||||||
|
case SortByAiringDate:
|
||||||
|
sort.Slice(list.Items, func(i, j int) bool {
|
||||||
|
a := list.Items[i]
|
||||||
|
b := list.Items[j]
|
||||||
|
|
||||||
|
epsA := a.Anime().UpcomingEpisode()
|
||||||
|
epsB := b.Anime().UpcomingEpisode()
|
||||||
|
|
||||||
|
if epsA == nil && epsB == nil {
|
||||||
|
if a.Rating.Overall == b.Rating.Overall {
|
||||||
|
return a.Anime().Title.Canonical < b.Anime().Title.Canonical
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.Rating.Overall > b.Rating.Overall
|
||||||
|
}
|
||||||
|
|
||||||
|
if epsA == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if epsB == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return epsA.Episode.AiringDate.Start < epsB.Episode.AiringDate.Start
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -30,10 +30,9 @@ const (
|
|||||||
// Settings represents user settings.
|
// Settings represents user settings.
|
||||||
type Settings struct {
|
type Settings struct {
|
||||||
UserID string `json:"userId"`
|
UserID string `json:"userId"`
|
||||||
SortBy string `json:"sortBy"`
|
SortBy string `json:"sortBy" editable:"true"`
|
||||||
TitleLanguage string `json:"titleLanguage" editable:"true"`
|
TitleLanguage string `json:"titleLanguage" editable:"true"`
|
||||||
Providers ServiceProviders `json:"providers"`
|
Providers ServiceProviders `json:"providers"`
|
||||||
Avatar AvatarSettings `json:"avatar"`
|
|
||||||
Format FormatSettings `json:"format"`
|
Format FormatSettings `json:"format"`
|
||||||
Notification NotificationSettings `json:"notification"`
|
Notification NotificationSettings `json:"notification"`
|
||||||
Editor EditorSettings `json:"editor"`
|
Editor EditorSettings `json:"editor"`
|
||||||
@ -110,30 +109,20 @@ type ServiceProviders struct {
|
|||||||
Anime string `json:"anime"`
|
Anime string `json:"anime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AvatarSettings ...
|
|
||||||
type AvatarSettings struct {
|
|
||||||
Source string `json:"source" editable:"true"`
|
|
||||||
SourceURL string `json:"sourceUrl" editable:"true"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CalendarSettings ...
|
// CalendarSettings ...
|
||||||
type CalendarSettings struct {
|
type CalendarSettings struct {
|
||||||
ShowAddedAnimeOnly bool `json:"showAddedAnimeOnly" editable:"true"`
|
ShowAddedAnimeOnly bool `json:"showAddedAnimeOnly" editable:"true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSettings ...
|
// NewSettings creates the default settings for a new user.
|
||||||
func NewSettings(user *User) *Settings {
|
func NewSettings(user *User) *Settings {
|
||||||
return &Settings{
|
return &Settings{
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
SortBy: SortByAiringDate,
|
SortBy: SortByRating,
|
||||||
TitleLanguage: TitleLanguageCanonical,
|
TitleLanguage: TitleLanguageCanonical,
|
||||||
Providers: ServiceProviders{
|
Providers: ServiceProviders{
|
||||||
Anime: "",
|
Anime: "",
|
||||||
},
|
},
|
||||||
Avatar: AvatarSettings{
|
|
||||||
Source: "",
|
|
||||||
SourceURL: "",
|
|
||||||
},
|
|
||||||
Format: FormatSettings{
|
Format: FormatSettings{
|
||||||
RatingsPrecision: 1,
|
RatingsPrecision: 1,
|
||||||
},
|
},
|
||||||
|
38
layout/sidebar/badge.scarlet
Normal file
38
layout/sidebar/badge.scarlet
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
.badge
|
||||||
|
horizontal
|
||||||
|
justify-content center
|
||||||
|
align-items center
|
||||||
|
background reverse-light-color
|
||||||
|
border-radius 50%
|
||||||
|
padding 0.5rem
|
||||||
|
color text-color
|
||||||
|
width 30px
|
||||||
|
height 30px
|
||||||
|
|
||||||
|
:hover
|
||||||
|
color text-color
|
||||||
|
background reverse-light-hover-color
|
||||||
|
|
||||||
|
.sidebar-badge
|
||||||
|
position absolute
|
||||||
|
top 50%
|
||||||
|
transform translateY(-50%)
|
||||||
|
|
||||||
|
:active
|
||||||
|
transform translateY(-50%) translateY(3px)
|
||||||
|
|
||||||
|
.left-badge
|
||||||
|
left 12%
|
||||||
|
|
||||||
|
.right-badge
|
||||||
|
right 12%
|
||||||
|
|
||||||
|
.badge-important
|
||||||
|
background badge-important-bg-color
|
||||||
|
color badge-important-text-color
|
||||||
|
font-weight bold
|
||||||
|
|
||||||
|
:hover
|
||||||
|
background badge-important-hover-bg-color
|
||||||
|
color badge-important-text-color
|
||||||
|
text-shadow none
|
@ -65,42 +65,3 @@ const sidebar-spacing-y = 0.7rem
|
|||||||
.icon
|
.icon
|
||||||
font-size 1rem
|
font-size 1rem
|
||||||
margin-right 0.75rem
|
margin-right 0.75rem
|
||||||
|
|
||||||
.badge
|
|
||||||
horizontal
|
|
||||||
justify-content center
|
|
||||||
align-items center
|
|
||||||
background reverse-light-color
|
|
||||||
border-radius 50%
|
|
||||||
padding 0.5rem
|
|
||||||
color text-color
|
|
||||||
width 30px
|
|
||||||
height 30px
|
|
||||||
|
|
||||||
:hover
|
|
||||||
color text-color
|
|
||||||
background reverse-light-hover-color
|
|
||||||
|
|
||||||
.sidebar-badge
|
|
||||||
position absolute
|
|
||||||
top 50%
|
|
||||||
transform translateY(-50%)
|
|
||||||
|
|
||||||
:active
|
|
||||||
transform translateY(-50%) translateY(3px)
|
|
||||||
|
|
||||||
.left-badge
|
|
||||||
left 12%
|
|
||||||
|
|
||||||
.right-badge
|
|
||||||
right 12%
|
|
||||||
|
|
||||||
.badge-important
|
|
||||||
background badge-important-bg-color
|
|
||||||
color badge-important-text-color
|
|
||||||
font-weight bold
|
|
||||||
|
|
||||||
:hover
|
|
||||||
background badge-important-hover-bg-color
|
|
||||||
color badge-important-text-color
|
|
||||||
text-shadow none
|
|
||||||
|
@ -17,18 +17,14 @@ component AnimeListScrollable(animeListItems []*arn.AnimeListItem, viewUser *arn
|
|||||||
a(href=item.Link(viewUser.Nick))= item.Anime().Title.ByUser(user)
|
a(href=item.Link(viewUser.Nick))= item.Anime().Title.ByUser(user)
|
||||||
|
|
||||||
.anime-list-item-actions
|
.anime-list-item-actions
|
||||||
if user != nil && item.Status != arn.AnimeListStatusCompleted && user.Location.CountryName != "Japan"
|
if user != nil && item.Status != arn.AnimeListStatusCompleted
|
||||||
//- if user.ID == "KpdWUlPzR"
|
|
||||||
//- a(href=arn.Nyaa.GetLink(item.Anime()), title="Search on Nyaa", target="_blank", rel="noopener")
|
|
||||||
//- RawIcon("download")
|
|
||||||
//- else
|
|
||||||
if item.Anime().EpisodeByNumber(item.Episodes + 1) != nil
|
if item.Anime().EpisodeByNumber(item.Episodes + 1) != nil
|
||||||
for _, link := range item.Anime().EpisodeByNumber(item.Episodes + 1).Links
|
for _, link := range item.Anime().EpisodeByNumber(item.Episodes + 1).Links
|
||||||
a.tip(href=link, aria-label="Watch episode " + fmt.Sprint(item.Episodes + 1), target="_blank", rel="noopener")
|
a.tip(href=link, aria-label="Watch episode " + fmt.Sprint(item.Episodes + 1), target="_blank", rel="noopener")
|
||||||
RawIcon("eye")
|
RawIcon("eye")
|
||||||
|
|
||||||
.anime-list-item-airing-date
|
.anime-list-item-airing-date
|
||||||
if (item.Status == arn.AnimeListStatusWatching || item.Status == arn.AnimeListStatusPlanned) && item.Anime().UpcomingEpisode() != nil
|
if item.Status != arn.AnimeListStatusCompleted && item.Anime().UpcomingEpisode() != nil
|
||||||
span.utc-airing-date(data-start-date=item.Anime().UpcomingEpisode().Episode.AiringDate.Start, data-end-date=item.Anime().UpcomingEpisode().Episode.AiringDate.End, data-episode-number=item.Anime().UpcomingEpisode().Episode.Number)
|
span.utc-airing-date(data-start-date=item.Anime().UpcomingEpisode().Episode.AiringDate.Start, data-end-date=item.Anime().UpcomingEpisode().Episode.AiringDate.End, data-episode-number=item.Anime().UpcomingEpisode().Episode.Number)
|
||||||
|
|
||||||
if item.Status != arn.AnimeListStatusCompleted
|
if item.Status != arn.AnimeListStatusCompleted
|
||||||
|
@ -22,11 +22,17 @@ const (
|
|||||||
func Filter(ctx aero.Context) error {
|
func Filter(ctx aero.Context) error {
|
||||||
user := utils.GetUser(ctx)
|
user := utils.GetUser(ctx)
|
||||||
status := ctx.Get("status")
|
status := ctx.Get("status")
|
||||||
return AnimeList(ctx, user, status)
|
sortBy := arn.SortByRating
|
||||||
|
|
||||||
|
if user != nil {
|
||||||
|
sortBy = user.Settings().SortBy
|
||||||
|
}
|
||||||
|
|
||||||
|
return AnimeList(ctx, user, status, sortBy)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AnimeList renders the anime list items.
|
// AnimeList renders the anime list items.
|
||||||
func AnimeList(ctx aero.Context, user *arn.User, status string) error {
|
func AnimeList(ctx aero.Context, user *arn.User, status string, sortBy string) error {
|
||||||
nick := ctx.Get("nick")
|
nick := ctx.Get("nick")
|
||||||
index, _ := ctx.GetInt("index")
|
index, _ := ctx.GetInt("index")
|
||||||
viewUser, err := arn.GetUserByNick(nick)
|
viewUser, err := arn.GetUserByNick(nick)
|
||||||
@ -50,9 +56,9 @@ func AnimeList(ctx aero.Context, user *arn.User, status string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sort the items
|
// Sort the items
|
||||||
statusList.Sort()
|
statusList.Sort(sortBy)
|
||||||
|
|
||||||
// These are all animer list items for the given status
|
// These are all anime list items for the given status
|
||||||
allItems := statusList.Items
|
allItems := statusList.Items
|
||||||
|
|
||||||
// Slice the part that we need
|
// Slice the part that we need
|
||||||
|
@ -33,7 +33,7 @@ func Get(ctx aero.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
watchingList := animeList.Watching()
|
watchingList := animeList.Watching()
|
||||||
watchingList.Sort()
|
watchingList.Sort(user.Settings().SortBy)
|
||||||
|
|
||||||
return ctx.HTML(components.BrowserExtension(watchingList, animeList.User(), user))
|
return ctx.HTML(components.BrowserExtension(watchingList, animeList.User(), user))
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,11 @@ func Get(ctx aero.Context) error {
|
|||||||
// Profile renders the user profile page of the given viewUser.
|
// Profile renders the user profile page of the given viewUser.
|
||||||
func Profile(ctx aero.Context, viewUser *arn.User) error {
|
func Profile(ctx aero.Context, viewUser *arn.User) error {
|
||||||
user := utils.GetUser(ctx)
|
user := utils.GetUser(ctx)
|
||||||
|
sortBy := arn.SortByRating
|
||||||
|
|
||||||
|
if user != nil {
|
||||||
|
sortBy = user.Settings().SortBy
|
||||||
|
}
|
||||||
|
|
||||||
// Anime list
|
// Anime list
|
||||||
animeList := viewUser.AnimeList()
|
animeList := viewUser.AnimeList()
|
||||||
@ -43,7 +48,7 @@ func Profile(ctx aero.Context, viewUser *arn.User) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
completedList := animeList.FilterStatus(arn.AnimeListStatusCompleted)
|
completedList := animeList.FilterStatus(arn.AnimeListStatusCompleted)
|
||||||
completedList.SortByRating()
|
completedList.Sort(sortBy)
|
||||||
|
|
||||||
// Genres
|
// Genres
|
||||||
topGenres := animeList.TopGenres(5)
|
topGenres := animeList.TopGenres(5)
|
||||||
|
@ -7,7 +7,7 @@ component SettingsStyle(user *arn.User)
|
|||||||
.widget.mountable(data-api="/api/settings/" + user.ID)
|
.widget.mountable(data-api="/api/settings/" + user.ID)
|
||||||
h3.widget-title
|
h3.widget-title
|
||||||
Icon("font")
|
Icon("font")
|
||||||
span Style
|
span General
|
||||||
|
|
||||||
.widget-section
|
.widget-section
|
||||||
label(for="Theme")= "Theme:"
|
label(for="Theme")= "Theme:"
|
||||||
@ -25,6 +25,18 @@ component SettingsStyle(user *arn.User)
|
|||||||
|
|
||||||
InputNumber("Format.RatingsPrecision", float64(user.Settings().Format.RatingsPrecision), "Ratings precision", "How many decimals after the comma would you like to display in ratings on anime pages?", "0", "2", "1")
|
InputNumber("Format.RatingsPrecision", float64(user.Settings().Format.RatingsPrecision), "Ratings precision", "How many decimals after the comma would you like to display in ratings on anime pages?", "0", "2", "1")
|
||||||
|
|
||||||
|
.widget.mountable(data-api="/api/settings/" + user.ID)
|
||||||
|
h3.widget-title
|
||||||
|
Icon("list")
|
||||||
|
span List
|
||||||
|
|
||||||
|
.widget-section
|
||||||
|
label(for="SortBy")= "Sort by:"
|
||||||
|
select.widget-ui-element.action(id="SortBy", data-field="SortBy", value=user.Settings().SortBy, title="Sorting algorithm for anime lists", data-action="save", data-trigger="change")
|
||||||
|
option(value="airing date") Airing date ⮞ Rating ⮞ Title
|
||||||
|
option(value="rating") Rating ⮞ Title
|
||||||
|
option(value="title") Title
|
||||||
|
|
||||||
if arn.IsDevelopment()
|
if arn.IsDevelopment()
|
||||||
.widget.mountable(data-api="/api/settings/" + user.ID)
|
.widget.mountable(data-api="/api/settings/" + user.ID)
|
||||||
h3.widget-title
|
h3.widget-title
|
||||||
|
15
patches/user-sort-fix/user-sort-fix.go
Normal file
15
patches/user-sort-fix/user-sort-fix.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/animenotifier/notify.moe/arn"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
defer arn.Node.Close()
|
||||||
|
|
||||||
|
for user := range arn.StreamUsers() {
|
||||||
|
settings := user.Settings()
|
||||||
|
settings.SortBy = arn.SortByAiringDate
|
||||||
|
settings.Save()
|
||||||
|
}
|
||||||
|
}
|
@ -3,14 +3,6 @@ html
|
|||||||
font-family "Ubuntu", "Trebuchet MS", sans-serif
|
font-family "Ubuntu", "Trebuchet MS", sans-serif
|
||||||
font-size 90%
|
font-size 90%
|
||||||
|
|
||||||
// > 900px
|
|
||||||
// html
|
|
||||||
// font-size 90%
|
|
||||||
|
|
||||||
// > 1400px
|
|
||||||
// html
|
|
||||||
// font-size 95%
|
|
||||||
|
|
||||||
body
|
body
|
||||||
tab-size 4
|
tab-size 4
|
||||||
overflow hidden
|
overflow hidden
|
||||||
|
@ -3,10 +3,9 @@ mixin ui-element
|
|||||||
background ui-background
|
background ui-background
|
||||||
border-radius ui-element-border-radius
|
border-radius ui-element-border-radius
|
||||||
default-transition
|
default-transition
|
||||||
|
|
||||||
:hover
|
:hover
|
||||||
border-color ui-hover-border-color
|
border-color ui-hover-border-color
|
||||||
// background ui-hover-background
|
|
||||||
// box-shadow outline-shadow-medium
|
|
||||||
|
|
||||||
mixin ui-disabled
|
mixin ui-disabled
|
||||||
color button-color !important
|
color button-color !important
|
||||||
|
@ -7,6 +7,11 @@
|
|||||||
box-sizing inherit
|
box-sizing inherit
|
||||||
font inherit
|
font inherit
|
||||||
|
|
||||||
|
// This breaks accessibility, but is needed to ensure a consistent style.
|
||||||
|
// Make sure you add your own focus styles.
|
||||||
|
::-moz-focus-inner
|
||||||
|
border 0
|
||||||
|
|
||||||
// Set root element to use border-box sizing,
|
// Set root element to use border-box sizing,
|
||||||
// all sub-elements will inherit this property.
|
// all sub-elements will inherit this property.
|
||||||
html
|
html
|
||||||
|
Loading…
Reference in New Issue
Block a user