aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMiguel Ángel Moreno <mail@migalmoreno.com>2024-04-19 15:57:29 +0200
committerMiguel Ángel Moreno <mail@migalmoreno.com>2024-04-19 16:35:54 +0200
commit472e4adf02453f69a428fb80bb5fcdae1390925d (patch)
tree59ddf393f32219c8bdad9c9f85955e446e734820 /src
parentd8bf35e4b9c1fa3e9a0ac6fd3aad37a054e36c09 (diff)
feat: refine media queue component and handlers
Diffstat (limited to 'src')
-rw-r--r--src/frontend/tubo/components/audio_player.cljs11
-rw-r--r--src/frontend/tubo/components/layout.cljs4
-rw-r--r--src/frontend/tubo/components/play_queue.cljs188
-rw-r--r--src/frontend/tubo/events.cljs12
4 files changed, 128 insertions, 87 deletions
diff --git a/src/frontend/tubo/components/audio_player.cljs b/src/frontend/tubo/components/audio_player.cljs
index 149c963..f2e4943 100644
--- a/src/frontend/tubo/components/audio_player.cljs
+++ b/src/frontend/tubo/components/audio_player.cljs
@@ -95,14 +95,16 @@
muted? @(rf/subscribe [:muted])
bookmarks @(rf/subscribe [:bookmarks])
!player @(rf/subscribe [:player])
+ media-queue-pos @(rf/subscribe [:media-queue-pos])
{:keys [theme]} @(rf/subscribe [:settings])
service-color (and service-id (utils/get-service-color service-id))
bg-color (str "rgba(" (if (= theme "dark") "23, 23, 23" "255, 255, 255") ", 0.95)")
liked? (some #(= (:url %) url) (-> bookmarks first :items))]
(when show-audio-player?
- [:div.sticky.bottom-0.z-10.p-3.absolute.box-border.m-0
+ [:div.sticky.bottom-0.z-10.p-3.absolute.box-border.m-0.transition-all.ease-in.delay-0
{:style
- {:display (when show-media-queue? "none")
+ {:visibility (when show-media-queue? "hidden")
+ :opacity (if show-media-queue? 0 1)
:background-image (str "linear-gradient(0deg, " bg-color "," bg-color "), url(\"" thumbnail-url "\")")
:backgroundSize "cover"
:backgroundPosition "center"
@@ -120,7 +122,7 @@
[main-controls service-color]
[:div.flex.lg:justify-end.lg:flex-1
[player/volume-slider !player volume-level muted? service-color]
- [player/button [:i.fa-solid.fa-list] #(rf/dispatch [::events/toggle-media-queue])
+ [player/button [:i.fa-solid.fa-list] #(rf/dispatch [::events/show-media-queue true])
:show-on-mobile? true
:extra-classes "pl-4 pr-3"]
[layout/popover-menu !menu-active?
@@ -134,6 +136,9 @@
:icon [:i.fa-solid.fa-plus]
:on-click #(rf/dispatch [::events/add-bookmark-list-modal
[bookmarks/add-to-bookmark-list-modal current-stream]])}
+ {:label "Remove from queue"
+ :icon [:i.fa-solid.fa-trash]
+ :on-click #(rf/dispatch [::events/remove-from-media-queue media-queue-pos])}
{:label "Close player"
:icon [:i.fa-solid.fa-close]
:on-click #(rf/dispatch [::events/dispose-audio-player])}]
diff --git a/src/frontend/tubo/components/layout.cljs b/src/frontend/tubo/components/layout.cljs
index 910d9fd..7fc8853 100644
--- a/src/frontend/tubo/components/layout.cljs
+++ b/src/frontend/tubo/components/layout.cljs
@@ -5,12 +5,12 @@
[tubo.utils :as utils]))
(defn thumbnail
- [thumbnail-url route name duration & {:keys [classes] :or {classes "h-44 xs:h-28"}}]
+ [thumbnail-url route name duration & {:keys [classes rounded?] :or {classes "h-44 xs:h-28" rounded? true}}]
[:div.flex.py-2.box-border {:class classes}
[:div.relative.min-w-full
[:a.absolute.min-w-full.min-h-full.z-10 {:href route :title name}]
(if thumbnail-url
- [:img.rounded.object-cover.min-h-full.max-h-full.min-w-full {:src thumbnail-url}]
+ [:img.object-cover.min-h-full.max-h-full.min-w-full {:src thumbnail-url :class (when rounded? "rounded")}]
[:div.bg-gray-300.flex.min-h-full.min-w-full.justify-center.items-center.rounded
[:i.fa-solid.fa-image.text-3xl.text-white]])
(when duration
diff --git a/src/frontend/tubo/components/play_queue.cljs b/src/frontend/tubo/components/play_queue.cljs
index 4b93ce4..ddb81d5 100644
--- a/src/frontend/tubo/components/play_queue.cljs
+++ b/src/frontend/tubo/components/play_queue.cljs
@@ -1,7 +1,9 @@
(ns tubo.components.play-queue
(:require
+ [reagent.core :as r]
[re-frame.core :as rf]
[reitit.frontend.easy :as rfe]
+ [tubo.components.modals.bookmarks :as bookmarks]
[tubo.components.items :as items]
[tubo.components.layout :as layout]
[tubo.components.player :as player]
@@ -9,19 +11,48 @@
[tubo.utils :as utils]))
(defn play-queue-item
- [{:keys [service-id uploader-name uploader-url name duration
- stream url service-color thumbnail-url]} media-queue-pos i]
- [:div.flex.w-full.h-24.rounded.cursor-pointer.px-2.my-1
- {:class (when (= i media-queue-pos) "bg-[#f0f0f0] dark:bg-stone-800")
- :on-click #(rf/dispatch [::events/change-media-queue-pos i])}
- [:div.w-56
- [layout/thumbnail thumbnail-url nil name duration {:classes "h-24"}]]
- [:div.flex.flex-col.px-4.py-2.w-full
- [:h1.line-clamp-1 name]
- [:div.text-neutral-600.dark:text-neutral-300.text-sm.flex.flex-col.xs:flex-row
- [:span.line-clamp-1 uploader-name]
- [:span.px-2.hidden.xs:inline-block {:dangerouslySetInnerHTML {:__html "&bull;"}}]
- [:span (utils/get-service-name service-id)]]]])
+ [item media-queue-pos i bookmarks]
+ (let [!menu-active? (r/atom false)]
+ (fn [{:keys [service-id uploader-name uploader-url name duration
+ stream url thumbnail-url] :as item}
+ media-queue-pos i bookmarks]
+ (let [liked? (some #(= (:url %) url) (-> bookmarks first :items))
+ media-queue-pos @(rf/subscribe [:media-queue-pos])
+ media-queue @(rf/subscribe [:media-queue])]
+ [:div.relative.w-full
+ {:ref #(when (and (> (count media-queue) 0) (= media-queue-pos i))
+ (rf/dispatch [::events/scroll-into-view %]))}
+ [:div.flex.cursor-pointer.py-2
+ {:class (when (= i media-queue-pos) "bg-[#f0f0f0] dark:bg-stone-800")
+ :on-click #(rf/dispatch [::events/change-media-queue-pos i])}
+ [:div.flex.items-center.justify-center.min-w-20.w-20.xs:min-w-28.xs:w-28
+ [:span.font-bold.text-neutral-400.text-sm
+ (if (= i media-queue-pos) [:i.fa-solid.fa-play] (inc i))]]
+ [:div.w-36
+ [layout/thumbnail thumbnail-url nil name duration :classes "h-16 !p-0" :rounded? false]]
+ [:div.flex.flex-col.pl-4.pr-12.w-full
+ [:h1.line-clamp-1 {:title name} name]
+ [:div.text-neutral-600.dark:text-neutral-300.text-sm.flex.flex-col.xs:flex-row
+ [:span.line-clamp-1 {:title uploader-name} uploader-name]
+ [:span.px-2.hidden.xs:inline-block {:dangerouslySetInnerHTML {:__html "&bull;"}}]
+ [:span (utils/get-service-name service-id)]]]]
+ [:div.absolute.right-0.top-0.min-h-full.flex.items-center
+ [layout/popover-menu !menu-active?
+ [{:label (if liked? "Remove favorite" "Favorite")
+ :icon [:i.fa-solid.fa-heart (when liked? {:style {:color (utils/get-service-color service-id)}})]
+ :on-click #(rf/dispatch [(if liked? ::events/remove-from-likes ::events/add-to-likes) item])}
+ {:label "Play radio"
+ :icon [:i.fa-solid.fa-tower-cell]
+ :on-click #(rf/dispatch [::events/start-stream-radio item])}
+ {:label "Add to playlist"
+ :icon [:i.fa-solid.fa-plus]
+ :on-click #(rf/dispatch [::events/add-bookmark-list-modal
+ [bookmarks/add-to-bookmark-list-modal item]])}
+ {:label "Remove from queue"
+ :icon [:i.fa-solid.fa-trash]
+ :on-click #(rf/dispatch [::events/remove-from-media-queue i])}]
+ :menu-styles {:right "40px"}
+ :extra-classes "px-7 py-2"]]]))))
(defn queue
[]
@@ -36,66 +67,71 @@
!elapsed-time @(rf/subscribe [:elapsed-time])
!player @(rf/subscribe [:player])
loop-playback @(rf/subscribe [:loop-playback])
- player-ready? (and @!player (> (.-readyState @!player) 0))]
- (when (and show-media-queue media-queue)
- [:div.fixed.flex.flex-col.items-center.px-5.py-2.min-w-full.w-full.z-10
- {:style {:minHeight "calc(100dvh - 56px)" :height "calc(100dvh - 56px)"}
- :class "dark:bg-neutral-900/90 bg-white/90 backdrop-blur"}
- [layout/focus-overlay #(rf/dispatch [::events/toggle-media-queue]) show-media-queue true]
- [:div.z-20.w-full.flex.flex-col.flex-auto.h-full
- {:class "lg:w-4/5 xl:w-3/5"}
- [:div.flex.justify-between.items-center.shrink-0
- [:h1.text-2xl.font-bold.py-6 "Play Queue"]
- [:button.p-2.text-xl
- {:on-click #(rf/dispatch [::events/toggle-media-queue])}
- [:i.fa-solid.fa-close]]]
- [:div.flex.flex-col.pr-2.overflow-y-auto.flex-auto
- (for [[i item] (map-indexed vector media-queue)]
- ^{:key i} [play-queue-item item media-queue-pos i])]
- [:div.flex.flex-col.py-4.shrink-0
- [:div.flex.flex-col.w-full.py-2
- [:a.text-md.line-clamp-1
- {:href (rfe/href :tubo.routes/stream nil {:url url})} name]
- [:a.text-sm.pt-2.text-neutral-600.dark:text-neutral-300.line-clamp-1
- {:href (rfe/href :tubo.routes/channel nil {:url uploader-url})} uploader-name]]
- [:div.flex.flex-auto.py-2.w-full.items-center
- [:span.mr-2 (if @!elapsed-time (utils/format-duration @!elapsed-time) "00:00")]
- [player/time-slider !player !elapsed-time service-color]
- [:span.ml-2 (if player-ready? (utils/format-duration (.-duration @!player)) "00:00")]]
- [:div.flex.justify-center.items-center
- [player/loop-button loop-playback service-color true]
- [player/button
- [:i.fa-solid.fa-backward-step]
- #(when (and media-queue (not= media-queue-pos 0))
- (rf/dispatch [::events/change-media-queue-pos
- (- media-queue-pos 1)]))
- :disabled? (not (and media-queue (not= media-queue-pos 0)))
- :extra-classes "text-xl"
- :show-on-mobile? true]
- [player/button
- [:i.fa-solid.fa-backward]
- #(set! (.-currentTime @!player) (- @!elapsed-time 5))
- :extra-classes "text-xl"
- :show-on-mobile? true]
- [player/button
- (if (or loading? (not @!player))
- [layout/loading-icon service-color "text-3xl"]
- (if paused?
- [:i.fa-solid.fa-play]
- [:i.fa-solid.fa-pause]))
- #(rf/dispatch [::events/set-player-paused (not paused?)])
- :extra-classes "text-3xl"
- :show-on-mobile? true]
- [player/button
- [:i.fa-solid.fa-forward]
- #(set! (.-currentTime @!player) (+ @!elapsed-time 5))
- :extra-classes "text-xl"
- :show-on-mobile? true]
- [player/button
- [:i.fa-solid.fa-forward-step]
- #(when (and media-queue (< (+ media-queue-pos 1) (count media-queue)))
- (rf/dispatch [::events/change-media-queue-pos
- (+ media-queue-pos 1)]))
- :disabled? (not (and media-queue (< (+ media-queue-pos 1) (count media-queue))))
- :extra-classes "text-xl"
- :show-on-mobile? true]]]]])))
+ player-ready? @(rf/subscribe [:player-ready])
+ bookmarks @(rf/subscribe [:bookmarks])]
+ [:div.fixed.flex.flex-col.items-center.min-w-full.w-full.z-10.backdrop-blur
+ {:style {:minHeight "calc(100dvh - 56px)"
+ :height "calc(100dvh - 56px)"
+ :visibility (when-not show-media-queue "hidden")
+ :opacity (if show-media-queue 1 0)}
+ :class "dark:bg-neutral-900/90 bg-white/90 backdrop-blur"}
+ [layout/focus-overlay #(rf/dispatch [::events/show-media-queue false]) show-media-queue true]
+ [:div.z-20.w-full.flex.flex-col.flex-auto.h-full.lg:pt-5
+ {:class "lg:w-4/5 xl:w-3/5"}
+ [:div.flex.flex-col.overflow-y-auto.flex-auto.gap-y-1
+ (for [[i item] (map-indexed vector media-queue)]
+ ^{:key i} [play-queue-item item media-queue-pos i bookmarks])]
+ [:div.flex.flex-col.py-4.shrink-0.px-5
+ [:div.flex.flex-col.w-full.py-2
+ [:a.text-md.line-clamp-1
+ {:href (rfe/href :tubo.routes/stream nil {:url url})
+ :title name}
+ name]
+ [:a.text-sm.pt-2.text-neutral-600.dark:text-neutral-300.line-clamp-1
+ {:href (rfe/href :tubo.routes/channel nil {:url uploader-url})
+ :title uploader-name}
+ uploader-name]]
+ [:div.flex.flex-auto.py-2.w-full.items-center.text-sm
+ [:span.mr-4 (if (and @!elapsed-time @!player) (utils/format-duration @!elapsed-time) "00:00")]
+ [player/time-slider !player !elapsed-time service-color]
+ [:span.ml-4 (if (and player-ready? @!player) (utils/format-duration (.-duration @!player)) "00:00")]]
+ [:div.flex.justify-center.items-center
+ [player/loop-button loop-playback service-color true]
+ [player/button
+ [:i.fa-solid.fa-backward-step]
+ #(when (and media-queue (not= media-queue-pos 0))
+ (rf/dispatch [::events/change-media-queue-pos
+ (- media-queue-pos 1)]))
+ :disabled? (not (and media-queue (not= media-queue-pos 0)))
+ :extra-classes "text-xl"
+ :show-on-mobile? true]
+ [player/button
+ [:i.fa-solid.fa-backward]
+ #(set! (.-currentTime @!player) (- @!elapsed-time 5))
+ :extra-classes "text-xl"
+ :show-on-mobile? true]
+ [player/button
+ (if (or loading? (not player-ready?))
+ [layout/loading-icon service-color "text-3xl"]
+ (if paused?
+ [:i.fa-solid.fa-play]
+ [:i.fa-solid.fa-pause]))
+ #(rf/dispatch [::events/set-player-paused (not paused?)])
+ :extra-classes "text-3xl"
+ :show-on-mobile? true]
+ [player/button
+ [:i.fa-solid.fa-forward]
+ #(set! (.-currentTime @!player) (+ @!elapsed-time 5))
+ :extra-classes "text-xl"
+ :show-on-mobile? true]
+ [player/button
+ [:i.fa-solid.fa-forward-step]
+ #(when (and media-queue (< (+ media-queue-pos 1) (count media-queue)))
+ (rf/dispatch [::events/change-media-queue-pos
+ (+ media-queue-pos 1)]))
+ :disabled? (not (and media-queue (< (+ media-queue-pos 1) (count media-queue))))
+ :extra-classes "text-xl"
+ :show-on-mobile? true]
+ [player/button [:i.fa-solid.fa-list] #(rf/dispatch [::events/show-media-queue false])
+ :show-on-mobile? true
+ :extra-classes "pl-4 pr-3"]]]]]))
diff --git a/src/frontend/tubo/events.cljs b/src/frontend/tubo/events.cljs
index da0657b..401de69 100644
--- a/src/frontend/tubo/events.cljs
+++ b/src/frontend/tubo/events.cljs
@@ -194,10 +194,10 @@
::body-overflow! (not (:show-mobile-nav db))}))
(rf/reg-event-fx
- ::toggle-media-queue
- (fn [{:keys [db]} _]
- {:db (assoc db :show-media-queue (not (:show-media-queue db)))
- ::body-overflow! (not (:show-media-queue db))}))
+ ::show-media-queue
+ (fn [{:keys [db]} [_ show?]]
+ {:db (assoc db :show-media-queue show?)
+ ::body-overflow! show?}))
(rf/reg-event-fx
::scroll-into-view
@@ -239,12 +239,12 @@
match (assoc new-match :controllers controllers)]
{:db (-> db
(assoc :current-match match)
- (assoc :show-media-queue false)
(assoc :show-mobile-nav false)
(assoc :show-pagination-loading false))
::scroll-to-top nil
::body-overflow! false
- :fx [[:dispatch [::get-services]]
+ :fx [[:dispatch [::show-media-queue false]]
+ [:dispatch [::get-services]]
[:dispatch [::get-kiosks (:service-id db)]]]})))
(rf/reg-event-fx