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

Restructure app init; fixes #5 and #11

Squashed commit of the following:

commit d4242bf1a390994606b7bd6e630c55338a14aad4
Author: Arne Schlüter <arne@schlueter.is>
Date:   Mon Jul 9 21:12:44 2018 +0200

    Add loading spinner, done with reworked app boot flow; fixes #5 and #11

commit e864ae4e578f96b86f3c0239b79f5224f0bb0020
Author: Arne Schlüter <arne@schlueter.is>
Date:   Mon Jul 9 19:43:02 2018 +0200

    Start restructuring app boot flow

commit a8cdbef80acde9f185a588ab86f8ea6964ebe8ab
Author: Arne Schlüter <arne@schlueter.is>
Date:   Mon Jul 9 14:03:43 2018 +0200

    Ignore rebel readline artifacts

commit 67eae3bc6aa2938ad6748c78b6259e532e66f865
Author: Arne Schlüter <arne@schlueter.is>
Date:   Mon Jul 9 14:03:11 2018 +0200

    Update shadow-cljs and run npm audit fix
This commit is contained in:
Arne Schlüter 2018-07-09 21:16:28 +02:00
commit cd06abff97
12 changed files with 1487 additions and 505 deletions

View file

@ -21,10 +21,8 @@
(reagent/render [views/main-panel] (.getElementById js/document "app")))
(defn ^:export init []
(routes/start-routing!)
(storage/reg-co-fx! :airsonic-ui {:fx :store
:cofx :store})
(re-frame/dispatch-sync [::events/initialize-db])
(re-frame/dispatch [::events/try-remember-user])
(re-frame/dispatch-sync [::events/initialize-app])
(dev-setup)
(mount-root))

View file

@ -2,6 +2,4 @@
(:require [airsonic-ui.routes :as routes]))
(def default-db
{;; because navigate! executes asynchronously we force to display the login screen first
:current-route [routes/default-route]
:notifications (sorted-map)})
{:notifications (sorted-map)})

View file

