mirror of
https://github.com/heyarne/airsonic-ui.git
synced 2026-05-07 02:33:39 +02:00
Add keyboard shortcuts (#43)
* Use rf instead of re-frame * Add bulma modal component * Add option to toggle a modal * Add rudimentary keyboard shortcuts; closes #41
This commit is contained in:
parent
a75cdca9e1
commit
149fd07566
17 changed files with 291 additions and 101 deletions
|
|
@ -2,7 +2,7 @@
|
|||
"This namespace contains some JS interop code to interact with an audio player
|
||||
and receive information about the current playback status so we can use it in
|
||||
our re-frame app."
|
||||
(:require [re-frame.core :as re-frame]
|
||||
(:require [re-frame.core :as rf]
|
||||
[airsonic-ui.audio.playlist :as playlist]
|
||||
[goog.functions :refer [throttle]]))
|
||||
|
||||
|
|
@ -29,13 +29,13 @@
|
|||
|
||||
|
||||
(defn attach-listeners! [el]
|
||||
(let [emit-audio-update (throttle #(re-frame/dispatch [:audio/update (->status el)]) 16)]
|
||||
(let [emit-audio-update (throttle #(rf/dispatch [:audio/update (->status el)]) 16)]
|
||||
(doseq [event ["loadstart" "progress" "play" "timeupdate" "pause"]]
|
||||
(.addEventListener el event emit-audio-update))))
|
||||
|
||||
;; effects to be fired from event handlers
|
||||
|
||||
(re-frame/reg-fx
|
||||
(rf/reg-fx
|
||||
:audio/play
|
||||
(fn [stream-url]
|
||||
(when-not @audio
|
||||
|
|
@ -45,19 +45,19 @@
|
|||
(set! (.-src @audio) stream-url)
|
||||
(.play @audio)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
(rf/reg-fx
|
||||
:audio/pause
|
||||
(fn [_]
|
||||
(some-> @audio .pause)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
(rf/reg-fx
|
||||
:audio/stop
|
||||
(fn [_]
|
||||
(when-let [audio @audio]
|
||||
(.pause audio)
|
||||
(set! (.-currentTime audio) 0))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
(rf/reg-fx
|
||||
:audio/toggle-play-pause
|
||||
(fn [_]
|
||||
(if-let [a @audio]
|
||||
|
|
@ -65,7 +65,7 @@
|
|||
(.play a)
|
||||
(.pause a)))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
(rf/reg-fx
|
||||
:audio/seek
|
||||
(fn [[percentage duration]]
|
||||
(set! (. @audio -currentTime)
|
||||
|
|
@ -78,16 +78,16 @@
|
|||
[db _]
|
||||
(:audio db))
|
||||
|
||||
(re-frame/reg-sub :audio/summary summary)
|
||||
(rf/reg-sub :audio/summary summary)
|
||||
|
||||
(defn playlist
|
||||
"Lists the complete playlist"
|
||||
[summary _]
|
||||
(:playlist summary))
|
||||
|
||||
(re-frame/reg-sub
|
||||
(rf/reg-sub
|
||||
:audio/playlist
|
||||
(fn [_ _] (re-frame/subscribe [:audio/summary]))
|
||||
(fn [_ _] (rf/subscribe [:audio/summary]))
|
||||
playlist)
|
||||
|
||||
(defn current-song
|
||||
|
|
@ -96,9 +96,9 @@
|
|||
[playlist _]
|
||||
(playlist/peek playlist))
|
||||
|
||||
(re-frame/reg-sub
|
||||
(rf/reg-sub
|
||||
:audio/current-song
|
||||
(fn [_ _] (re-frame/subscribe [:audio/playlist]))
|
||||
(fn [_ _] (rf/subscribe [:audio/playlist]))
|
||||
current-song)
|
||||
|
||||
(defn playback-status
|
||||
|
|
@ -106,9 +106,9 @@
|
|||
[summary _]
|
||||
(:playback-status summary))
|
||||
|
||||
(re-frame/reg-sub
|
||||
(rf/reg-sub
|
||||
:audio/playback-status
|
||||
(fn [_ _] (re-frame/subscribe [:audio/summary]))
|
||||
(fn [_ _] (rf/subscribe [:audio/summary]))
|
||||
playback-status)
|
||||
|
||||
(defn is-playing?
|
||||
|
|
@ -117,7 +117,7 @@
|
|||
(and (not (:paused? playback-status))
|
||||
(not (:ended? playback-status))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
(rf/reg-sub
|
||||
:audio/is-playing?
|
||||
(fn [_ _] (re-frame/subscribe [:audio/playback-status]))
|
||||
(fn [_ _] (rf/subscribe [:audio/playback-status]))
|
||||
is-playing?)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
(ns airsonic-ui.components.audio-player.events
|
||||
(:require [re-frame.core :as re-frame]
|
||||
(:require [re-frame.core :as rf]
|
||||
[airsonic-ui.audio.playlist :as playlist]
|
||||
[airsonic-ui.api.helpers :as api]))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
(rf/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]]
|
||||
|
|
@ -12,17 +12,17 @@
|
|||
{:audio/play (api/stream-url (:credentials db) (playlist/peek playlist))
|
||||
:db (assoc-in db [:audio :playlist] playlist)})))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
(rf/reg-event-db
|
||||
:audio-player/set-playback-mode
|
||||
(fn [db [_ playback-mode]]
|
||||
(update-in db [:audio :playlist] #(playlist/set-playback-mode % playback-mode))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
(rf/reg-event-db
|
||||
:audio-player/set-repeat-mode
|
||||
(fn [db [_ repeat-mode]]
|
||||
(update-in db [:audio :playlist] #(playlist/set-repeat-mode % repeat-mode))))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
(rf/reg-event-fx
|
||||
:audio-player/next-song
|
||||
(fn [{:keys [db]} _]
|
||||
(let [db (update-in db [:audio :playlist] playlist/next-song)
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
{:db db
|
||||
:audio/play (api/stream-url (:credentials db) next)})))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
(rf/reg-event-fx
|
||||
:audio-player/previous-song
|
||||
(fn [{:keys [db]} _]
|
||||
(let [db (update-in db [:audio :playlist] playlist/previous-song)
|
||||
|
|
@ -38,17 +38,17 @@
|
|||
{:db db
|
||||
:audio/play (api/stream-url (:credentials db) prev)})))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
(rf/reg-event-db
|
||||
:audio-player/enqueue-next
|
||||
(fn [db [_ song]]
|
||||
(update-in db [:audio :playlist] #(playlist/enqueue-next % song))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
(rf/reg-event-db
|
||||
:audio-player/enqueue-last
|
||||
(fn [db [_ song]]
|
||||
(update-in db [:audio :playlist] #(playlist/enqueue-last % song))))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
(rf/reg-event-fx
|
||||
:audio-player/toggle-play-pause
|
||||
(fn [_ _]
|
||||
{:audio/toggle-play-pause nil}))
|
||||
|
|
@ -60,9 +60,9 @@
|
|||
(cond-> {:db (assoc-in db [:audio :playback-status] status)}
|
||||
(:ended? status) (assoc :dispatch [:audio-player/next-song])))
|
||||
|
||||
(re-frame/reg-event-fx :audio/update audio-update)
|
||||
(rf/reg-event-fx :audio/update audio-update)
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
(rf/reg-event-fx
|
||||
:audio-player/seek
|
||||
(fn [{:keys [db]} [_ percentage]]
|
||||
(let [duration (:duration (playlist/peek (get-in db [:audio :playlist])))]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
(ns airsonic-ui.components.keyboard-shortcuts.config)
|
||||
|
||||
;; this keymap has the following structure:
|
||||
;; [[readable-key readable-description event-vector event-keys]
|
||||
;; ...]
|
||||
|
||||
(def keymap
|
||||
[["Space" "Toggle play / pause"
|
||||
[:audio-player/toggle-play-pause]
|
||||
[{:keyCode 32}]]
|
||||
["←" "Previous song"
|
||||
[:audio-player/previous-song]
|
||||
[{:keyCode 37}]]
|
||||
["→" "Next song"
|
||||
[:audio-player/next-song]
|
||||
[{:keyCode 39}]]
|
||||
["?" "Show / hide keyboard shortcut help"
|
||||
[:bulma.modal.events/toggle :keyboard-shortcuts-help]
|
||||
[{:keyCode 63}]]])
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
(ns airsonic-ui.components.keyboard-shortcuts.events
|
||||
(:require [re-frame.core :as rf]
|
||||
[re-pressed.core :as rp]
|
||||
[airsonic-ui.components.keyboard-shortcuts.config :as config]))
|
||||
|
||||
(rf/reg-event-fx
|
||||
::init-shortcuts
|
||||
(fn []
|
||||
(let [event-keys (map (juxt #(nth % 2) #(nth % 3)) config/keymap)
|
||||
prevent-default-keys (mapcat last event-keys)]
|
||||
{:dispatch-n [[::rp/add-keyboard-event-listener "keydown"]
|
||||
[::rp/set-keydown-rules {:event-keys event-keys
|
||||
:prevent-default-keys prevent-default-keys}]]})))
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
(ns airsonic-ui.components.keyboard-shortcuts.views
|
||||
(:require [bulma.modal.views :as bulma]
|
||||
[airsonic-ui.components.keyboard-shortcuts.config :as config]))
|
||||
|
||||
(defn help-modal []
|
||||
[bulma/modal-card {:title "Keyboard Shortcuts"
|
||||
:modal-id :keyboard-shortcuts-help}
|
||||
[:table.table.is-hoverable.is-fullwidth
|
||||
[:thead [:tr [:th "Key"] [:th "Function"]]]
|
||||
[:tbody
|
||||
(for [[idx [k desc]] (map-indexed vector config/keymap)]
|
||||
^{:key idx} [:tr [:td>code k] [:td desc]])]]])
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
(ns airsonic-ui.components.library.subs
|
||||
(:require [re-frame.core :as re-frame]
|
||||
(:require [re-frame.core :as rf]
|
||||
[airsonic-ui.config :as conf]))
|
||||
|
||||
;; first some helper functions to make the structure a bit clearer
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
(map (fn [[k v]] [(inc k) v]))
|
||||
(into (sorted-map))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
(rf/reg-sub
|
||||
:library/paginated
|
||||
:<- [:api/responses-for-endpoint "getAlbumList2"]
|
||||
paginated-library)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
(ns airsonic-ui.core
|
||||
(:require [reagent.core :as reagent]
|
||||
[re-frame.core :as re-frame]
|
||||
[re-frame.core :as rf]
|
||||
;; 3rd party effects / coeffects
|
||||
[day8.re-frame.http-fx]
|
||||
[akiroz.re-frame.storage :as storage]
|
||||
|
|
@ -11,6 +11,7 @@
|
|||
[airsonic-ui.api.events]
|
||||
[airsonic-ui.api.subs]
|
||||
[airsonic-ui.components.audio-player.events]
|
||||
[airsonic-ui.components.keyboard-shortcuts.events :as keyboard]
|
||||
[airsonic-ui.components.library.subs]
|
||||
[airsonic-ui.components.search.events]
|
||||
[airsonic-ui.components.search.subs]
|
||||
|
|
@ -24,12 +25,12 @@
|
|||
(println "dev mode")))
|
||||
|
||||
(defn mount-root []
|
||||
(re-frame/clear-subscription-cache!)
|
||||
(rf/clear-subscription-cache!)
|
||||
(reagent/render [views/main-panel] (.getElementById js/document "app")))
|
||||
|
||||
(defn ^:export init []
|
||||
(storage/reg-co-fx! :airsonic-ui {:fx :store
|
||||
:cofx :store})
|
||||
(re-frame/dispatch-sync [::events/initialize-app])
|
||||
(storage/reg-co-fx! :airsonic-ui {:fx :store, :cofx :store})
|
||||
(rf/dispatch-sync [::events/initialize-app])
|
||||
(rf/dispatch [::keyboard/init-shortcuts])
|
||||
(dev-setup)
|
||||
(mount-root))
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
(ns airsonic-ui.events
|
||||
(:require [re-frame.core :as re-frame]
|
||||
(:require [re-frame.core :as rf]
|
||||
[ajax.core :as ajax]
|
||||
[airsonic-ui.routes :as routes]
|
||||
[airsonic-ui.db :as db]
|
||||
[airsonic-ui.api.helpers :as api]))
|
||||
|
||||
(re-frame/reg-fx
|
||||
(rf/reg-fx
|
||||
;; a simple effect to keep println statements out of our event handlers
|
||||
:log
|
||||
(fn [params]
|
||||
|
|
@ -31,9 +31,9 @@
|
|||
(assoc effects :dispatch [:credentials/verify credentials])
|
||||
effects)))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
(rf/reg-event-fx
|
||||
::initialize-app
|
||||
[(re-frame/inject-cofx :store)]
|
||||
[(rf/inject-cofx :store)]
|
||||
initialize-app)
|
||||
|
||||
(defn verify-credentials
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
(if (every? string? ((juxt :u :p :server) credentials))
|
||||
{:dispatch [:credentials/send-authentication-request credentials]}))
|
||||
|
||||
(re-frame/reg-event-fx :credentials/verify verify-credentials)
|
||||
(rf/reg-event-fx :credentials/verify verify-credentials)
|
||||
|
||||
;; ---
|
||||
;; auth logic
|
||||
|
|
@ -57,7 +57,7 @@
|
|||
{:db (assoc db :credentials credentials)
|
||||
:dispatch [:credentials/send-authentication-request credentials]}))
|
||||
|
||||
(re-frame/reg-event-fx :credentials/user-login user-login)
|
||||
(rf/reg-event-fx :credentials/user-login user-login)
|
||||
|
||||
(defn authentication-request
|
||||
"Tries to authenticate a user by requesting info about the given user, saving
|
||||
|
|
@ -69,7 +69,7 @@
|
|||
: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)
|
||||
(rf/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
|
||||
|
|
@ -79,7 +79,7 @@
|
|||
[:credentials/authentication-failure response]
|
||||
[:credentials/authentication-success credentials response])})
|
||||
|
||||
(re-frame/reg-event-fx :credentials/authentication-response authentication-response)
|
||||
(rf/reg-event-fx :credentials/authentication-response authentication-response)
|
||||
|
||||
(defn authentication-failure
|
||||
"Removes all stored credentials and displays potential api errors to the user"
|
||||
|
|
@ -88,7 +88,7 @@
|
|||
:store (dissoc store :credentials)
|
||||
:db (dissoc db :credentials)})
|
||||
|
||||
(re-frame/reg-event-fx :credentials/authentication-failure authentication-failure)
|
||||
(rf/reg-event-fx :credentials/authentication-failure authentication-failure)
|
||||
|
||||
(defn authentication-success
|
||||
"Gets called after the server indicates that the credentials entered by a user
|
||||
|
|
@ -99,7 +99,7 @@
|
|||
(assoc :user (api/unwrap-response auth-response)))
|
||||
:dispatch [::logged-in]})
|
||||
|
||||
(re-frame/reg-event-fx :credentials/authentication-success authentication-success)
|
||||
(rf/reg-event-fx :credentials/authentication-success authentication-success)
|
||||
|
||||
(defn logged-in
|
||||
[cofx _]
|
||||
|
|
@ -107,9 +107,9 @@
|
|||
[::routes/library])]
|
||||
{:dispatch [:routes/do-navigation redirect]}))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
(rf/reg-event-fx
|
||||
::logged-in
|
||||
[(re-frame/inject-cofx :routes/from-query-param :redirect)]
|
||||
[(rf/inject-cofx :routes/from-query-param :redirect)]
|
||||
logged-in)
|
||||
|
||||
(defn logout
|
||||
|
|
@ -123,21 +123,21 @@
|
|||
:db db/default-db
|
||||
:audio/stop nil}))
|
||||
|
||||
(re-frame/reg-event-fx ::logout logout)
|
||||
(rf/reg-event-fx ::logout logout)
|
||||
|
||||
;; ---
|
||||
;; routing
|
||||
;; ---
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
(rf/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
|
||||
(rf/reg-event-fx
|
||||
:routes/unauthorized
|
||||
[(re-frame/inject-cofx :routes/current-route)]
|
||||
[(rf/inject-cofx :routes/current-route)]
|
||||
(fn [{:routes/keys [current-route]} _]
|
||||
{:dispatch [::logout :redirect-to current-route]}))
|
||||
|
||||
|
|
@ -161,10 +161,10 @@
|
|||
:dispatch-later [{:ms (get notification-duration level)
|
||||
:dispatch [:notification/hide id]}]}))
|
||||
|
||||
(re-frame/reg-event-fx :notification/show show-notification)
|
||||
(rf/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)
|
||||
(rf/reg-event-db :notification/hide hide-notification)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
(ns airsonic-ui.routes
|
||||
(:require [bide.core :as r]
|
||||
[cljs.reader :refer [read-string]]
|
||||
[re-frame.core :as re-frame]
|
||||
[re-frame.core :as rf]
|
||||
[airsonic-ui.config :as conf]))
|
||||
|
||||
(def default-route ::login)
|
||||
|
|
@ -96,15 +96,15 @@
|
|||
|
||||
;; subscription returning the matched route for the current hashbang
|
||||
|
||||
(re-frame/reg-sub :routes/current-route (fn [db _] (:routes/current-route db)))
|
||||
(rf/reg-sub :routes/current-route (fn [db _] (:routes/current-route db)))
|
||||
|
||||
;; NOTE: There is some duplication here. The route events are provided as a
|
||||
;; subscription but they are also invoked directly in events.cljs. It didn't
|
||||
;; seem to justify pulling in a whole library and we need it in our top most view
|
||||
|
||||
(re-frame/reg-sub
|
||||
(rf/reg-sub
|
||||
:routes/events-for-current-route
|
||||
(fn [db _] (re-frame/subscribe [:routes/current-route]))
|
||||
(fn [db _] (rf/subscribe [:routes/current-route]))
|
||||
(fn [current-route _] (apply route-events current-route)))
|
||||
|
||||
;; these are helper effects we can use to navigate; the first two manage an atom
|
||||
|
|
@ -133,7 +133,7 @@
|
|||
(apply r/navigate! router route)
|
||||
(dissoc context :event)))))
|
||||
|
||||
(re-frame/reg-event-fx :routes/do-navigation do-navigation (fn [& _] nil))
|
||||
(rf/reg-event-fx :routes/do-navigation do-navigation (fn [& _] nil))
|
||||
|
||||
(defn can-access? [route]
|
||||
(or (not (protected-routes route))
|
||||
|
|
@ -143,8 +143,8 @@
|
|||
[route-id params query]
|
||||
#_(println "calling on-navigate with" route credentials')
|
||||
(if (can-access? route-id)
|
||||
(re-frame/dispatch [:routes/did-navigate route-id params query])
|
||||
(re-frame/dispatch [:routes/unauthorized route-id params query])))
|
||||
(rf/dispatch [:routes/did-navigate route-id params query])
|
||||
(rf/dispatch [:routes/unauthorized route-id params query])))
|
||||
|
||||
(defn encode-route
|
||||
"Takes a parsed route and returns a representation that's suitable for
|
||||
|
|
@ -163,13 +163,13 @@
|
|||
(r/match router (subs (.. js/window -location -hash) 1)))
|
||||
|
||||
;; add the current route to our coeffect map
|
||||
(re-frame/reg-cofx
|
||||
(rf/reg-cofx
|
||||
:routes/current-route
|
||||
(fn [coeffects _]
|
||||
(assoc coeffects :routes/current-route (current-route))))
|
||||
|
||||
;; add route into from a URL parameter to our coeffect map
|
||||
(re-frame/reg-cofx
|
||||
(rf/reg-cofx
|
||||
:routes/from-query-param
|
||||
(fn [coeffects param]
|
||||
;; this allows us to encode a complete route in a url fragment; useful for
|
||||
|
|
@ -184,5 +184,5 @@
|
|||
:on-navigate on-navigate}))
|
||||
([_] (start-routing!))) ;; <- 1-arity is for the re-frame effect exposed below
|
||||
|
||||
(re-frame/reg-fx
|
||||
(rf/reg-fx
|
||||
:routes/start-routing start-routing!)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
[airsonic-ui.components.bangpow.views :refer [not-found]]
|
||||
[airsonic-ui.components.collection.views :as collection]
|
||||
[airsonic-ui.components.current-queue.views :refer [current-queue]]
|
||||
[airsonic-ui.components.keyboard-shortcuts.views :as keyboard]
|
||||
[airsonic-ui.components.library.views :as library]
|
||||
[airsonic-ui.components.podcast.views :as podcast]
|
||||
[airsonic-ui.components.search.views :as search]))
|
||||
|
|
@ -130,6 +131,7 @@
|
|||
[route-id :as route] @(subscribe [:routes/current-route])]
|
||||
[(add-classes :div route-id)
|
||||
[notification-list notifications]
|
||||
[keyboard/help-modal]
|
||||
(if is-booting?
|
||||
[:div.app-loading>div.loader]
|
||||
[:div
|
||||
|
|
|
|||
20
src/cljs/bulma/modal/events.cljs
Normal file
20
src/cljs/bulma/modal/events.cljs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
(ns bulma.modal.events
|
||||
(:require [re-frame.core :as rf]))
|
||||
|
||||
(defn show-modal [db [_ modal-id]]
|
||||
(assoc-in db [:bulma :visible-modal] modal-id))
|
||||
|
||||
(rf/reg-event-db ::show show-modal)
|
||||
|
||||
(defn hide-modal [db _]
|
||||
(update db :bulma dissoc :visible-modal))
|
||||
|
||||
(rf/reg-event-db ::hide hide-modal)
|
||||
|
||||
(defn toggle-modal [db [_ modal-id]]
|
||||
(let [visible-modal (get-in db [:bulma :visible-modal])]
|
||||
(if (= visible-modal modal-id)
|
||||
(hide-modal db [::hide])
|
||||
(show-modal db [::show modal-id]))))
|
||||
|
||||
(rf/reg-event-db ::toggle toggle-modal)
|
||||
19
src/cljs/bulma/modal/subs.cljs
Normal file
19
src/cljs/bulma/modal/subs.cljs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
(ns bulma.modal.subs
|
||||
(:require [re-frame.core :as rf]))
|
||||
|
||||
(defn visible-modal
|
||||
"Gives us the ID of the currently visible modal"
|
||||
[db _]
|
||||
(get-in db [:bulma :visible-modal]))
|
||||
|
||||
(rf/reg-sub ::visible-modal visible-modal)
|
||||
|
||||
(defn visible?
|
||||
"Predicate to check the visibility of a single modal"
|
||||
[visible-modal [_ modal-id]]
|
||||
(= visible-modal modal-id))
|
||||
|
||||
(rf/reg-sub
|
||||
::visible?
|
||||
:<- [::visible-modal]
|
||||
visible?)
|
||||
47
src/cljs/bulma/modal/views.cljs
Normal file
47
src/cljs/bulma/modal/views.cljs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
(ns bulma.modal.views
|
||||
(:require [re-frame.core :as rf]
|
||||
[bulma.modal.events :as ev]
|
||||
[bulma.modal.subs :as sub]))
|
||||
|
||||
(defn hide-modal [_]
|
||||
(rf/dispatch [::ev/hide]))
|
||||
|
||||
(defn modal
|
||||
"Generic modal; arguments:
|
||||
|
||||
options:
|
||||
{:has-hide-button? boolean
|
||||
:modal-id :some-identifier}
|
||||
|
||||
& children"
|
||||
[{:keys [has-hide-button? modal-id]} & children]
|
||||
{:pre [(some? modal-id)]}
|
||||
(let [visible? @(rf/subscribe [::sub/visible? modal-id])
|
||||
modal-tag (if visible? :div.modal.is-active :div.modal)]
|
||||
[modal-tag
|
||||
[:div.modal-background {:on-click hide-modal}]
|
||||
(into [:div.modal-content] children)
|
||||
(when has-hide-button?
|
||||
[:button.modal-hide.is-large {:aria-label "hide"
|
||||
:on-click hide-modal}])]))
|
||||
|
||||
(defn modal-card
|
||||
"A card modal that renders content on a background. Arguments:
|
||||
|
||||
options:
|
||||
{:title \"Title of the card\"
|
||||
:foot [[:div \"An array of hiccup elements\"]]
|
||||
:modal-id :some-identifier}
|
||||
|
||||
& children"
|
||||
[{:keys [title foot modal-id]} & children]
|
||||
[modal {:has-hide-button? (not (some? title))
|
||||
:modal-id modal-id}
|
||||
(when title
|
||||
[:div.modal-card-head
|
||||
[:p.modal-card-title title]
|
||||
[:button.delete {:aria-label "hide"
|
||||
:on-click hide-modal}]])
|
||||
(into [:section.modal-card-body] children)
|
||||
(when foot
|
||||
(into [:div.modal-card-foot] foot))])
|
||||
Loading…
Add table
Add a link
Reference in a new issue