mirror of
https://github.com/heyarne/airsonic-ui.git
synced 2026-05-07 10:43:39 +02:00
Merge incomplete podcast support
commit 4ac35d6530f7770e7b80307321c72541a55e2c8e
Author: Arne Schlüter <arne@schlueter.is>
Date: Mon Oct 8 21:09:04 2018 +0200
Stub out podcast detail view
commit 60742a22e93bfe6f432e06d56d3e4da671184559
Author: Arne Schlüter <arne@schlueter.is>
Date: Tue Sep 18 23:02:39 2018 +0200
Simplify api helpers; closes #16
commit 8bbc79ebf4dbbe3dbfa08cb4c7c1edd341d507eb
Author: Arne Schlüter <arne@schlueter.is>
Date: Tue Sep 18 19:39:17 2018 +0200
Adjust `stream-url` to work with podcast episodes
commit 991ba5b65230a7429c160ca1b7968ecbb8595e0b
Author: Arne Schlüter <arne@schlueter.is>
Date: Tue Sep 18 19:14:08 2018 +0200
Fix breadcrumbs for podcasts
commit 37c3a894eded2fe37f9af031d3132c7175702266
Author: Arne Schlüter <arne@schlueter.is>
Date: Tue Sep 18 15:11:54 2018 +0200
Stub out overview for podcasts
This commit is contained in:
parent
38eea1c8c9
commit
fa485bbf42
19 changed files with 350 additions and 133 deletions
|
|
@ -3,17 +3,13 @@
|
|||
[airsonic-ui.audio.playlist :as playlist]
|
||||
[airsonic-ui.api.helpers :as api]))
|
||||
|
||||
(defn- song-url [db song]
|
||||
(let [creds (:credentials db)]
|
||||
(api/song-url (:server creds) (select-keys creds [:u :p]) song)))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
; sets up the db, starts to play a song and adds the rest to a playlist
|
||||
:audio-player/play-all
|
||||
(fn [{:keys [db]} [_ songs start-idx]]
|
||||
(let [playlist (-> (playlist/->playlist songs :playback-mode :linear :repeat-mode :repeat-all)
|
||||
(playlist/set-current-song start-idx))]
|
||||
{:audio/play (song-url db (playlist/peek playlist))
|
||||
{:audio/play (api/stream-url (:credentials db) (playlist/peek playlist))
|
||||
:db (assoc-in db [:audio :playlist] playlist)})))
|
||||
|
||||
;; FIXME: :audio/play might not get the right argument here
|
||||
|
|
@ -34,7 +30,7 @@
|
|||
(let [db (update-in db [:audio :playlist] playlist/next-song)
|
||||
next (playlist/peek (get-in db [:audio :playlist]))]
|
||||
{:db db
|
||||
:audio/play (song-url db next)})))
|
||||
:audio/play (api/stream-url (:credentials db) next)})))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
:audio-player/previous-song
|
||||
|
|
@ -42,7 +38,7 @@
|
|||
(let [db (update-in db [:audio :playlist] playlist/previous-song)
|
||||
prev (playlist/peek (get-in db [:audio :playlist]))]
|
||||
{:db db
|
||||
:audio/play (song-url db prev)})))
|
||||
:audio/play (api/stream-url (:credentials db) prev)})))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
:audio-player/enqueue-next
|
||||
|
|
|
|||
|
|
@ -22,17 +22,17 @@
|
|||
year (conj [:li [icon :calendar] (str "Released in " year)]))))
|
||||
|
||||
|
||||
(defn preview [album]
|
||||
(defn album-card [album]
|
||||
(let [{:keys [artist artistId name id]} album]
|
||||
[card album
|
||||
:url-fn #(url-for ::routes/album-view {:id id})
|
||||
:url-fn #(url-for ::routes/album.detail {:id id})
|
||||
:content [:div
|
||||
;; link to album
|
||||
[:div.title.is-5
|
||||
[:a {:href (url-for ::routes/album-view {:id id})
|
||||
[: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-view {:id artistId})
|
||||
[:div.subtitle.is-6 [:a {:href (url-for ::routes/artist.detail {:id artistId})
|
||||
:title artist} artist]]]]))
|
||||
|
||||
(defn listing [albums]
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
[:div.columns.is-multiline.is-mobile
|
||||
(for [[idx album] (map-indexed vector albums)]
|
||||
^{:key idx} [:div.column.is-one-fifth-desktop.is-one-quarter-tablet.is-half-mobile
|
||||
[preview album]])])
|
||||
[album-card album]])])
|
||||
|
||||
(defn detail
|
||||
"Lists all songs in an album"
|
||||
|
|
|
|||
13
src/cljs/airsonic_ui/components/podcast/events.cljs
Normal file
13
src/cljs/airsonic_ui/components/podcast/events.cljs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
(ns airsonic-ui.components.podcast.events)
|
||||
|
||||
(defn subscribe-to-channel
|
||||
[db [_ channel-url]])
|
||||
|
||||
(defn delete-podcast-channel
|
||||
[db [_ channel-id]])
|
||||
|
||||
(defn download-episode
|
||||
[db [_ episode-id]])
|
||||
|
||||
(defn delete-episode
|
||||
[db [_ episode-id]])
|
||||
52
src/cljs/airsonic_ui/components/podcast/subs.cljs
Normal file
52
src/cljs/airsonic_ui/components/podcast/subs.cljs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
(ns airsonic-ui.components.podcast.subs
|
||||
(:require [re-frame.core :refer [reg-sub]]))
|
||||
|
||||
;; this unwraps the api response into a collection
|
||||
(reg-sub
|
||||
::podcast.response
|
||||
:<- [:api/response-for "getPodcasts"]
|
||||
(fn [response]
|
||||
(:channel response)))
|
||||
|
||||
(defn podcast-channels
|
||||
"Given a podcast response, returns information about the channels that have
|
||||
been subscribed to."
|
||||
[response _]
|
||||
(when response
|
||||
(map #(dissoc % :episode) response)))
|
||||
|
||||
(reg-sub
|
||||
::podcast.channels
|
||||
:<- [::podcast.response]
|
||||
podcast-channels)
|
||||
|
||||
(defn sorted-podcast-episodes
|
||||
"Given a response of all podcasts, returns all episodes sorted by a given function"
|
||||
[response [_ key-fn & {:keys [n reverse?]
|
||||
:or {n 15
|
||||
reverse? true}}]]
|
||||
;; some podcasts have an :artist and some don't, we make sure all of them have one
|
||||
(let [id->channel (into {} (map (juxt :id :title) response))]
|
||||
(let [sorted (->> (mapcat :episode response)
|
||||
(map (fn [episode]
|
||||
(assoc episode :artist (id->channel (:channelId episode)))))
|
||||
(sort-by (or key-fn identity)))]
|
||||
(take n (if reverse? (reverse sorted) sorted)))))
|
||||
|
||||
(reg-sub
|
||||
::podcast.all-episodes-by
|
||||
:<- [::podcast.response]
|
||||
sorted-podcast-episodes)
|
||||
|
||||
(defn podcast-detail
|
||||
"Since there's no real detail request, this function provides some abstraction
|
||||
for that in providing a lense to only the podcast with a specific channel-id."
|
||||
[[response [_ params _]] _]
|
||||
(let [channel-id (:id params)]
|
||||
(first (filter #(= channel-id (:id %)) response))))
|
||||
|
||||
(reg-sub
|
||||
::podcast.detail-from-route
|
||||
:<- [::podcast.response]
|
||||
:<- [:routes/current-route]
|
||||
podcast-detail)
|
||||
83
src/cljs/airsonic_ui/components/podcast/views.cljs
Normal file
83
src/cljs/airsonic_ui/components/podcast/views.cljs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
(ns airsonic-ui.components.podcast.views
|
||||
(:require [re-frame.core :refer [subscribe]]
|
||||
[airsonic-ui.helpers :refer [muted-dispatch]]
|
||||
[airsonic-ui.routes :as routes :refer [url-for]]
|
||||
[airsonic-ui.components.podcast.subs :as subs]
|
||||
[airsonic-ui.views.cover :refer [cover card]]
|
||||
[airsonic-ui.views.icon :refer [icon]]
|
||||
[airsonic-ui.components.debug.views :refer [debug]]))
|
||||
|
||||
;; TODO: Implement detail pages for podcasts
|
||||
;; TODO: Implement CRUD frontend for podcasts
|
||||
;; TODO: Error handling for channels and episodes
|
||||
|
||||
(defn channel-card
|
||||
"Displays the cover of a podcast and links to the podcasts detail page"
|
||||
[channel]
|
||||
[card channel
|
||||
:url-fn #(url-for ::routes/podcast.detail {:id (:id channel)})
|
||||
:content [:div.title.is-5
|
||||
[:a {:href (url-for ::routes/podcast.detail {:id (:id channel)})
|
||||
:title (:title channel)} (:title channel)]]])
|
||||
(defn- channel-overview [channels]
|
||||
[:div.columns.is-multiline.is-mobile
|
||||
(for [[idx channel] (map-indexed vector channels)]
|
||||
^{:key idx}
|
||||
[:div.column.is-one-fifth-desktop.is-one-quarter-tablet.is-half-mobile
|
||||
[channel-card channel]])])
|
||||
|
||||
(defn- episode-actions [episode]
|
||||
(case (:status episode)
|
||||
"completed"
|
||||
[[:td>a {:title "Play next"
|
||||
:href "#"
|
||||
:on-click (muted-dispatch [:audio-player/enqueue-next episode])}
|
||||
[icon :plus]]
|
||||
[:td>a {:title "Play last"
|
||||
:href "#"
|
||||
:on-click (muted-dispatch [:audio-player/enqueue-last episode])}
|
||||
[icon :caret-right]]]
|
||||
"skipped" ;; FIXME: Show download button
|
||||
[[:td] [:td]]))
|
||||
|
||||
(defn- episode-list [episodes]
|
||||
[:table.table.is-striped.is-hoverable.is-fullwidth>tbody
|
||||
(for [[idx episode] (map-indexed vector episodes)]
|
||||
^{:key idx}
|
||||
(into
|
||||
[:tr
|
||||
[:td.grow [:span
|
||||
[:a {:href (url-for ::routes/podcast.detail {:id (:channelId episode)})}
|
||||
(:artist episode)]
|
||||
" - "
|
||||
[:a {:title (:title episode)
|
||||
:href "#"
|
||||
:on-click (muted-dispatch [:audio-player/play-all episodes idx])}
|
||||
(:title episode)]]]]
|
||||
(episode-actions episode)))])
|
||||
|
||||
(defn detail
|
||||
"Detail page for a single channel"
|
||||
[_]
|
||||
;; NOTE: This isn't especially pretty, but it works. The detail page can only
|
||||
;; ever be displayed for the podcast the current route points to
|
||||
(let [channel @(subscribe [::subs/podcast.detail-from-route])]
|
||||
[:div
|
||||
[:section.section>div.hero-body
|
||||
[:div.container>article.media
|
||||
[:div.media-left [cover channel 128]]
|
||||
[:div.media-content
|
||||
[:h2.title (:title channel)]
|
||||
[:p (:description channel)]]]]
|
||||
[:section.section>div.container [episode-list (:episode channel)]]]))
|
||||
|
||||
(defn overview
|
||||
"All channels and most recently published shows"
|
||||
[_]
|
||||
(let [channels @(subscribe [::subs/podcast.channels])
|
||||
episodes @(subscribe [::subs/podcast.all-episodes-by :created])]
|
||||
[:section.section>div.container
|
||||
[:h1.title "Subscriptions"]
|
||||
[channel-overview channels]
|
||||
[:h1.title "Latest Episodes"]
|
||||
[episode-list episodes]]))
|
||||
|
|
@ -19,10 +19,11 @@
|
|||
:default-value search-term
|
||||
:placeholder "Search"}]]])))
|
||||
|
||||
|
||||
(defn artist-results [{:keys [artist]}]
|
||||
[:div.columns.is-multiline.is-mobile
|
||||
(for [[idx artist] (map-indexed vector artist)]
|
||||
(let [url #(url-for ::routes/artist-view (select-keys % [:id]))]
|
||||
(let [url #(url-for ::routes/artist.detail (select-keys % [:id]))]
|
||||
^{:key idx} [:div.column.is-2
|
||||
[card artist
|
||||
:url-fn url
|
||||
|
|
@ -33,7 +34,7 @@
|
|||
(defn album-results [{:keys [album]}]
|
||||
[:div.columns.is-multiline.is-mobile
|
||||
(for [[idx album] (map-indexed vector album)]
|
||||
(let [url #(url-for ::routes/album-view (select-keys % [:id]))
|
||||
(let [url #(url-for ::routes/album.detail (select-keys % [:id]))
|
||||
title (str (:name album) " (" (:artist album) ")")]
|
||||
^{:key idx} [:div.column.is-2 [card album
|
||||
:url-fn url
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue