From 525d1bca0e0fe724dc93b906521b916ed7fdfb6f Mon Sep 17 00:00:00 2001 From: Miguel Ángel Moreno Date: Sat, 23 Nov 2024 21:09:05 +0100 Subject: feat: add context-aware navigation elements --- src/frontend/tubo/components/navigation.cljs | 95 ------------- src/frontend/tubo/navigation/views.cljs | 193 +++++++++++++++++++++++++++ src/frontend/tubo/player/views.cljs | 5 - src/frontend/tubo/search/views.cljs | 43 ------ src/frontend/tubo/views.cljs | 2 +- 5 files changed, 194 insertions(+), 144 deletions(-) delete mode 100644 src/frontend/tubo/components/navigation.cljs create mode 100644 src/frontend/tubo/navigation/views.cljs (limited to 'src/frontend') diff --git a/src/frontend/tubo/components/navigation.cljs b/src/frontend/tubo/components/navigation.cljs deleted file mode 100644 index c5e58ed..0000000 --- a/src/frontend/tubo/components/navigation.cljs +++ /dev/null @@ -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]]]])) diff --git a/src/frontend/tubo/navigation/views.cljs b/src/frontend/tubo/navigation/views.cljs new file mode 100644 index 0000000..c8ef085 --- /dev/null +++ b/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]]])) diff --git a/src/frontend/tubo/player/views.cljs b/src/frontend/tubo/player/views.cljs index 6a87242..075f675 100644 --- a/src/frontend/tubo/player/views.cljs +++ b/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 diff --git a/src/frontend/tubo/search/views.cljs b/src/frontend/tubo/search/views.cljs index a121568..4fdc6d6 100644 --- a/src/frontend/tubo/search/views.cljs +++ b/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]) diff --git a/src/frontend/tubo/views.cljs b/src/frontend/tubo/views.cljs index 3a68b65..a0bc34c 100644 --- a/src/frontend/tubo/views.cljs +++ b/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])) -- cgit v1.2.3