1
0
Fork 0
mirror of https://github.com/heyarne/airsonic-ui.git synced 2026-05-07 10:43:39 +02:00

Add loading indicator to breadcrumbs

Also moves the current route data into a level 3 subscription (finally).
This commit is contained in:
Arne Schlüter 2018-10-17 12:27:27 +02:00
commit 15860d0f03
5 changed files with 83 additions and 35 deletions

View file

@ -3,12 +3,22 @@
[re-frame.core :refer [reg-sub]] [re-frame.core :refer [reg-sub]]
[airsonic-ui.helpers :refer [kebabify]])) [airsonic-ui.helpers :refer [kebabify]]))
(defn responses
"Returns the response cache"
[db _]
(:api/responses db))
(reg-sub :api/responses responses)
(defn response-for (defn response-for
"Returns the cached response for a single endpoint" "Returns the cached response for a single endpoint"
[db [_ endpoint params]] [responses [_ endpoint params]]
(get-in db [:api/responses [endpoint params]])) (get responses [endpoint params]))
(reg-sub :api/response-for response-for) (reg-sub
:api/response-for
:<- [:api/responses]
response-for)
(defn endpoint->kw (defn endpoint->kw
"Given an endpoint like `getAlbumList2`, returns a cleaned keyword like "Given an endpoint like `getAlbumList2`, returns a cleaned keyword like
@ -21,12 +31,29 @@
(str/replace #"\d+$" "") (str/replace #"\d+$" "")
(kebabify))) (kebabify)))
(defn route-data (defn current-route-data
"Given a list of event vectors, returns that responses for all API requests." "Returns all responses for the current route"
[db [_ route-events]] [[responses current-route-events] _]
(->> (filter #(= :api/request (first %)) route-events) (->> (filter #(= :api/request (first %)) current-route-events)
(mapcat (fn [[_ endpoint params]] (mapcat (fn [[_ endpoint params]]
[(endpoint->kw endpoint) (get-in db [:api/responses [endpoint params]])])) [(endpoint->kw endpoint) (get responses [endpoint params])]))
(apply hash-map))) (apply hash-map)))
(reg-sub :api/route-data route-data) (reg-sub
:api/current-route-data
:<- [:api/responses]
:<- [:routes/events-for-current-route]
current-route-data)
(defn content-pending?
"Tells us if any of the requests fired for the current route are
awaiting responses."
[current-route-data _]
(->> (vals current-route-data)
(map :api/is-loading?)
(some true?)))
(reg-sub
:api/content-pending?
:<- [:api/current-route-data]
content-pending?)

View file