@ -4,12 +4,7 @@
[airsonic-ui.routes :as routes]
[airsonic-ui.db :as db]
[airsonic-ui.utils.api :as api]
[day8.re-frame.tracing :refer-macros [fn-traced]])) ; <- useful to debug handlers
;; this is where all of the event handling takes place; the names put the events into
;; the following categories:
;; ::events/something-happening -> relevant to only this app
;; :single-colon/something -> coming from external sources (e.g. :audio/... or :routes/...) that are potentially reusable
[day8.re-frame.tracing :refer-macros [fn-traced defn-traced]])) ; <- useful to debug handlers
(re-frame/reg-fx
;; a simple effect to keep println statements out of our event handlers
@ -17,71 +12,88 @@
(fn [params]
(apply println params)))
;; database reset / init
;; ---
;; app boot flow
;; * restoring a previous session
;; * initializing the router
;; * sending out the appropriate requests
;; ---
(re-frame/reg-event-db
::initialize-db
(re-frame/reg-event-fx
::initialize-app
(fn [_]
db/default-db))
{:db db/default-db
:dispatch [:init-flow/restore-previous-session]}))
(defn restore-previous-session
"See comment above for different steps; what's important here is that we check
for a previous session before anything else, otherwise we might run into auth
troubles with our router."
[{:keys [db store]} _]
(let [credentials (:credentials store)]
{:db (assoc db :credentials credentials)
:dispatch-n [(if credentials
[:init-flow/credentials-found credentials]
[:init-flow/credentials-missing])]
:routes/start-routing nil}))
(re-frame/reg-event-fx
:init-flow/restore-previous-session
[(re-frame/inject-cofx :store)]
restore-previous-session)
(defn credentials-found [_ [_ {:keys [u p server]}]]
{:dispatch [:credentials/verification-request u p server]})
(re-frame/reg-event-fx
:init-flow/credentials-found credentials-found)
(re-frame/reg-event-fx
:init-flow/credentials-missing
;; we don't do anything special here, it's just for the sake of clarity
(fn [_ _] {}))
;; ---
;; auth logic
;; ---
(defn authenticate
(defn-traced credentials-verification-request
"Tries to authenticate a user by pinging the server with credentials, saving
them when the request was succesful. Bypasses the request when a user saved
them when the request was successful. Bypasses the request when a user saved
their credentials."
[{:keys [db]} [_ user pass server]]
{:db (assoc-in db [:credentials :server] server)
:http-xhrio {:method :get
[_ [_ user pass server]]
{:http-xhrio {:method :get
:uri (api/url server "ping" {:u user :p pass})
:response-format (ajax/json-response-format {:keywords? true})
:on-success [::verify-auth-response user pass]
:on-success [:credentials/verification-response user pass server]
:on-failure [:api/bad-response]}})
(re-frame/reg-event-fx
::authenticate authenticate)
:credentials/verification-request credentials-verification-request)
(defn verify-auth-response
(defn credentials-verification-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 [_ user pass response]]
[fx [_ user pass server response]]
{:dispatch (if (api/is-error? response)
[:notification/show :error (api/error-msg (api/->exception response))]
[::credentials-verified user pass])})
[:credentials/verified user pass server])})
(re-frame/reg-event-fx
::verify-auth-response verify-auth-response)
(defn try-remember-user
"Enables skipping the auth request when credentials are saved in the
local storage; otherwise has no effect"
[{:keys [db store]} [_]]
(when-let [credentials (:credentials store)]
{:db (assoc-in db [:credentials :server] (:server credentials))
:dispatch [::credentials-verified (:u credentials) (:p credentials) nil]}))
(re-frame/reg-event-fx
::try-remember-user
[(re-frame/inject-cofx :store)]
try-remember-user)
:credentials/verification-response credentials-verification-response)
(defn credentials-verified
"Gets called after the server indicates that the credentials entered by a user
are correct (see `authenticate`)"
[{:keys [db store]} [_ user pass]]
(let [auth {:u user :p pass}
credentials (merge (:credentials db) auth)]
{:routes/set-credentials auth
are correct (see `credentials-verification-request`)"
[{:keys [db]} [_ user pass server]]
(let [credentials {:u user :p pass :server server}]
{:routes/set-credentials credentials
:store {:credentials credentials}
:db (assoc db :credentials credentials)
:dispatch [::logged-in]}))
(re-frame/reg-event-fx
::credentials-verified
[(re-frame/inject-cofx :store)]
credentials-verified)
:credentials/verified credentials-verified)
;; TODO: We have to find another solution for this once we have routes that
;; don't require a login but have the bottom controls
@ -91,20 +103,28 @@
(fn [_]
(.. js/document -documentElement -classList (add "has-navbar-fixed-bottom"))))
;; we do this in two steps to make sure the credentials are set once we navigate
(defn logged-in
[cofx _]
(let [redirect (or (get-in cofx [:routes/from-query-param :redirect])
[::routes/main])]
{:routes/navigate redirect
:show-nav-bar nil}))
(re-frame/reg-event-fx
::logged-in
(fn [_ _]
{:routes/navigate [::routes/main]
:show-nav-bar nil}))
[(re-frame/inject-cofx :routes/from-query-param :redirect)]
logged-in)
(defn logout
"Clears all credentials and redirects the user to the login page"
[_ _]
{:routes/navigate [::routes/login]
:routes/unset-credentials nil
:store nil
:db db/default-db})
[_ [_ & args]]
(let [args (apply hash-map args)]
{:routes/navigate (if-let [redirect (:redirect-to args)]
[::routes/login {} {:redirect (routes/encode-route redirect)}]
[::routes/login])
:routes/unset-credentials nil
:store nil
:db db/default-db}))
(re-frame/reg-event-fx
::logout logout)
@ -136,11 +156,12 @@
(re-frame/reg-event-fx
:api/good-response good-api-response)
(defn bad-api-response [db event]
{:log ["API call gone bad; are CORS headers missing? check for :status 0" event]
:dispatch [:notification/show :error "Communication with server failed. Check browser logs for details."]})
(re-frame/reg-event-fx
:api/bad-response
(fn [db event]
{:log ["API call gone bad; are CORS headers missing? check for :status 0" event]
:dispatch [:notification/show :error "Communication with server failed. Check browser logs for details."]}))
:api/bad-response bad-api-response)
;; ---
;; musique
@ -206,8 +227,9 @@
(re-frame/reg-event-fx
:routes/unauthorized
(fn [_ _]
{:dispatch [::logout]}))
[(re-frame/inject-cofx :routes/current-route)]
(fn [{:routes/keys [current-route]} _]
{:dispatch [::logout :redirect-to current-route]}))
;; ---
;; user messages

