Browse Source

feat: add context-aware navigation elements

Miguel Ángel Moreno 4 months ago
parent
commit
525d1bca0e

+ 0 - 95
src/frontend/tubo/components/navigation.cljs

@@ -1,95 +0,0 @@
-(ns tubo.components.navigation
-  (:require
-   [re-frame.core :as rf]
-   [reitit.frontend.easy :as rfe]
-   [tubo.components.layout :as layout]
-   [tubo.kiosks.views :as kiosks]
-   [tubo.search.views :as search]
-   [tubo.services.views :as services]))
-
-(defn mobile-nav-item
-  [route icon label & {:keys [new-tab? active?]}]
-  [:li.px-5.py-2
-   [:a.flex {:href route :target (when new-tab? "_blank")}
-    [:div.w-6.flex.justify-center.items-center.mr-4
-     (conj icon {:class ["text-neutral-600" "dark:text-neutral-300"]})]
-    [:span {:class (when active? "font-bold")} label]]])
-
-(defn mobile-nav
-  [show-mobile-nav? service-color services available-kiosks &
-   {:keys [service-id] :as kiosk-args}]
-  [:<>
-   [layout/focus-overlay #(rf/dispatch [:toggle-mobile-nav]) show-mobile-nav?]
-   [:div.fixed.overflow-x-hidden.min-h-screen.w-60.top-0.transition-all.ease-in-out.delay-75.bg-white.dark:bg-neutral-900.z-20
-    {:class [(if show-mobile-nav? "left-0" "left-[-245px]")]}
-    [:div.flex.justify-center.py-4.items-center.text-white
-     {:style {:background service-color}}
-     [layout/logo :height 75 :width 75]
-     [:h3.text-3xl.font-bold "Tubo"]]
-    [services/services-dropdown services service-id service-color]
-    [:div.relative.py-4
-     [:ul.flex.flex-col
-      (for [[i kiosk] (map-indexed vector available-kiosks)]
-        ^{:key i}
-        [mobile-nav-item
-         (rfe/href :kiosk-page nil {:serviceId service-id :kioskId kiosk})
-         [:i.fa-solid.fa-fire] kiosk
-         :active? (kiosks/kiosk-active? (assoc kiosk-args :kiosk kiosk))])]]
-    [:div.relative.dark:border-neutral-800.border-gray-300.pt-4
-     {:class "border-t-[1px]"}
-     [:ul.flex.flex-col
-      [mobile-nav-item (rfe/href :bookmarks-page) [:i.fa-solid.fa-bookmark]
-       "Bookmarks"]
-      [mobile-nav-item (rfe/href :settings-page) [:i.fa-solid.fa-cog]
-       "Settings"]]]]])
-
-(defn navbar
-  [{{:keys [kioskId]} :query-params path :path}]
-  (let [service-id                               @(rf/subscribe [:service-id])
-        service-color                            @(rf/subscribe
-                                                   [:service-color])
-        services                                 @(rf/subscribe [:services])
-        show-mobile-nav?                         @(rf/subscribe
-                                                   [:show-mobile-nav])
-        show-search-form?                        @(rf/subscribe
-                                                   [:show-search-form])
-        {:keys [default-service]}                @(rf/subscribe [:settings])
-        {:keys [available-kiosks default-kiosk]} @(rf/subscribe [:kiosks])]
-    [:nav.sticky.flex.items-center.px-2.h-14.top-0.z-20
-     {:style {:background service-color}}
-     [:div.flex.flex-auto.items-center
-      [:button.ml-2.invisible.absolute.lg:visible.lg:relative
-       [:a.font-bold {:href (rfe/href :homepage)}
-        [layout/logo :height 35 :width 35]]]
-      [:button.text-white.mx-3.lg:hidden
-       {:on-click #(rf/dispatch [:toggle-mobile-nav])}
-       [:i.fa-solid.fa-bars]]
-      [search/search-form]
-      [:div.flex.flex-auto.justify-end.lg:justify-between
-       {:class (when show-search-form? :hidden)}
-       [:div.hidden.lg:flex
-        [services/services-dropdown services service-id service-color]
-        [kiosks/kiosks-menu
-         :kiosks available-kiosks
-         :service-id service-id
-         :kiosk-id kioskId
-         :default-service default-service
-         :default-kiosk default-kiosk
-         :path path]]
-       [:div.flex.items-center.text-white.justify-end
-        (when-not show-search-form?
-          [:button.mx-3
-           {:on-click #(rf/dispatch [:search/show-form true])}
-           [:i.fa-solid.fa-search]])
-        [:a.mx-3.hidden.lg:block
-         {:href (rfe/href :settings-page)}
-         [:i.fa-solid.fa-cog]]
-        [:a.mx-3.hidden.lg:block
-         {:href (rfe/href :bookmarks-page)}
-         [:i.fa-solid.fa-bookmark]]]
-       [mobile-nav show-mobile-nav? service-color services available-kiosks
-        :kiosk-id kioskId
-        :service-id service-id
-        :default-service default-service
-        :default-kiosk default-kiosk
-        :path path]]]]))

+ 193 - 0
src/frontend/tubo/navigation/views.cljs

@@ -0,0 +1,193 @@
+(ns tubo.navigation.views
+  (:require
+   [re-frame.core :as rf]
+   [reagent.core :as r]
+   [reitit.frontend.easy :as rfe]
+   [tubo.bookmarks.modals :as modals]
+   [tubo.components.layout :as layout]
+   [tubo.kiosks.views :as kiosks]
+   [tubo.services.views :as services]
+   [tubo.utils :as utils]
+   [tubo.stream.views :as stream]
+   [tubo.channel.views :as channel]))
+
+(defn search-form
+  []
+  (let [!query (r/atom "")
+        !input (r/atom nil)]
+    (fn []
+      (let [search-query      @(rf/subscribe [:search-query])
+            show-search-form? @(rf/subscribe [:show-search-form])
+            service-id        @(rf/subscribe [:service-id])]
+        [:form.relative.text-white.flex.items-center.flex-auto.lg:flex-1
+         {:class     (when-not show-search-form? "hidden")
+          :on-submit #(do (.preventDefault %)
+                          (when-not (empty? @!query)
+                            (rf/dispatch [:navigate
+                                          {:name   :search-page
+                                           :params {}
+                                           :query  {:q         search-query
+                                                    :serviceId service-id}}])))}
+         [:div.flex.justify-center.flex-auto.lg:flex-1
+          [:button.mx-2
+           {:on-click #(rf/dispatch [:search/show-form false])}
+           [:i.fa-solid.fa-arrow-left]]
+          [:input.w-full.lg:w-96.bg-transparent.py-2.pl-0.pr-6.mx-2.border-none.focus:ring-transparent.placeholder-white
+           {:type          "text"
+            :ref           #(do (reset! !input %)
+                                (when %
+                                  (.focus %)))
+            :default-value @!query
+            :on-change     #(let [input (.. % -target -value)]
+                              (when-not (empty? input)
+                                (rf/dispatch [:search/change-query input]))
+                              (reset! !query input))
+            :placeholder   "Search"}]
+          [:button.mx-4 {:type "submit"} [:i.fa-solid.fa-search]]
+          [:button.mx-4.text-xs.absolute.right-8.top-3
+           {:on-click #(when @!input
+                         (set! (.-value @!input) "")
+                         (reset! !query "")
+                         (.focus @!input))
+            :class    (when (empty? @!query) :invisible)}
+           [:i.fa-solid.fa-circle-xmark]]]]))))
+
+(defn mobile-nav-item
+  [route icon label & {:keys [new-tab? active?]}]
+  [:li.px-5.py-2
+   [:a.flex {:href route :target (when new-tab? "_blank")}
+    [:div.w-6.flex.justify-center.items-center.mr-4
+     (conj icon {:class ["text-neutral-600" "dark:text-neutral-300"]})]
+    [:span {:class (when active? "font-bold")} label]]])
+
+(defn mobile-nav
+  [show-mobile-nav? service-color services available-kiosks &
+   {:keys [service-id] :as kiosk-args}]
+  [:<>
+   [layout/focus-overlay #(rf/dispatch [:toggle-mobile-nav]) show-mobile-nav?]
+   [:div.fixed.overflow-x-hidden.min-h-screen.w-60.top-0.transition-all.ease-in-out.delay-75.bg-white.dark:bg-neutral-900.z-20
+    {:class [(if show-mobile-nav? "left-0" "left-[-245px]")]}
+    [:div.flex.justify-center.py-4.items-center.text-white
+     {:style {:background service-color}}
+     [layout/logo :height 75 :width 75]
+     [:h3.text-3xl.font-bold "Tubo"]]
+    [services/services-dropdown services service-id service-color]
+    [:div.relative.py-4
+     [:ul.flex.flex-col
+      (for [[i kiosk] (map-indexed vector available-kiosks)]
+        ^{:key i}
+        [mobile-nav-item
+         (rfe/href :kiosk-page nil {:serviceId service-id :kioskId kiosk})
+         [:i.fa-solid.fa-fire] kiosk
+         :active? (kiosks/kiosk-active? (assoc kiosk-args :kiosk kiosk))])]]
+    [:div.relative.dark:border-neutral-800.border-gray-300.pt-4
+     {:class "border-t-[1px]"}
+     [:ul.flex.flex-col
+      [mobile-nav-item (rfe/href :bookmarks-page) [:i.fa-solid.fa-bookmark]
+       "Bookmarks"]
+      [mobile-nav-item (rfe/href :settings-page) [:i.fa-solid.fa-cog]
+       "Settings"]]]]])
+
+(defn nav-left-content
+  [title]
+  (let [show-search-form? @(rf/subscribe [:show-search-form])
+        show-queue?       @(rf/subscribe [:show-queue])
+        show-main-player? @(rf/subscribe [:main-player/show])]
+    [:div.flex.items-center.gap-x-4
+     (when-not (or show-queue? show-main-player?)
+       [:button.ml-2.invisible.absolute.lg:visible.lg:relative
+        [:a.font-bold {:href (rfe/href :homepage)}
+         [layout/logo :height 35 :width 35]]])
+     (when (and show-queue? (not show-search-form?))
+       [:button.mx-2
+        {:on-click #(rf/dispatch [:queue/show
+                                  false])}
+        [:i.fa-solid.fa-arrow-left]])
+     (when (and show-main-player? (not show-search-form?))
+       [:button.mx-2
+        {:on-click #(rf/dispatch [:player/switch-from-main nil])}
+        [:i.fa-solid.fa-arrow-left]])
+     (when-not (or show-queue? show-main-player? show-search-form?)
+       [:button.text-white.mx-3.lg:hidden
+        {:on-click #(rf/dispatch
+                     [:toggle-mobile-nav])}
+        [:i.fa-solid.fa-bars]])
+     (when-not (or show-queue? show-main-player? show-search-form?)
+       [:h1.text-lg.sm:text-xl.font-bold.line-clamp-1.lg:hidden title])
+     (when (and show-queue? (not show-search-form?))
+       [:h1.text-lg.sm:text-xl.font-bold.line-clamp-1
+        "Play Queue"])
+     (when (and show-main-player? (not show-search-form?))
+       [:h1.text-lg.sm:text-xl.font-bold.line-clamp-1
+        "Main Player"])]))
+
+(defn nav-right-content
+  [{{:keys [kioskId]} :query-params path :path :as match}]
+  (let [show-search-form? @(rf/subscribe [:show-search-form])
+        show-main-player? @(rf/subscribe [:main-player/show])
+        show-queue?       @(rf/subscribe [:show-queue])
+        service-id        @(rf/subscribe [:service-id])
+        service-color     @(rf/subscribe [:service-color])
+        services          @(rf/subscribe [:services])
+        settings          @(rf/subscribe [:settings])
+        kiosks            @(rf/subscribe [:kiosks])]
+    [:div.flex.flex-auto.justify-end.lg:justify-between
+     {:class (when show-search-form? :hidden)}
+     (when-not (or show-queue? show-main-player?)
+       [:div.hidden.lg:flex
+        [services/services-dropdown services service-id service-color]
+        [kiosks/kiosks-menu
+         :kiosks (:available-kiosks kiosks)
+         :service-id service-id
+         :kiosk-id kioskId
+         :default-service (:default-service settings)
+         :default-kiosk (:default-kiosk kiosks)
+         :path path]])
+     [:div.flex.flex-auto.items-center.text-white.justify-end
+      [:button.mx-3
+       {:on-click #(rf/dispatch [:search/show-form true])}
+       [:i.fa-solid.fa-search]]
+      (when-not (or show-queue? show-main-player?)
+        [:div.lg:hidden
+         (case (-> match
+                   :data
+                   :name)
+           :channel-page [channel/metadata-popover
+                          @(rf/subscribe [:channel])]
+           :stream-page  [stream/metadata-popover @(rf/subscribe [:stream])]
+           [:<>])])
+      [:a.mx-3.hidden.lg:block
+       {:href (rfe/href :settings-page)}
+       [:i.fa-solid.fa-cog]]
+      [:a.mx-3.hidden.lg:block
+       {:href (rfe/href :bookmarks-page)}
+       [:i.fa-solid.fa-bookmark]]]]))
+
+(defn navbar
+  [{{:keys [kioskId]} :query-params path :path :as match}]
+  (let [service-id       @(rf/subscribe [:service-id])
+        service-color    @(rf/subscribe [:service-color])
+        services         @(rf/subscribe [:services])
+        show-mobile-nav? @(rf/subscribe [:show-mobile-nav])
+        settings         @(rf/subscribe [:settings])
+        kiosks           @(rf/subscribe [:kiosks])]
+    [:nav.sticky.flex.items-center.px-2.h-14.top-0.z-20
+     {:style {:background service-color}}
+     [:div.flex.flex-auto.items-center
+      [mobile-nav show-mobile-nav? service-color services
+       (:available-kiosks kiosks)
+       :kiosk-id kioskId
+       :service-id service-id
+       :default-service (:default-service settings)
+       :default-kiosk (:default-kiosk kiosks)
+       :path path]
+      [nav-left-content
+       (case (-> match
+                 :data
+                 :name)
+         :channel-page (:name @(rf/subscribe [:channel]))
+         :kiosk-page   (:id @(rf/subscribe [:kiosk]))
+         :stream-page  (:name @(rf/subscribe [:stream]))
+         nil)]
+      [search-form]
+      [nav-right-content match]]]))

+ 0 - 5
src/frontend/tubo/player/views.cljs

@@ -177,11 +177,6 @@
     [:div.fixed.w-full.bg-neutral-100.dark:bg-neutral-900.overflow-auto.z-10.transition-all.ease-in-out
      {:class ["h-[calc(100%-56px)]"
               (if show-player? "translate-y-0" "translate-y-full")]}
-     [:div.sticky.z-10.right-0.top-0
-      [:button.absolute.text-white.m-8.text-2xl.z-10.right-0
-       {:on-click #(rf/dispatch [:player/switch-from-main nil])}
-       [:i.fa-solid.fa-close
-        {:class "drop-shadow-[0_0_1px_#000]"}]]]
      (when (and show-player? stream)
        [:div
         [:div.flex.flex-col.items-center.w-full.xl:py-6

+ 0 - 43
src/frontend/tubo/search/views.cljs

@@ -1,52 +1,9 @@
 (ns tubo.search.views
   (:require
    [re-frame.core :as rf]
-   [reagent.core :as r]
    [tubo.components.items :as items]
    [tubo.components.layout :as layout]))
 
-(defn search-form
-  []
-  (let [!query (r/atom "")
-        !input (r/atom nil)]
-    (fn []
-      (let [search-query      @(rf/subscribe [:search-query])
-            show-search-form? @(rf/subscribe [:show-search-form])
-            service-id        @(rf/subscribe [:service-id])]
-        [:form.relative.flex.items-center.text-white.ml-4
-         {:class     (when-not show-search-form? "hidden")
-          :on-submit #(do (.preventDefault %)
-                          (when-not (empty? @!query)
-                            (rf/dispatch [:navigate
-                                          {:name   :search-page
-                                           :params {}
-                                           :query  {:q         search-query
-                                                    :serviceId service-id}}])))}
-         [:div.flex
-          [:button.mx-2
-           {:on-click #(rf/dispatch [:search/show-form false]) :type "button"}
-           [:i.fa-solid.fa-arrow-left]]
-          [:input.w-full.sm:w-96.bg-transparent.py-2.pl-0.pr-6.mx-2.border-none.focus:ring-transparent.placeholder-white
-           {:type          "text"
-            :ref           #(do (reset! !input %)
-                                (when %
-                                  (.focus %)))
-            :default-value @!query
-            :on-change     #(let [input (.. % -target -value)]
-                              (when-not (empty? input)
-                                (rf/dispatch [:search/change-query input]))
-                              (reset! !query input))
-            :placeholder   "Search"}]
-          [:button.mx-4 {:type "submit"} [:i.fa-solid.fa-search]]
-          [:button.mx-4.text-xs.absolute.right-8.top-3
-           {:type     "button"
-            :on-click #(when @!input
-                         (set! (.-value @!input) "")
-                         (reset! !query "")
-                         (.focus @!input))
-            :class    (when (empty? @!query) :invisible)}
-           [:i.fa-solid.fa-circle-xmark]]]]))))
-
 (defn search
   [{{:keys [q serviceId]} :query-params}]
   (let [{:keys [items next-page]} @(rf/subscribe [:search-results])

+ 1 - 1
src/frontend/tubo/views.cljs

@@ -1,7 +1,7 @@
 (ns tubo.views
   (:require
    [re-frame.core :as rf]
-   [tubo.components.navigation :as navigation]
+   [tubo.navigation.views :as navigation]
    [tubo.notifications.views :as notifications]
    [tubo.player.views :as player]
    [tubo.queue.views :as queue]))