@ -104,9 +104,7 @@
"Provides the complete UI to browse the media library, interact with search "Provides the complete UI to browse the media library, interact with search
results etc" results etc"
[[route-id :as route]] [[route-id :as route]]
(let [;; TODO: Move this to a layer 3 subscription ↓ (let [content @(subscribe [:api/current-route-data])]
route-events @(subscribe [:routes/events-for-current-route])
content @(subscribe [:api/route-data route-events])]
[:div [:div
[:section.section [:section.section
[breadcrumbs route content] [breadcrumbs route content]

View file

@ -1,16 +1,21 @@
(ns airsonic-ui.views.breadcrumbs (ns airsonic-ui.views.breadcrumbs
(:require [airsonic-ui.routes :as routes :refer [url-for]])) (:require [re-frame.core :refer [subscribe]]
[airsonic-ui.routes :as routes :refer [url-for]]
[airsonic-ui.views.loading-spinner :refer [loading-spinner]]))
;; Breadcrumbs are implemented in such a way that they provide a stringent ;; Breadcrumbs are implemented in such a way that they provide a stringent
;; hierarchy no matter how you came to the url. They should allow easy ;; hierarchy no matter how you came to the url. They should allow easy
;; navigation upwards that hierarchy (e.g. album -> artist) ;; navigation upwards that hierarchy (e.g. album -> artist)
(defn- bulma-breadcrumbs [& items] (defn- bulma-breadcrumbs [& items]
[:div.container>nav.breadcrumb {:aria-label "breadcrumbs"} (let [content-pending? @(subscribe [:api/content-pending?])]
[:ul [:div.container
(for [[idx [href label]] (map-indexed vector (butlast items))] [:nav.breadcrumb {:aria-label "breadcrumbs"}
[:li {:key idx} [:a {:href href} label]]) [:ul
[:li.is-active>a (last items)]]]) (for [[idx [href label]] (map-indexed vector (butlast items))]
[:li {:key idx} [:a {:href href} label]])
[:li.is-active>a (last items)
(when content-pending? [loading-spinner])]]]]))
(defmulti breadcrumbs (defmulti breadcrumbs
(fn dispatch-on [[route-id] content] route-id)) (fn dispatch-on [[route-id] content] route-id))

View file

@ -169,11 +169,11 @@
@keyframes you-spin-my-head-right-round @keyframes you-spin-my-head-right-round
from from
transform: rotate(0deg) transform: rotate(0deg)
transform-origin: 49% 50% transform-origin: 50% 46%
to to
transform: rotate(359deg) transform: rotate(359deg)
transform-origin: 49% 50% transform-origin: 50% 46%
.loading-spinner .loading-spinner
.icon .icon

View file

@ -6,9 +6,9 @@
(deftest single-response (deftest single-response
(testing "Should return the response for a specified endpoint" (testing "Should return the response for a specified endpoint"
(let [db {:api/responses {["search2" {:query "query term"}] :result}}] (let [responses (sub/responses {:api/responses {["search2" {:query "query term"}] :result}} [:api/responses])]
(is (= :result (sub/response-for db [:api/response-for "search2" {:query "query term"}]))) (is (= :result (sub/response-for responses [:api/response-for "search2" {:query "query term"}])))
(is (nil? (sub/response-for db [:api/response-for "search2" {:query "another query term"}])))))) (is (nil? (sub/response-for responses [:api/response-for "search2" {:query "another query term"}]))))))
(deftest endpoint-keywordification (deftest endpoint-keywordification
(testing "Should strip prefixes" (testing "Should strip prefixes"
@ -18,18 +18,36 @@
(is (= :album-list (sub/endpoint->kw "getAlbumList2"))) (is (= :album-list (sub/endpoint->kw "getAlbumList2")))
(is (= :search (sub/endpoint->kw "search3"))))) (is (= :search (sub/endpoint->kw "search3")))))
(def responses {["getAlbumList2" {:type "recent" :size 18}]
{:album [{:genre "foo", :artistId "12345"}
{:genre "electronic", :artistId "9999"}]}
["getArtistInfo" {:id "128"}]
{:biography "Interesting bio"
:largeImageUrl "https://lastfm-img2.akamaized.net/i/u/300x300/fb416b59cd694587aca0b2dec8f41198.png"}})
(deftest responses-for-route (deftest responses-for-route
(testing "Should return all cached responses for a route" (testing "Should return all cached responses for a route"
(let [route-events [[:api/request "getAlbumList2" {:type "recent", :size 18}] (let [current-route-events [[:api/request "getAlbumList2" {:type "recent", :size 18}]
[:event/should-be-ignored] [:event/should-be-ignored]
[:api/request "getArtistInfo" {:id "128"}]] [:api/request "getArtistInfo" {:id "128"}]]]
db {:api/responses {["getAlbumList2" {:type "recent" :size 18}] (is (= {:album-list (get responses ["getAlbumList2" {:type "recent" :size 18}])
{:album [{:genre "foo", :artistId "12345"} :artist-info (get responses ["getArtistInfo" {:id "128"}])}
{:genre "electronic", :artistId "9999"}]} (sub/current-route-data [responses current-route-events]
[:api/current-route-data]))))))
["getArtistInfo" {:id "128"}] (deftest content-pending
{:biography "Interesting bio" (testing "Should indicate if there are outstanding requests for the current route"
:largeImageUrl "https://lastfm-img2.akamaized.net/i/u/300x300/fb416b59cd694587aca0b2dec8f41198.png"}}}] (let [current-route-events [[:api/request "getAlbumList2" {:type "recent", :size 18}]
(is (= {:album-list (get-in db [:api/responses ["getAlbumList2" {:type "recent" :size 18}]]) [:event/should-be-ignored]
:artist-info (get-in db [:api/responses ["getArtistInfo" {:id "128"}]])} [:api/request "getArtistInfo" {:id "128"}]]
(sub/route-data db [:api/route-data route-events])))))) done responses
in-progress (assoc-in responses
[["getAlbumList2" {:type "recent" :size 18}] :api/is-loading?]
true)]
(is (true? (-> (sub/current-route-data [in-progress current-route-events]
[:api/current-route-data])
(sub/content-pending? [:api/content-pending?]))))
(is (not (true? (-> (sub/current-route-data [done current-route-events]
[:api/current-route-data])
(sub/content-pending? [:api/content-pending?]))))))))