mirror of
https://github.com/heyarne/airsonic-ui.git
synced 2026-05-06 18:33:38 +02:00
Add loading indicator to breadcrumbs (#29)
Also moves the current route data into a level 3 subscription (finally).
This commit is contained in:
parent
10f2e32ecd
commit
ec4504e475
5 changed files with 83 additions and 35 deletions
|
|
@ -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?)
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
|
||||||
|
|
@ -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?])]
|
||||||
|
[:div.container
|
||||||
|
[:nav.breadcrumb {:aria-label "breadcrumbs"}
|
||||||
[:ul
|
[:ul
|
||||||
(for [[idx [href label]] (map-indexed vector (butlast items))]
|
(for [[idx [href label]] (map-indexed vector (butlast items))]
|
||||||
[:li {:key idx} [:a {:href href} label]])
|
[:li {:key idx} [:a {:href href} label]])
|
||||||
[:li.is-active>a (last items)]]])
|
[: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))
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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")))))
|
||||||
|
|
||||||
(deftest responses-for-route
|
(def responses {["getAlbumList2" {:type "recent" :size 18}]
|
||||||
(testing "Should return all cached responses for a route"
|
|
||||||
(let [route-events [[:api/request "getAlbumList2" {:type "recent", :size 18}]
|
|
||||||
[:event/should-be-ignored]
|
|
||||||
[:api/request "getArtistInfo" {:id "128"}]]
|
|
||||||
db {:api/responses {["getAlbumList2" {:type "recent" :size 18}]
|
|
||||||
{:album [{:genre "foo", :artistId "12345"}
|
{:album [{:genre "foo", :artistId "12345"}
|
||||||
{:genre "electronic", :artistId "9999"}]}
|
{:genre "electronic", :artistId "9999"}]}
|
||||||
|
|
||||||
["getArtistInfo" {:id "128"}]
|
["getArtistInfo" {:id "128"}]
|
||||||
{:biography "Interesting bio"
|
{:biography "Interesting bio"
|
||||||
:largeImageUrl "https://lastfm-img2.akamaized.net/i/u/300x300/fb416b59cd694587aca0b2dec8f41198.png"}}}]
|
:largeImageUrl "https://lastfm-img2.akamaized.net/i/u/300x300/fb416b59cd694587aca0b2dec8f41198.png"}})
|
||||||
(is (= {:album-list (get-in db [:api/responses ["getAlbumList2" {:type "recent" :size 18}]])
|
|
||||||
:artist-info (get-in db [:api/responses ["getArtistInfo" {:id "128"}]])}
|
(deftest responses-for-route
|
||||||
(sub/route-data db [:api/route-data route-events]))))))
|
(testing "Should return all cached responses for a route"
|
||||||
|
(let [current-route-events [[:api/request "getAlbumList2" {:type "recent", :size 18}]
|
||||||
|
[:event/should-be-ignored]
|
||||||
|
[:api/request "getArtistInfo" {:id "128"}]]]
|
||||||
|
(is (= {:album-list (get responses ["getAlbumList2" {:type "recent" :size 18}])
|
||||||
|
:artist-info (get responses ["getArtistInfo" {:id "128"}])}
|
||||||
|
(sub/current-route-data [responses current-route-events]
|
||||||
|
[:api/current-route-data]))))))
|
||||||
|
|
||||||
|
(deftest content-pending
|
||||||
|
(testing "Should indicate if there are outstanding requests for the current route"
|
||||||
|
(let [current-route-events [[:api/request "getAlbumList2" {:type "recent", :size 18}]
|
||||||
|
[:event/should-be-ignored]
|
||||||
|
[:api/request "getArtistInfo" {:id "128"}]]
|
||||||
|
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?]))))))))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue