From 555582836c424578f3e72934aa6075c3e013c3a1 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 16 Oct 2017 12:56:46 +0200 Subject: [PATCH] Added infinite scrolling --- mixins/LoadMore.pixy | 4 +-- pages/forum/forum.pixy | 4 +-- pages/soundtracks/soundtracks.go | 24 ++++++++++++----- pages/soundtracks/soundtracks.pixy | 7 ++--- scripts/Actions.ts | 42 +++++++++++++++++++++++++++--- scripts/AnimeNotifier.ts | 5 ++++ scripts/InfiniteScroller.ts | 25 ++++++++++++++++++ 7 files changed, 93 insertions(+), 18 deletions(-) create mode 100644 scripts/InfiniteScroller.ts diff --git a/mixins/LoadMore.pixy b/mixins/LoadMore.pixy index 928ba6ee..94337ed8 100644 --- a/mixins/LoadMore.pixy +++ b/mixins/LoadMore.pixy @@ -1,4 +1,4 @@ -component LoadMore - button.action(data-action="loadMore", data-trigger="click") +component LoadMore(index int) + button#load-more-button.action(data-action="loadMore", data-trigger="click", data-index=index) Icon("refresh") span Load more \ No newline at end of file diff --git a/pages/forum/forum.pixy b/pages/forum/forum.pixy index bf0ddc2c..093fb157 100644 --- a/pages/forum/forum.pixy +++ b/pages/forum/forum.pixy @@ -8,8 +8,8 @@ component Forum(tag string, threads []*arn.Thread, threadsPerPage int) button#new-thread.action(data-action="load", data-trigger="click", data-url="/new/thread") Icon("plus") span New thread - if len(threads) == threadsPerPage - LoadMore + //- if len(threads) == threadsPerPage + //- LoadMore component ThreadList(threads []*arn.Thread) if len(threads) == 0 diff --git a/pages/soundtracks/soundtracks.go b/pages/soundtracks/soundtracks.go index 0ada1abd..ce2052ec 100644 --- a/pages/soundtracks/soundtracks.go +++ b/pages/soundtracks/soundtracks.go @@ -10,7 +10,7 @@ import ( "github.com/animenotifier/notify.moe/utils" ) -const maxTracks = 9 +const maxTracks = 12 // Get renders the soundtracks page. func Get(ctx *aero.Context) string { @@ -30,7 +30,7 @@ func Get(ctx *aero.Context) string { tracks = tracks[:maxTracks] } - return ctx.HTML(components.SoundTracks(tracks, user)) + return ctx.HTML(components.SoundTracks(tracks, maxTracks, user)) } // From renders the soundtracks from the given index. @@ -42,7 +42,7 @@ func From(ctx *aero.Context) string { return ctx.Error(http.StatusBadRequest, "Invalid start index", err) } - tracks, err := arn.FilterSoundTracks(func(track *arn.SoundTrack) bool { + allTracks, err := arn.FilterSoundTracks(func(track *arn.SoundTrack) bool { return !track.IsDraft && len(track.Media) > 0 }) @@ -50,17 +50,27 @@ func From(ctx *aero.Context) string { return ctx.Error(http.StatusInternalServerError, "Error fetching soundtracks", err) } - if index < 0 || index >= len(tracks) { - return ctx.Error(http.StatusBadRequest, "Invalid start index (maximum is "+strconv.Itoa(len(tracks))+")", nil) + if index < 0 || index >= len(allTracks) { + return ctx.Error(http.StatusBadRequest, "Invalid start index (maximum is "+strconv.Itoa(len(allTracks))+")", nil) } - arn.SortSoundTracksLatestFirst(tracks) + arn.SortSoundTracksLatestFirst(allTracks) - tracks = tracks[index:] + tracks := allTracks[index:] if len(tracks) > maxTracks { tracks = tracks[:maxTracks] } + nextIndex := index + maxTracks + + if nextIndex >= len(allTracks) { + // End of data - no more scrolling + ctx.Response().Header().Set("X-LoadMore-Index", "-1") + } else { + // Send the index for the next request + ctx.Response().Header().Set("X-LoadMore-Index", strconv.Itoa(nextIndex)) + } + return ctx.HTML(components.SoundTracksScrollable(tracks, user)) } diff --git a/pages/soundtracks/soundtracks.pixy b/pages/soundtracks/soundtracks.pixy index 0ccf177a..e05647d1 100644 --- a/pages/soundtracks/soundtracks.pixy +++ b/pages/soundtracks/soundtracks.pixy @@ -1,4 +1,4 @@ -component SoundTracks(tracks []*arn.SoundTrack, user *arn.User) +component SoundTracks(tracks []*arn.SoundTrack, tracksPerPage int, user *arn.User) h1 Soundtracks .music-buttons @@ -15,8 +15,9 @@ component SoundTracks(tracks []*arn.SoundTrack, user *arn.User) #load-more-target.sound-tracks SoundTracksScrollable(tracks, user) - //- .buttons - //- LoadMore + if len(tracks) == tracksPerPage + .buttons + LoadMore(tracksPerPage) component SoundTracksScrollable(tracks []*arn.SoundTrack, user *arn.User) each track in tracks diff --git a/scripts/Actions.ts b/scripts/Actions.ts index 342f1640..2c70ac74 100644 --- a/scripts/Actions.ts +++ b/scripts/Actions.ts @@ -370,17 +370,51 @@ export function arrayRemove(arn: AnimeNotifier, element: HTMLElement) { } // Load more -export function loadMore(arn: AnimeNotifier, element: HTMLElement) { +export function loadMore(arn: AnimeNotifier, button: HTMLButtonElement) { + // Prevent firing this event multiple times + if(arn.isLoading || button.disabled) { + return + } + + arn.loading(true) + button.disabled = true + let target = arn.app.find("load-more-target") - let index = "9" + let index = button.dataset.index fetch("/_" + arn.app.currentPath + "/from/" + index) + .then(response => { + let newIndex = response.headers.get("X-LoadMore-Index") + + // End of data? + if(newIndex === "-1") { + button.classList.add("hidden") + } else { + button.dataset.index = newIndex + } + + return response + }) .then(response => response.text()) .then(body => { - target.innerHTML += body - arn.app.emit("DOMContentLoaded") + let tmp = document.createElement(target.tagName) + tmp.innerHTML = body + + let children = [...tmp.childNodes] + + window.requestAnimationFrame(() => { + for(let child of children) { + target.appendChild(child) + } + + arn.app.emit("DOMContentLoaded") + }) }) .catch(err => arn.statusMessage.showError(err)) + .then(() => { + arn.loading(false) + button.disabled = false + }) } // Chrome extension installation diff --git a/scripts/AnimeNotifier.ts b/scripts/AnimeNotifier.ts index 088c6303..23605e29 100644 --- a/scripts/AnimeNotifier.ts +++ b/scripts/AnimeNotifier.ts @@ -9,6 +9,7 @@ import { PushManager } from "./PushManager" import { TouchController } from "./TouchController" import { Analytics } from "./Analytics" import { SideBar } from "./SideBar" +import { InfiniteScroller } from "./InfiniteScroller" export class AnimeNotifier { app: Application @@ -22,6 +23,7 @@ export class AnimeNotifier { pushManager: PushManager touchController: TouchController sideBar: SideBar + infiniteScroller: InfiniteScroller mainPageLoaded: boolean isLoading: boolean lastReloadContentPath: string @@ -125,6 +127,9 @@ export class AnimeNotifier { // Sidebar control this.sideBar = new SideBar(this.app.find("sidebar")) + + // Infinite scrolling + this.infiniteScroller = new InfiniteScroller(this.app.content.parentElement, 100) // Loading this.loading(false) diff --git a/scripts/InfiniteScroller.ts b/scripts/InfiniteScroller.ts new file mode 100644 index 00000000..c2e387c5 --- /dev/null +++ b/scripts/InfiniteScroller.ts @@ -0,0 +1,25 @@ +export class InfiniteScroller { + container: HTMLElement + threshold: number + + constructor(container, threshold) { + this.container = container + this.threshold = threshold + + this.container.addEventListener("scroll", e => { + if(this.container.scrollTop + this.container.clientHeight >= this.container.scrollHeight - threshold) { + this.loadMore() + } + }) + } + + loadMore() { + let button = document.getElementById("load-more-button") + + if(!button) { + return + } + + button.click() + } +} \ No newline at end of file