diff --git a/src/cljs/airsonic_ui/audio/core.cljs b/src/cljs/airsonic_ui/audio/core.cljs index 5a95653..d9f2139 100644 --- a/src/cljs/airsonic_ui/audio/core.cljs +++ b/src/cljs/airsonic_ui/audio/core.cljs @@ -109,15 +109,6 @@ :<- [:audio/summary] current-playlist) -(defn current-queue - [playlist _] - (vals (:items playlist))) - -(rf/reg-sub - :audio/current-queue - :<- [:audio/current-playlist] - current-queue) - (defn current-song "Gives us information about the currently played song as presented by the airsonic api" diff --git a/src/cljs/airsonic_ui/components/collection/views.cljs b/src/cljs/airsonic_ui/components/collection/views.cljs index dc33d85..e451621 100644 --- a/src/cljs/airsonic_ui/components/collection/views.cljs +++ b/src/cljs/airsonic_ui/components/collection/views.cljs @@ -1,30 +1,36 @@ (ns airsonic-ui.components.collection.views "A collection is a list of audio files that belong together (e.g. an album or a podcast's overview)" - (:require [airsonic-ui.helpers :refer [format-duration]] - [airsonic-ui.routes :as routes :refer [url-for]] - [airsonic-ui.views.cover :refer [cover card]] - [bulma.icon :refer [icon]])) + (:require [re-frame.core :refer [subscribe]] + [bulma.icon :refer [icon]] + [bulma.dropdown.views :refer [dropdown]] + [airsonic-ui.helpers :as h] + [airsonic-ui.routes :as routes] + [airsonic-ui.views.cover :refer [cover card]])) (defn collection-info [{:keys [songCount duration year]}] (vec (cond-> [:ul.is-smaller.collection-info [:li [icon :audio-spectrum] (str songCount (if (= 1 songCount) " track" " tracks"))] - [:li [icon :clock] (format-duration duration)]] + [:li [icon :clock] (h/format-duration duration)]] year (conj [:li [icon :calendar] (str "Released in " year)])))) -(defn album-card [album] - (let [{:keys [artist artistId name id]} album] - [card album - :url-fn #(url-for ::routes/album.detail {:id id}) - :content [:div - ;; link to album - [:div.title.is-5 - [:a {:href (url-for ::routes/album.detail {:id id}) - :title name} name]] - ;; link to artist page - [:div.subtitle.is-6 [:a {:href (url-for ::routes/artist.detail {:id artistId}) - :title artist} artist]]]])) +;; TODO: Maybe this view belongs somewhere else? +;; Something like a collection-grid component? + +(defn album-card + "A single element in a grid of albums. Shows the cover, artist and album name." + [{:keys [artist artistId name id] :as album}] + [card album + :url-fn #(routes/url-for ::routes/album.detail {:id id}) + :content [:div + ;; link to album + [:div.title.is-5 + [:a {:href (routes/url-for ::routes/album.detail {:id id}) + :title name} name]] + ;; link to artist page + [:div.subtitle.is-6 [:a {:href (routes/url-for ::routes/artist.detail {:id artistId}) + :title artist} artist]]]]) (defn listing [albums] ;; always show 5 in a row @@ -33,8 +39,49 @@ ^{:key idx} [:div.column.is-one-fifth-desktop.is-one-quarter-tablet.is-half-mobile [album-card album]])]) +;; TODO: Avoid duplication +(defn artist-link [{id :artistId, artist :artist}] + (if id + [:a {:href (routes/url-for ::routes/artist.detail {:id id})} artist] + artist)) + +(defn song-link [{:keys [song album idx]}] + [:a + {:href "#" :on-click (h/muted-dispatch [:audio-player/play-all (:song album) idx] :sync? true)} + (:title song)]) + +(defn song-actions [{:keys [song album idx]}] + [dropdown {:items [{:label "Play next" :event [:audio-player/enqueue-next song]} + {:label "Play last" :event [:audio-player/enqueue-last song]}]}]) + +(defn song-table [{:keys [album]}] + ;; we subscribe here instead of one level higher up to make this a more + ;; reusable component; this way we can for example get a list of all songs + ;; in a search result and easily highlight the currently playing track + (let [current-song @(subscribe [:audio/current-song])] + [:table.song-listing-table.table.is-fullwidth + [:thead>tr + [:td.is-narrow] + [:td.song-artist "Artist"] + [:td.song-title "Title"] + [:td.song-duration "Duration"] + [:td.is-narrow]] + [:tbody + (for [[idx song] (map-indexed vector (:song album))] + ^{:key idx} + [(if (= (:id song) (:id current-song)) :tr.is-playing :tr) + [:td.song-tracknr.is-narrow (:track song)] + [:td.song-artist [artist-link song]] + [:td.song-title [song-link {:album album + :song song + :idx idx}]] + [:td.song-duration (h/format-duration (:duration song) :brief? true)] + [:td.song-actions.is-narrow [song-actions {:album album + :song song + :idx idx}]]])]])) + (defn detail - "Lists all songs in an album" + "Shows a detail view of a single album, listing all " [{:keys [album]}] [:div [:section.hero.is-small>div.hero-body @@ -45,4 +92,5 @@ [:h2.title (:name album)] [:h3.subtitle (:artist album)] [collection-info album]]]]] - [:section.section>div.container [song/listing (:song album)]]]) + [:section.section>div.container + [song-table {:album album}]]]) diff --git a/src/cljs/airsonic_ui/components/current_queue/views.cljs b/src/cljs/airsonic_ui/components/current_queue/views.cljs index a6bbdca..58f68b4 100644 --- a/src/cljs/airsonic_ui/components/current_queue/views.cljs +++ b/src/cljs/airsonic_ui/components/current_queue/views.cljs @@ -1,5 +1,5 @@ (ns airsonic-ui.components.current-queue.views - (:require [re-frame.core :refer [subscribe dispatch]] + (:require [re-frame.core :refer [subscribe dispatch-sync]] [reagent.core :as r] ["react-sortable-hoc" :refer [SortableHandle]] [bulma.icon :refer [icon]] @@ -7,6 +7,8 @@ [airsonic-ui.helpers :as helpers] [airsonic-ui.components.sortable.views :as sortable] [airsonic-ui.routes :as routes] + + ;; ↓ registers subscription handlers ↓ [airsonic-ui.components.current-queue.subs])) (def SortHandle @@ -60,7 +62,9 @@ :on-sort-end (fn [{:keys [old-idx new-idx]}] - (dispatch [:audio-player/move-song old-idx new-idx]))}]]) + ;; if we don't dispatch-sync, the UI sometimes places the row back and + ;; resorts it a litle later + (dispatch-sync [:audio-player/move-song old-idx new-idx]))}]]) (defn collection-info [{:keys [playlist-info]}] [:ul.is-smaller.collection-info diff --git a/src/cljs/bulma/dropdown/views.cljs b/src/cljs/bulma/dropdown/views.cljs index 0d4e2f8..a697350 100644 --- a/src/cljs/bulma/dropdown/views.cljs +++ b/src/cljs/bulma/dropdown/views.cljs @@ -5,12 +5,10 @@ [bulma.dropdown.events :as ev] [bulma.dropdown.subs :as sub])) -;; there's "muted-dispatch" in airsonic-ui.helpers which does the same thing -;; it's not used here because all the bulma.*-components should work independently - -(defn muted-dispatch [event-vector] +(defn choose-action [event-vector] (fn [e] (.preventDefault e) + (dispatch [::ev/hide]) (dispatch event-vector))) (defn generate-id [] @@ -42,4 +40,4 @@ (for [[idx {:keys [label event]}] (map-indexed vector items)] ^{:key (str dropdown-id "-" idx)} [:a.dropdown-item {:href "#" - :on-click (muted-dispatch event)} label])]]])))) + :on-click (choose-action event)} label])]]])))) diff --git a/src/sass/app.sass b/src/sass/app.sass index 911c787..4732201 100644 --- a/src/sass/app.sass +++ b/src/sass/app.sass @@ -298,17 +298,6 @@ margin-right: 1rem margin-bottom: 0 - .collection-info - list-style: none - - li - display: inline-block - margin-left: 0.75rem - - &:first-child - margin-left: 0 - - .song-list counter-reset: track @@ -323,6 +312,16 @@ display: inline padding-right: 0.375rem +.collection-info + list-style: none + + li + display: inline-block + margin-left: 0.75rem + + &:first-child + margin-left: 0 + .song-listing-table tr.is-playing background-color: $table-row-active-background-color