1
0
Fork 0
mirror of https://github.com/heyarne/airsonic-ui.git synced 2026-05-06 18:33:38 +02:00

Fix sketchy pagination (#31)

* Fix test watcher and notifications on linux

* Improve pagination; no items are repeated, items loaded ahead are kept and many calculations have been moved to subscriptions

Closes #28
This commit is contained in:
Arne Schlüter 2018-10-23 15:37:35 +02:00 committed by GitHub
commit 4b9f99ecc2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 3187 additions and 69 deletions

View file

@ -2,21 +2,40 @@
(:require [re-frame.core :as re-frame]
[airsonic-ui.config :as conf]))
(defn complete-library
"Concatenates all responses of one type of library to make paging through
it a bit easier."
;; first some helper functions to make the structure a bit clearer
(defn filter-response-kind
"Takes all library responses and returns only the ones matching a specific kind"
[kind responses]
(filter (fn [[[_ params] _]]
(= kind (:type params))) responses))
(defn partition-responses
"Returns a map of responses, where each response is neatly mapped to the page
it show on."
[kind responses]
(->> (filter-response-kind kind responses)
(sort-by (fn [[[_ params] _]] (:offset params)))
(mapcat (fn [[[_ params] {albums :album}]]
(let [start-page (/ (:offset params) conf/albums-per-page)]
(zipmap (drop start-page (range))
(partition-all conf/albums-per-page albums)))))
(into (sorted-map))))
;; `complete-library` is the subscription that is actually exported
(defn paginated-library
"Returns a sorted map that can be used to access the library content loaded
from the server. Each key represents a page and the associated value
represents the page's content."
[responses [_ kind]]
(let [sorted-albums (->> (filter (fn [[[_ params] _]]
(= kind (:type params))) responses)
(sort-by (fn [[[_ params] _]] (:offset params)))
(map (comp :album val)))]
;; NOTE: we concatenate this manually to avoid duplication; we have to do
;; this because fetch more than conf/albums-per-page per page, otherwise we
;; can't know whether to show a link to the next page
(concat (mapcat (partial take conf/albums-per-page) (butlast sorted-albums))
(last sorted-albums))))
;; note that we "humanize" the keys, meaning page 1 is the page with offset 0
(->> (partition-responses kind responses)
(map (fn [[k v]] [(inc k) v]))
(into (sorted-map))))
(re-frame/reg-sub
:library/complete
:library/paginated
:<- [:api/responses-for-endpoint "getAlbumList2"]
complete-library)
paginated-library)

View file

@ -12,45 +12,43 @@
^{:key idx} [:li (when (= params active-item)
{:class-name "is-active"})
[:a {:href (apply url-for route)} label]]))]])
;; the pagination should be used like this
;; [pagination {:per-page 12
;; :max-pages nil
;; :url-fn generate-url
;; :current-page 0
;; :items [,,,]}]
(defn num-pages [items per-page max-pages]
(min (Math/ceil (/ (count items) per-page)) max-pages))
;; this variable determines how many pages before the first known page we should list
(def page-padding 2)
(defn pagination
"Builds a pagination, calling `url-fn` for every rendered page link with the
page as its argument. When `max-pages` is `nil` an infinite pagination
will be rendered."
[{:keys [items per-page max-pages current-page url-fn]
:or {max-pages (.-MAX_VALUE js/Number)}}]
(let [num-pages (num-pages items per-page max-pages)
[{:keys [items current-page url-fn]}]
;; NOTE: This is currently slightly flawed. We don't have any good way to
;; know whether we're on the last possible page so we take the last loaded
;; page instead
(let [num-pages (last (keys items))
first-page? (= current-page 1)
last-page? (= current-page num-pages)]
[:nav.pagination {:role "pagination", :aria-label "pagination"}
pages (range (max 1 (- current-page page-padding))
(min (inc (+ current-page page-padding)) (inc num-pages))) ]
[:nav.pagination.is-centered {:role "pagination", :aria-label "pagination"}
;; now we add buttons to progress one page in each direction
[:a.pagination-previous (if first-page?
{:disabled true}
{:href (url-fn (dec current-page))}) "Previous page"]
[:a.pagination-next (if last-page?
{:disabled true}
{:href (url-fn (inc current-page))}) "Next page"]
[:a.pagination-next {:href (url-fn (inc current-page))} "Next page"]
;; and here we modify the links around our current page
[:ul.pagination-list
(when (> current-page 3)
^{:key "ellipsis-before"} [:li>span.pagination-ellipsis "…"])
(for [page (range (max 1 (- current-page 2))
(min (+ current-page 3) (inc num-pages)))]
;; some indication that there are previous pages
(when (> current-page (inc page-padding))
[:li>span.pagination-ellipsis "…"])
;; all pagination links around our current page
(for [page pages]
(let [current-page? (= page current-page)]
^{:key page} [(cond-> :li>a.pagination-link
current-page? (add-classes :is-current))
(cond-> {:href (url-fn page), :aria-label (str "Page " page)}
current-page? (assoc :aria-current "page")) page]))
(when (< current-page (- num-pages 2))
^{:key "ellipsis-after"} [:li>span.pagination-ellipsis "…"])]]))
;; some indication that there are more pages after
(when (< current-page (- num-pages page-padding))
[:li>span.pagination-ellipsis "…"])]]))
(def tab-items [[[::routes/library {:kind "recent"} nil] "Recently played"]
[[::routes/library {:kind "newest"} nil] "Newest additions"]
@ -63,13 +61,11 @@
[[_ {:keys [kind]} {:keys [page]
:or {page 1}}]
{:keys [scan-status]}]
(let [library @(subscribe [:library/complete kind])
;; FIXME: vv Views shouldn't do calculations vv
visible (->> (drop (* (dec (int page)) conf/albums-per-page) library)
(take conf/albums-per-page))
(let [page (int page)
library @(subscribe [:library/paginated kind])
current-items (get library page)
url-fn #(url-for ::routes/library {:kind kind} {:page %})
pagination [pagination {:current-page (int page)
:per-page conf/albums-per-page
pagination [pagination {:current-page page
:items library
:url-fn url-fn}]]
[:div
@ -82,5 +78,5 @@
[:section.section>div.container
[tabs {:items tab-items :active-item {:kind kind}}]
pagination
[:section.section [collection/listing visible]]
pagination]]))
[:section.section [collection/listing current-items]
pagination]]]))

View file

@ -5,3 +5,4 @@
;; how many covers are shown per page when browsing the library
(def albums-per-page 20)
(def albums-prefetch-factor 5)

View file

@ -49,7 +49,7 @@
;; we fetch more than just the albums needed for the current page so we can
;; page through it faster
[:api/request "getAlbumList2" {:type kind
:size (* 3 conf/albums-per-page)
:size (* conf/albums-prefetch-factor conf/albums-per-page)
:offset (* (dec (int page)) conf/albums-per-page)}]]
[:routes/do-navigation [route-id {:kind "recent"} {:page 1}]]))