mirror of
https://github.com/heyarne/airsonic-ui.git
synced 2026-05-06 18:33:38 +02:00
Closes #21. Squashed commit of the following: commit 964b29cf127cf51de86543d040bcb6c674b36d7e Author: Arne Schlüter <arne@schlueter.is> Date: Wed Aug 22 17:56:48 2018 +0200 Pass content for current route nicely to views commit b469a0a4b69457ddf3a679ac1acc82fbaffdc8fd Author: Arne Schlüter <arne@schlueter.is> Date: Wed Aug 22 16:01:04 2018 +0200 Add response cache in app-db commit da9faf89138f42ee544efc64c2e46787091b3dc7 Author: Arne Schlüter <arne@schlueter.is> Date: Wed Aug 22 13:40:57 2018 +0200 Move api helpers and tests to own namespace
256 lines
8.5 KiB
Clojure
256 lines
8.5 KiB
Clojure
(ns airsonic-ui.events
|
|
(:require [re-frame.core :as re-frame]
|
|
[ajax.core :as ajax]
|
|
[airsonic-ui.routes :as routes]
|
|
[airsonic-ui.db :as db]
|
|
[airsonic-ui.api.helpers :as api]
|
|
[airsonic-ui.audio.playlist :as playlist]))
|
|
|
|
(re-frame/reg-fx
|
|
;; a simple effect to keep println statements out of our event handlers
|
|
:log
|
|
(fn [params]
|
|
(apply println params)))
|
|
|
|
(defn noop
|
|
"An event handler that can be used for clarity; doesn't do anything, but might
|
|
give a name to an event"
|
|
[cofx _] cofx)
|
|
|
|
;; ---
|
|
;; app boot flow
|
|
;; * restoring a previous session
|
|
;; * initializing the router
|
|
;; * sending out the appropriate requests
|
|
;; ---
|
|
|
|
(defn initialize-app
|
|
[{{:keys [credentials]} :store} _]
|
|
(let [effects {:db db/default-db
|
|
:routes/start-routing nil}]
|
|
(if (not (empty? credentials))
|
|
(assoc effects :dispatch [:credentials/verify credentials])
|
|
effects)))
|
|
|
|
(re-frame/reg-event-fx
|
|
::initialize-app
|
|
[(re-frame/inject-cofx :store)]
|
|
initialize-app)
|
|
|
|
(defn verify-credentials
|
|
"Initializes the whole authentication chain when we have locally stored
|
|
credentials that look plausible."
|
|
[_ [_ credentials]]
|
|
;; TODO: spec this
|
|
(if (every? string? ((juxt :u :p :server) credentials))
|
|
{:dispatch [:credentials/send-authentication-request credentials]}))
|
|
|
|
(re-frame/reg-event-fx :credentials/verify verify-credentials)
|
|
|
|
;; ---
|
|
;; auth logic
|
|
;; ---
|
|
|
|
(defn user-login
|
|
"Gets called after the user clicked on the login button"
|
|
[cofx [_ user pass server]]
|
|
(let [credentials {:u user, :p pass, :server server, :verified? false}]
|
|
(-> (assoc-in cofx [:db :credentials] credentials)
|
|
(assoc :dispatch [:credentials/send-authentication-request credentials]))))
|
|
|
|
(re-frame/reg-event-fx :credentials/user-login user-login)
|
|
|
|
(defn authentication-request
|
|
"Tries to authenticate a user by pinging the server with credentials, saving
|
|
them when the request was successful. Bypasses the request when a user saved
|
|
their credentials."
|
|
[cofx [_ credentials]]
|
|
(assoc cofx :http-xhrio {:method :get
|
|
:uri (api/url (:server credentials) "ping" (select-keys credentials [:u :p]))
|
|
:response-format (ajax/json-response-format {:keywords? true})
|
|
:on-success [:credentials/authentication-response credentials]
|
|
:on-failure [:api/failed-response]})) ; <- we don't need endpoint and params here because the response is not cached
|
|
|
|
(re-frame/reg-event-fx :credentials/send-authentication-request authentication-request)
|
|
|
|
(defn authentication-response
|
|
"Since we don't get real status codes, we have to look into the server's
|
|
response and see whether we actually sent the correct credentials"
|
|
[fx [_ credentials response]]
|
|
(assoc fx :dispatch (if (api/is-error? response)
|
|
[:credentials/authentication-failure response]
|
|
[:credentials/authentication-success (assoc credentials :verified? true)])))
|
|
|
|
(re-frame/reg-event-fx :credentials/authentication-response authentication-response)
|
|
|
|
(defn authentication-failure
|
|
"Removes all stored credentials and displays potential api errors to the user"
|
|
[fx [_ response]]
|
|
(-> (assoc fx :dispatch [:notification/show :error (api/error-msg (api/->exception response))])
|
|
(update :store dissoc :credentials)
|
|
(update :db dissoc :credentials)))
|
|
|
|
(re-frame/reg-event-fx :credentials/authentication-failure authentication-failure)
|
|
|
|
(defn authentication-success
|
|
"Gets called after the server indicates that the credentials entered by a user
|
|
are correct (see `credentials-verification-request`)"
|
|
[{:keys [db]} [_ credentials]]
|
|
{:store {:credentials credentials}
|
|
:db (assoc db :credentials (assoc credentials :verified? true))
|
|
:dispatch [::logged-in]})
|
|
|
|
(re-frame/reg-event-fx :credentials/authentication-success authentication-success)
|
|
|
|
;; TODO: We have to find another solution for this once we have routes that
|
|
;; don't require a login but have the bottom controls
|
|
|
|
(re-frame/reg-fx
|
|
:show-nav-bar
|
|
(fn [_]
|
|
(.. js/document -documentElement -classList (add "has-navbar-fixed-bottom"))))
|
|
|
|
(defn logged-in
|
|
[cofx _]
|
|
(let [redirect (or (get-in cofx [:routes/from-query-param :redirect])
|
|
[::routes/main])]
|
|
{:dispatch [:routes/do-navigation redirect]
|
|
:show-nav-bar nil}))
|
|
|
|
(re-frame/reg-event-fx
|
|
::logged-in
|
|
[(re-frame/inject-cofx :routes/from-query-param :redirect)]
|
|
logged-in)
|
|
|
|
(defn logout
|
|
"Clears all credentials and redirects the user to the login page"
|
|
[cofx [_ & args]]
|
|
(let [args (apply hash-map args)]
|
|
{:dispatch [:routes/do-navigation (if-let [redirect (:redirect-to args)]
|
|
[::routes/login {} {:redirect (routes/encode-route redirect)}]
|
|
[::routes/login])]
|
|
:store nil
|
|
:db db/default-db
|
|
:audio/stop nil}))
|
|
|
|
(re-frame/reg-event-fx ::logout logout)
|
|
|
|
;; ---
|
|
;; musique
|
|
;; ---
|
|
|
|
; TODO: Make play, next and previous a bit prettier and more DRY
|
|
|
|
(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
|
|
::play-songs
|
|
(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))
|
|
:db (assoc-in db [:audio :playlist] playlist)})))
|
|
|
|
;; FIXME: :audio/play might not get the right argument here
|
|
|
|
(re-frame/reg-event-db
|
|
::set-playback-mode
|
|
(fn [db [_ playback-mode]]
|
|
(update-in db [:audio :playlist] #(playlist/set-playback-mode % playback-mode))))
|
|
|
|
(re-frame/reg-event-db
|
|
::set-repeat-mode
|
|
(fn [db [_ repeat-mode]]
|
|
(update-in db [:audio :playlist] #(playlist/set-repeat-mode % repeat-mode))))
|
|
|
|
(re-frame/reg-event-fx
|
|
::next-song
|
|
(fn [{:keys [db]} _]
|
|
(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)})))
|
|
|
|
(re-frame/reg-event-fx
|
|
::previous-song
|
|
(fn [{:keys [db]} _]
|
|
(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)})))
|
|
|
|
(re-frame/reg-event-db
|
|
::enqueue-next
|
|
(fn [db [_ song]]
|
|
(update-in db [:audio :playlist] #(playlist/enqueue-next % song))))
|
|
|
|
(re-frame/reg-event-db
|
|
::enqueue-last
|
|
(fn [db [_ song]]
|
|
(update-in db [:audio :playlist] #(playlist/enqueue-last % song))))
|
|
|
|
(re-frame/reg-event-fx
|
|
::toggle-play-pause
|
|
(fn [_ _]
|
|
{:audio/toggle-play-pause nil}))
|
|
|
|
(defn audio-update
|
|
"Reacts to audio events fired by the HTML5 audio player and plays the next
|
|
track if necessary."
|
|
[{:keys [db]} [_ status]]
|
|
(cond-> {:db (assoc-in db [:audio :playback-status] status)}
|
|
(:ended? status) (assoc :dispatch [::next-song])))
|
|
|
|
(re-frame/reg-event-fx :audio/update audio-update)
|
|
|
|
;; ---
|
|
;; routing
|
|
;; ---
|
|
|
|
(re-frame/reg-event-fx
|
|
:routes/did-navigate
|
|
(fn [{:keys [db]} [_ route params query]]
|
|
{:db (assoc db :routes/current-route [route params query])
|
|
:dispatch-n (routes/route-events route params query)}))
|
|
|
|
(re-frame/reg-event-fx
|
|
:routes/unauthorized
|
|
[(re-frame/inject-cofx :routes/current-route)]
|
|
(fn [{:routes/keys [current-route]} _]
|
|
{:dispatch [::logout :redirect-to current-route]}))
|
|
|
|
;; ---
|
|
;; user messages
|
|
;; ---
|
|
|
|
(def notification-duration
|
|
{:info 2500
|
|
:error 10000})
|
|
|
|
(defn show-notification
|
|
"Displays an informative message to the user"
|
|
[fx [_ level message]]
|
|
(let [id (.now js/performance)
|
|
hide-later (fn [level]
|
|
[{:ms (get notification-duration level)
|
|
:dispatch [:notification/hide id]}])]
|
|
(if (nil? message)
|
|
(let [message level
|
|
level :info]
|
|
(-> (assoc-in fx [:db :notifications id] {:level level
|
|
:message message})
|
|
(assoc :dispatch-later (hide-later level))))
|
|
(-> (assoc-in fx [:db :notifications id] {:level level
|
|
:message message})
|
|
(assoc :dispatch-later (hide-later level))))))
|
|
|
|
(re-frame/reg-event-fx :notification/show show-notification)
|
|
|
|
(defn hide-notification
|
|
[db [_ notification-id]]
|
|
(update db :notifications dissoc notification-id))
|
|
|
|
(re-frame/reg-event-db :notification/hide hide-notification)
|