View file

@ -1,5 +1,6 @@
(ns airsonic-ui.routes
(:require [bide.core :as r]
[cljs.reader :refer [read-string]]
[re-frame.core :as re-frame]))
(def default-route ::login)
@ -72,8 +73,41 @@
(re-frame/dispatch [:routes/navigation route-id params query])
(re-frame/dispatch [:routes/unauthorized route-id params query])))
(defn encode-route
"Takes a parsed route and returns a representation that's suitable for
transportation in a uri component"
[route]
(js/encodeURIComponent (str route)))
(defn decode-route
"Decodes and encoded route from a uri component into a parsed route"
[encoded-route]
(read-string (js/decodeURIComponent encoded-route)))
(defn current-route
"Returns the parsed route for window.location.hash"
[]
(r/match router (subs (.. js/window -location -hash) 1)))
(re-frame/reg-cofx
:routes/current-route
(fn [coeffects _]
(assoc coeffects :routes/current-route (current-route))))
(re-frame/reg-cofx
:routes/from-query-param
(fn [coeffects param]
;; this allows us to encode a complete route in a url fragment; useful for
;; doing redirects
(let [[_ _ query] (current-route)
from-param (decode-route (get query param))]
(assoc-in coeffects [:routes/from-query-param param] from-param))))
(defn start-routing!
"Initializes the router and makes sure the correct events get dispatched."
[]
(r/start! router {:default default-route
:on-navigate on-navigate}))
([] (r/start! router {:default default-route
:on-navigate on-navigate}))
([_] (start-routing!))) ;; <- 1-arity is for the re-frame effect exposed below
(re-frame/reg-fx
:routes/start-routing start-routing!)

View file

@ -6,6 +6,7 @@
[airsonic-ui.subs :as subs]
[airsonic-ui.views.notifications :refer [notification-list]]
[airsonic-ui.views.loading-spinner :refer [loading-spinner]]
[airsonic-ui.views.breadcrumbs :refer [breadcrumbs]]
[airsonic-ui.views.bottom-bar :refer [bottom-bar]]
[airsonic-ui.views.login :refer [login-form]]
@ -51,24 +52,26 @@
(defn app [route params query]
(let [user @(subscribe [::subs/user])
content @(subscribe [::subs/current-content])]
[:div
[:main.columns
[:div.column.is-2.sidebar
[sidebar user]]
[:div.column
[:section.section
[breadcrumbs content]
(case route
::routes/main [most-recent content]
::routes/artist-view [artist-detail content]
::routes/album-view [album-detail content])]]]
[bottom-bar]]))
(if (= route ::routes/login)
[login-form]
[:div
[:main.columns
[:div.column.is-2.sidebar
[sidebar user]]
[:div.column
[:section.section
[breadcrumbs content]
(case route
::routes/main [most-recent content]
::routes/artist-view [artist-detail content]
::routes/album-view [album-detail content])]]]
[bottom-bar]])))
(defn main-panel []
(let [[route params query] @(subscribe [::subs/current-route])
notifications @(subscribe [::subs/notifications])]
[:div
[notification-list notifications]
(case route
::routes/login [login-form]
[app route params query])]))
(if route
[app route params query]
[:div.app-loading>div.loader])]))

View file

@ -16,7 +16,7 @@
server (r/atom (.. js/window -location -origin))
submit (fn [e]
(.preventDefault e)
(dispatch [::events/authenticate @user @pass @server]))]
(dispatch [:credentials/verification-request @user @pass @server]))]
(fn []
[:section.hero.is-fullheight>div.hero-body
[:div.container.has-text-centered>div.column.is-4.is-offset-4