diff --git a/pages/animelist/animelist.pixy b/pages/animelist/animelist.pixy index cf8eb30c..e58a792c 100644 --- a/pages/animelist/animelist.pixy +++ b/pages/animelist/animelist.pixy @@ -1,30 +1,35 @@ component AnimeLists(animeLists map[string]*arn.AnimeList, viewUser *arn.User, user *arn.User) - h2.anime-list-owner= viewUser.Nick + "'s collection" + ProfileHeader(viewUser, user) - if len(animeLists[arn.AnimeListStatusWatching].Items) > 0 - .anime-list-container - h3.status-name Watching - AnimeList(animeLists[arn.AnimeListStatusWatching], viewUser, user) + h2.page-title.anime-list-owner= viewUser.Nick + "'s collection" - if len(animeLists[arn.AnimeListStatusCompleted].Items) > 0 - .anime-list-container - h3.status-name Completed - AnimeList(animeLists[arn.AnimeListStatusCompleted], viewUser, user) + if len(animeLists[arn.AnimeListStatusWatching].Items) == 0 && len(animeLists[arn.AnimeListStatusCompleted].Items) == 0 && len(animeLists[arn.AnimeListStatusPlanned].Items) == 0 && len(animeLists[arn.AnimeListStatusHold].Items) == 0 && len(animeLists[arn.AnimeListStatusDropped].Items) == 0 + p.no-data No anime in the collection. + else + if len(animeLists[arn.AnimeListStatusWatching].Items) > 0 + .anime-list-container + h3.status-name Watching + AnimeList(animeLists[arn.AnimeListStatusWatching], viewUser, user) - if len(animeLists[arn.AnimeListStatusPlanned].Items) > 0 - .anime-list-container - h3.status-name Planned - AnimeList(animeLists[arn.AnimeListStatusPlanned], viewUser, user) + if len(animeLists[arn.AnimeListStatusCompleted].Items) > 0 + .anime-list-container + h3.status-name Completed + AnimeList(animeLists[arn.AnimeListStatusCompleted], viewUser, user) - if len(animeLists[arn.AnimeListStatusHold].Items) > 0 - .anime-list-container - h3.status-name On hold - AnimeList(animeLists[arn.AnimeListStatusHold], viewUser, user) + if len(animeLists[arn.AnimeListStatusPlanned].Items) > 0 + .anime-list-container + h3.status-name Planned + AnimeList(animeLists[arn.AnimeListStatusPlanned], viewUser, user) - if len(animeLists[arn.AnimeListStatusDropped].Items) > 0 - .anime-list-container - h3.status-name Dropped - AnimeList(animeLists[arn.AnimeListStatusDropped], viewUser, user) + if len(animeLists[arn.AnimeListStatusHold].Items) > 0 + .anime-list-container + h3.status-name On hold + AnimeList(animeLists[arn.AnimeListStatusHold], viewUser, user) + + if len(animeLists[arn.AnimeListStatusDropped].Items) > 0 + .anime-list-container + h3.status-name Dropped + AnimeList(animeLists[arn.AnimeListStatusDropped], viewUser, user) //- for status, animeList := range animeLists //- h3= status diff --git a/pages/forum/forum.pixy b/pages/forum/forum.pixy index a248d5ed..97fdff94 100644 --- a/pages/forum/forum.pixy +++ b/pages/forum/forum.pixy @@ -16,7 +16,7 @@ component Forum(tag string, threads []*arn.Thread, threadsPerPage int) component ThreadList(threads []*arn.Thread) if len(threads) == 0 - p No threads found. + p.no-data No threads found. else each thread in threads ThreadLink(thread) \ No newline at end of file diff --git a/pages/profile/posts.go b/pages/profile/posts.go index 9f91239a..d2668f45 100644 --- a/pages/profile/posts.go +++ b/pages/profile/posts.go @@ -6,6 +6,7 @@ import ( "github.com/aerogo/aero" "github.com/animenotifier/arn" "github.com/animenotifier/notify.moe/components" + "github.com/animenotifier/notify.moe/utils" ) const postLimit = 10 @@ -13,13 +14,13 @@ const postLimit = 10 // GetPostsByUser shows all forum posts of a particular user. func GetPostsByUser(ctx *aero.Context) string { nick := ctx.Get("nick") - user, err := arn.GetUserByNick(nick) + viewUser, err := arn.GetUserByNick(nick) if err != nil { return ctx.Error(http.StatusNotFound, "User not found", err) } - posts := user.Posts() + posts := viewUser.Posts() arn.SortPostsLatestFirst(posts) var postables []arn.Postable @@ -34,6 +35,6 @@ func GetPostsByUser(ctx *aero.Context) string { postables[i] = arn.ToPostable(post) } - return ctx.HTML(components.LatestPosts(postables, user)) + return ctx.HTML(components.LatestPosts(postables, viewUser, utils.GetUser(ctx))) } diff --git a/pages/profile/posts.pixy b/pages/profile/posts.pixy index 5d18a568..534addb8 100644 --- a/pages/profile/posts.pixy +++ b/pages/profile/posts.pixy @@ -1,6 +1,8 @@ -component LatestPosts(postables []arn.Postable, viewUser *arn.User) +component LatestPosts(postables []arn.Postable, viewUser *arn.User, user *arn.User) + ProfileHeader(viewUser, user) + if len(postables) > 0 - h2.thread-title= len(postables), " latest posts by ", postables[0].Author().Nick + h2.page-title= len(postables), " latest posts by ", postables[0].Author().Nick PostableList(postables) else p= viewUser.Nick + " hasn't written any posts yet." \ No newline at end of file diff --git a/pages/profile/profile.pixy b/pages/profile/profile.pixy index 05125822..457f533e 100644 --- a/pages/profile/profile.pixy +++ b/pages/profile/profile.pixy @@ -2,10 +2,10 @@ component ProfileHeader(viewUser *arn.User, user *arn.User) .profile img.profile-cover(src=viewUser.CoverImageURL(), alt="Cover image") - .image-container.mountable + .image-container.mountable.never-unmount ProfileImage(viewUser) - .intro-container.mountable + .intro-container.mountable.never-unmount h2#nick= viewUser.Nick if viewUser.Tagline != "" @@ -43,58 +43,78 @@ component ProfileHeader(viewUser *arn.User, user *arn.User) p.profile-field.role Icon("rocket") span= arn.Capitalize(viewUser.Role) + + ProfileNavigation(viewUser) + +component ProfileNavigation(viewUser *arn.User) + .buttons.tabs + a.button.tab.action(href="/+" + viewUser.Nick, data-action="diff", data-trigger="click") + Icon("th") + span.tab-text Anime + + a.button.tab.action(href="/+" + viewUser.Nick + "/animelist", data-action="diff", data-trigger="click") + Icon("list") + span.tab-text List + + a.button.tab.action(href="/+" + viewUser.Nick + "/threads", data-action="diff", data-trigger="click") + Icon("comment") + span.tab-text Threads + + a.button.tab.action(href="/+" + viewUser.Nick + "/posts", data-action="diff", data-trigger="click") + Icon("comments") + span.tab-text Posts + + a.button.tab.action(href="/+" + viewUser.Nick + "/tracks", data-action="diff", data-trigger="click") + Icon("music") + span.tab-text Tracks component Profile(viewUser *arn.User, user *arn.User, animeList *arn.AnimeList, threads []*arn.Thread, posts []*arn.Post, tracks []*arn.SoundTrack) ProfileHeader(viewUser, user) - .profile-category.mountable - h3 - a.ajax(href="/+" + viewUser.Nick + "/animelist", title="View all anime") Anime - - .profile-watching-list - if len(animeList.Items) == 0 - p No anime in the collection. - else - each item in animeList.Items - a.profile-watching-list-item.ajax(href=item.Anime().Link(), title=item.Anime().Title.Canonical + " (" + toString(item.Episodes) + " / " + arn.EpisodesToString(item.Anime().EpisodeCount) + ")") - img.anime-cover-image.profile-watching-list-item-image.lazy(data-src=item.Anime().Image.Tiny, alt=item.Anime().Title.Canonical) - - .profile-category.mountable - h3 - a.ajax(href="/+" + viewUser.Nick + "/threads", title="View all threads") Threads - - if len(threads) == 0 - p No threads on the forum. + .profile-watching-list.mountable + if len(animeList.Items) == 0 + p.no-data No anime in the collection. else - each thread in threads - ThreadLink(thread) + each item in animeList.Items + a.profile-watching-list-item.ajax(href=item.Anime().Link(), title=item.Anime().Title.Canonical + " (" + toString(item.Episodes) + " / " + arn.EpisodesToString(item.Anime().EpisodeCount) + ")") + img.anime-cover-image.profile-watching-list-item-image.lazy(data-src=item.Anime().Image.Tiny, alt=item.Anime().Title.Canonical) - .profile-category.mountable - h3 - a.ajax(href="/+" + viewUser.Nick + "/posts", title="View all posts") Posts - if len(posts) == 0 - p No posts on the forum. - else - each post in posts - .post - .post-author - Avatar(post.Author()) - .post-content - div!= post.HTML() - .post-toolbar.active - .spacer - .post-likes= len(post.Likes) - - .profile-category.mountable - h3 - a.ajax(href="/+" + viewUser.Nick + "/tracks", title="View all tracks") Tracks + //- .profile-category.mountable + //- h3 + //- a.ajax(href="/+" + viewUser.Nick + "/threads", title="View all threads") Threads - if len(tracks) == 0 - p No soundtracks posted yet. - else - .sound-tracks - each track in tracks - SoundTrack(track) + //- if len(threads) == 0 + //- p No threads on the forum. + //- else + //- each thread in threads + //- ThreadLink(thread) + + //- .profile-category.mountable + //- h3 + //- a.ajax(href="/+" + viewUser.Nick + "/posts", title="View all posts") Posts + //- if len(posts) == 0 + //- p No posts on the forum. + //- else + //- each post in posts + //- .post + //- .post-author + //- Avatar(post.Author()) + //- .post-content + //- div!= post.HTML() + //- .post-toolbar.active + //- .spacer + //- .post-likes= len(post.Likes) + + //- .profile-category.mountable + //- h3 + //- a.ajax(href="/+" + viewUser.Nick + "/tracks", title="View all tracks") Tracks + + //- if len(tracks) == 0 + //- p No soundtracks posted yet. + //- else + //- .sound-tracks + //- each track in tracks + //- SoundTrack(track) //- if user != nil && user.Role == "admin" //- .footer diff --git a/pages/profile/profile.scarlet b/pages/profile/profile.scarlet index 0ec8b188..e0d74bcb 100644 --- a/pages/profile/profile.scarlet +++ b/pages/profile/profile.scarlet @@ -88,5 +88,5 @@ profile-boot-duration = 2s // Categories -.profile-category - margin-bottom content-padding \ No newline at end of file +// .profile-category +// margin-bottom content-padding \ No newline at end of file diff --git a/pages/profile/threads.go b/pages/profile/threads.go index 5a0a8e9e..5f267439 100644 --- a/pages/profile/threads.go +++ b/pages/profile/threads.go @@ -6,19 +6,20 @@ import ( "github.com/aerogo/aero" "github.com/animenotifier/arn" "github.com/animenotifier/notify.moe/components" + "github.com/animenotifier/notify.moe/utils" ) // GetThreadsByUser shows all forum threads of a particular user. func GetThreadsByUser(ctx *aero.Context) string { nick := ctx.Get("nick") - user, err := arn.GetUserByNick(nick) + viewUser, err := arn.GetUserByNick(nick) if err != nil { return ctx.Error(http.StatusNotFound, "User not found", err) } - threads := user.Threads() + threads := viewUser.Threads() arn.SortThreadsLatestFirst(threads) - return ctx.HTML(components.ThreadList(threads)) + return ctx.HTML(components.ProfileThreads(threads, viewUser, utils.GetUser(ctx))) } diff --git a/pages/profile/threads.pixy b/pages/profile/threads.pixy new file mode 100644 index 00000000..993a7216 --- /dev/null +++ b/pages/profile/threads.pixy @@ -0,0 +1,5 @@ +component ProfileThreads(threads []*arn.Thread, viewUser *arn.User, user *arn.User) + ProfileHeader(viewUser, user) + + .forum + ThreadList(threads) diff --git a/pages/profile/tracks.pixy b/pages/profile/tracks.pixy index f554fca8..13981fdf 100644 --- a/pages/profile/tracks.pixy +++ b/pages/profile/tracks.pixy @@ -1,6 +1,12 @@ component TrackList(tracks []*arn.SoundTrack, viewUser *arn.User, user *arn.User) - h2= "Tracks added by " + viewUser.Nick - .sound-tracks - each track in tracks - SoundTrack(track) + ProfileHeader(viewUser, user) + + h2.page-title= "Tracks added by " + viewUser.Nick + + if len(tracks) == 0 + p.no-data No tracks added. + else + .sound-tracks + each track in tracks + SoundTrack(track) \ No newline at end of file diff --git a/pages/profile/watching.scarlet b/pages/profile/watching.scarlet index 6b3b9b13..c77377f6 100644 --- a/pages/profile/watching.scarlet +++ b/pages/profile/watching.scarlet @@ -1,5 +1,6 @@ .profile-watching-list horizontal-wrap + justify-content center .profile-watching-list-item margin 0.25rem @@ -8,6 +9,6 @@ width 55px !important border-radius 2px -< 380px - .profile-watching-list - justify-content center \ No newline at end of file +// < 380px +// .profile-watching-list +// justify-content center \ No newline at end of file diff --git a/pages/search/search.pixy b/pages/search/search.pixy index 8439d587..a7e8b16c 100644 --- a/pages/search/search.pixy +++ b/pages/search/search.pixy @@ -4,7 +4,7 @@ component SearchResults(users []*arn.User, animeResults []*arn.Anime) h3 Users .user-avatars.user-search if len(users) == 0 - p No users found. + p.no-data No users found. else each user in users .mountable(data-mountable-type="user") @@ -15,7 +15,7 @@ component SearchResults(users []*arn.User, animeResults []*arn.Anime) h3 Anime .profile-watching-list.anime-search if len(animeResults) == 0 - p No anime found. + p.no-data No anime found. else each anime in animeResults a.profile-watching-list-item.mountable.ajax(href=anime.Link(), title=anime.Title.Canonical, data-mountable-type="anime") diff --git a/scripts/Actions.ts b/scripts/Actions.ts index 69673e42..f56e96e3 100644 --- a/scripts/Actions.ts +++ b/scripts/Actions.ts @@ -76,7 +76,41 @@ export function load(arn: AnimeNotifier, element: HTMLElement) { // Diff export function diff(arn: AnimeNotifier, element: HTMLElement) { let url = element.dataset.url || (element as HTMLAnchorElement).getAttribute("href") - arn.diff(url) + + arn.diff(url).then(() => { + arn.requestIdleCallback(() => { + const duration = 300.0 + const steps = 60 + const interval = duration / steps + const fullSin = Math.PI / 2 + const contentPadding = 25 + + let target = element + let scrollHandle: number + let oldScroll = arn.app.content.parentElement.scrollTop + let newScroll = Math.max(target.offsetTop - contentPadding, 0) + let scrollDistance = newScroll - oldScroll + let timeStart = performance.now() + let timeEnd = timeStart + duration + + let scroll = () => { + let time = performance.now() + let progress = (time - timeStart) / duration + + if(progress > 1.0) { + progress = 1.0 + } + + arn.app.content.parentElement.scrollTop = oldScroll + scrollDistance * Math.sin(progress * fullSin) + + if(time >= timeEnd || arn.app.content.parentElement.scrollTop == newScroll) { + clearInterval(scrollHandle) + } + } + + scrollHandle = setInterval(scroll, interval) + }) + }) } // Forum reply diff --git a/scripts/AnimeNotifier.ts b/scripts/AnimeNotifier.ts index ed691ee5..955ea59f 100644 --- a/scripts/AnimeNotifier.ts +++ b/scripts/AnimeNotifier.ts @@ -44,10 +44,14 @@ export class AnimeNotifier { document.addEventListener("keydown", this.onKeyDown.bind(this), false) window.addEventListener("popstate", this.onPopState.bind(this)) + this.requestIdleCallback(this.onIdle.bind(this)) + } + + requestIdleCallback(func: Function) { if("requestIdleCallback" in window) { - window["requestIdleCallback"](this.onIdle.bind(this)) + window["requestIdleCallback"](func) } else { - this.onIdle() + func() } } @@ -194,6 +198,10 @@ export class AnimeNotifier { unmountMountables() { for(let element of findAll("mountable")) { + if(element.classList.contains("never-unmount")) { + continue + } + element.classList.remove("mounted") } } @@ -228,6 +236,10 @@ export class AnimeNotifier { } diff(url: string) { + if(url == this.app.currentPath) { + return + } + let request = fetch("/_" + url, { credentials: "same-origin" }) diff --git a/styles/tabs.scarlet b/styles/tabs.scarlet index ed305f6b..7881b8bf 100644 --- a/styles/tabs.scarlet +++ b/styles/tabs.scarlet @@ -23,4 +23,5 @@ .tabs // justify-content flex-start !important - margin-bottom 1rem \ No newline at end of file + margin-bottom 1rem + margin-top -0.6rem \ No newline at end of file