From ec7d69bb7267f37e139d9300da782adbbca60ae6 Mon Sep 17 00:00:00 2001 From: Miguel Ángel Moreno Date: Mon, 22 Jan 2024 00:42:28 +0100 Subject: feat(frontend): add common layout components for views --- src/frontend/tubo/components/comments.cljs | 10 +- src/frontend/tubo/components/layout.cljs | 60 ++++++++ src/frontend/tubo/views.cljs | 16 +- src/frontend/tubo/views/bookmarks.cljs | 18 +-- src/frontend/tubo/views/channel.cljs | 48 +++--- src/frontend/tubo/views/kiosk.cljs | 15 +- src/frontend/tubo/views/playlist.cljs | 44 ++---- src/frontend/tubo/views/search.cljs | 10 +- src/frontend/tubo/views/settings.cljs | 42 ++--- src/frontend/tubo/views/stream.cljs | 240 ++++++++++++++--------------- 10 files changed, 259 insertions(+), 244 deletions(-) create mode 100644 src/frontend/tubo/components/layout.cljs (limited to 'src/frontend') diff --git a/src/frontend/tubo/components/comments.cljs b/src/frontend/tubo/components/comments.cljs index 9886bdd..5990534 100644 --- a/src/frontend/tubo/components/comments.cljs +++ b/src/frontend/tubo/components/comments.cljs @@ -2,6 +2,7 @@ (:require [re-frame.core :as rf] [reitit.frontend.easy :as rfe] + [tubo.components.layout :as layout] [tubo.components.loading :as loading] [tubo.events :as events] [tubo.util :as util])) @@ -71,7 +72,8 @@ (when (:url next-page) (if pagination-loading? (loading/loading-icon service-color) - [:button.flex.items-center.justify-center - {:on-click #(rf/dispatch [::events/comments-pagination url (:url next-page)])} - [:i.fa-solid.fa-plus] - [:p.px-2 "Show more comments"]]))])) + [:div.flex.justify-center + [layout/secondary-button + "Show more comments" + #(rf/dispatch [::events/comments-pagination url (:url next-page)]) + "fa-solid fa-plus"]]))])) diff --git a/src/frontend/tubo/components/layout.cljs b/src/frontend/tubo/components/layout.cljs new file mode 100644 index 0000000..8a3a2c9 --- /dev/null +++ b/src/frontend/tubo/components/layout.cljs @@ -0,0 +1,60 @@ +(ns tubo.components.layout + (:require + [re-frame.core :as rf] + [tubo.components.loading :as loading])) + +(defn logo [] + [:img.mb-1 + {:src "/images/tubo.png" + :style {:maxHeight "25px" :maxWidth "40px"} + :title "Tubo"}]) + +(defn focus-overlay [on-click-cb active?] + [:div.w-full.fixed.min-h-screen.right-0.top-0.transition-all.delay-75.ease-in-out + {:class "bg-black/50" + :style {:visibility (when-not active? "hidden") + :opacity (if active? "1" "0")} + :on-click on-click-cb}]) + +(defn content-container + [& children] + (let [page-loading? @(rf/subscribe [:show-page-loading]) + service-color @(rf/subscribe [:service-color])] + [:div.flex.flex-col.flex-auto.items-center.px-5.py-4 + (if page-loading? + [loading/loading-icon service-color "text-5xl"] + [:div.flex.flex-col.flex-auto.w-full {:class "lg:w-4/5 xl:w-3/5"} + (map-indexed #(with-meta %2 {:key %1}) children)])])) + +(defn uploader-avatar + [source name & url] + (let [image [:img.rounded-full.object-cover.max-w-full.min-h-full {:src source :alt name}]] + (when source + [:div.relative.w-16.h-16.flex-auto.flex.items-center + (if url + [:a.flex-auto.flex {:href url :title name} image] + image)]))) + +(defn primary-button + [label on-click-cb left-icon right-icon] + [:button.dark:bg-white.bg-stone-800.px-4.rounded-3xl.py-1.outline-none.focus:ring-transparent.whitespace-nowrap + {:on-click on-click-cb} + (when left-icon + [:i.text-neutral-300.dark:text-neutral-800.text-sm + {:class left-icon}]) + [:span.mx-2.text-neutral-300.dark:text-neutral-900.font-bold.text-sm label] + (when right-icon + [:i.text-neutral-300.dark:text-neutral-800.text-sm + {:class right-icon}])]) + +(defn secondary-button + [label on-click-cb left-icon right-icon] + [:button.dark:bg-transparent.bg-neutral-100.px-4.rounded-3xl.py-1.border.border-neutral-700.dark:border-stone-700.outline-none.focus:ring-transparent.whitespace-nowrap + {:on-click on-click-cb} + (when left-icon + [:i.text-neutral-500.dark:text-white.text-sm + {:class left-icon}]) + [:span.mx-2.text-neutral-500.dark:text-white.font-bold.text-sm label] + (when right-icon + [:i.text-neutral-500.dark:text-white.text-sm + {:class right-icon}])]) diff --git a/src/frontend/tubo/views.cljs b/src/frontend/tubo/views.cljs index 28a3e82..07495b1 100644 --- a/src/frontend/tubo/views.cljs +++ b/src/frontend/tubo/views.cljs @@ -3,8 +3,9 @@ [reitit.frontend.easy :as rfe] [re-frame.core :as rf] [reagent.core :as r] - [tubo.components.navigation :as navigation] [tubo.components.audio-player :as player] + [tubo.components.layout :as layout] + [tubo.components.navigation :as navigation] [tubo.components.play-queue :as queue] [tubo.events :as events] [tubo.routes :as routes])) @@ -15,18 +16,11 @@ (defn mobile-nav [show-mobile-nav? service-id service-color services available-kiosks] [:<> - [:div.w-full.fixed.min-h-screen.right-0.top-0.transition-all.delay-75.ease-in-out - {:class "bg-black/50" - :style {:visibility (when-not show-mobile-nav? "hidden") - :opacity (if show-mobile-nav? "1" "0")} - :on-click #(rf/dispatch [::events/toggle-mobile-nav])}] - [:div.items-center.fixed.overflow-x-hidden.min-h-screen.w-60.top-0.ease-in-out.delay-75.bg-white.dark:bg-neutral-900 + [layout/focus-overlay #(rf/dispatch [::events/toggle-mobile-nav]) show-mobile-nav?] + [:div.fixed.overflow-x-hidden.min-h-screen.w-60.top-0.ease-in-out.delay-75.bg-white.dark:bg-neutral-900 {:class (str "transition-[right] " (if show-mobile-nav? "right-0" "right-[-245px]"))} [:div.flex.justify-center.py-8.items-center.text-white {:style {:background service-color}} - [:img.mb-1 - {:src "/images/tubo.png" - :style {:maxHeight "25px" :maxWidth "40px"} - :title "Tubo"}] + [layout/logo] [:h3.text-3xl.font-bold.px-4.font-roboto "Tubo"]] [:div.relative.flex.flex-col.items-center-justify-center.text-white {:style {:background service-color}} [:div.w-full.box-border.z-10 diff --git a/src/frontend/tubo/views/bookmarks.cljs b/src/frontend/tubo/views/bookmarks.cljs index 24c356c..7df71ce 100644 --- a/src/frontend/tubo/views/bookmarks.cljs +++ b/src/frontend/tubo/views/bookmarks.cljs @@ -2,20 +2,16 @@ (:require [re-frame.core :as rf] [tubo.components.items :as items] - [tubo.components.navigation :as navigation] + [tubo.components.layout :as layout] [tubo.events :as events])) (defn bookmarks-page [] (let [service-color @(rf/subscribe [:service-color]) bookmarks @(rf/subscribe [:bookmarks])] - [:div.flex.flex-col.items-center.px-5.py-2.flex-auto - [:div.flex.flex-col.flex-auto.w-full {:class "ml:w-4/5 xl:w-3/5"} - [navigation/back-button service-color] - [:div.flex.justify-between - [:h1.text-2xl.font-bold.py-6 "Bookmarks"] - [:button - {:on-click #(rf/dispatch [::events/enqueue-related-streams bookmarks service-color])} - [:i.fa-solid.fa-headphones] - [:span.ml-2.text-neutral-600.dark:text-neutral-300 "Background"]]] - [items/related-streams bookmarks]]])) + [layout/content-container + [:div.flex.justify-between.mt-6 + [:h1.text-3xl.font-nunito-semibold "Bookmarks"] + [layout/primary-button "Enqueue" + #(rf/dispatch [::events/enqueue-related-streams bookmarks service-color]) "fa-solid fa-headphones"]] + [items/related-streams bookmarks]])) diff --git a/src/frontend/tubo/views/channel.cljs b/src/frontend/tubo/views/channel.cljs index fd7965c..643acaf 100644 --- a/src/frontend/tubo/views/channel.cljs +++ b/src/frontend/tubo/views/channel.cljs @@ -2,8 +2,8 @@ (:require [re-frame.core :as rf] [tubo.components.items :as items] + [tubo.components.layout :as layout] [tubo.components.loading :as loading] - [tubo.components.navigation :as navigation] [tubo.events :as events])) (defn channel @@ -17,30 +17,22 @@ scrolled-to-bottom? @(rf/subscribe [:scrolled-to-bottom])] (when scrolled-to-bottom? (rf/dispatch [::events/channel-pagination url next-page-url])) - [:div.flex.flex-col.items-center.px-5.py-2.flex-auto - (if page-loading? - [loading/loading-icon service-color "text-5xl"] - [:div.flex.flex-col.flex-auto {:class "ml:w-4/5 xl:w-3/5"} - [navigation/back-button service-color] - (when banner - [:div.flex.justify-center - [:img {:src banner}]]) - [:div.flex.items-center.justify-between - [:div.flex.items-center.my-4.mx-2 - (when avatar - [:div.relative.w-16.h-16 - [:img.rounded-full.object-cover.max-w-full.min-h-full {:src avatar :alt name}]]) - [:div.m-4 - [:h1.text-xl name] - (when subscriber-count - [:div.flex.my-2.items-center - [:i.fa-solid.fa-users.text-xs] - [:span.mx-2 (.toLocaleString subscriber-count)]])]] - [:div.whitespace-nowrap.ml-4 - [:button - {:on-click #(rf/dispatch [::events/enqueue-related-streams related-streams service-color])} - [:i.fa-solid.fa-headphones.mx-3] - [:span.text-neutral-600.dark:text-neutral-300 "Background"]]]] - [:div.my-2 - [:p description]] - [items/related-streams related-streams next-page-url]])])) + [layout/content-container + (when banner + [:div.flex.justify-center + [:img.min-w-full {:src banner}]]) + [:div.flex.items-center.justify-between + [:div.flex.items-center.my-4.mx-2 + [layout/uploader-avatar avatar name] + [:div.m-4 + [:h1.text-xl name] + (when subscriber-count + [:div.flex.my-2.items-center + [:i.fa-solid.fa-users.text-xs] + [:span.mx-2 (.toLocaleString subscriber-count)]])]] + [layout/primary-button "Enqueue" + #(rf/dispatch [::events/enqueue-related-streams related-streams service-color]) + "fa-solid fa-headphones"]] + [:div.my-2 + [:p description]] + [items/related-streams related-streams next-page-url]])) diff --git a/src/frontend/tubo/views/kiosk.cljs b/src/frontend/tubo/views/kiosk.cljs index 884cf27..e3bf812 100644 --- a/src/frontend/tubo/views/kiosk.cljs +++ b/src/frontend/tubo/views/kiosk.cljs @@ -2,8 +2,8 @@ (:require [re-frame.core :as rf] [tubo.components.items :as items] + [tubo.components.layout :as layout] [tubo.components.loading :as loading] - [tubo.components.navigation :as navigation] [tubo.events :as events])) (defn kiosk @@ -11,15 +11,10 @@ (let [{:keys [id url related-streams next-page]} @(rf/subscribe [:kiosk]) next-page-url (:url next-page) service-color @(rf/subscribe [:service-color]) - page-loading? @(rf/subscribe [:show-page-loading]) scrolled-to-bottom? @(rf/subscribe [:scrolled-to-bottom])] (when scrolled-to-bottom? (rf/dispatch [::events/kiosk-pagination serviceId id next-page-url])) - [:div.flex.flex-col.items-center.px-5.py-2.flex-auto - (if page-loading? - [loading/loading-icon service-color "text-5xl"] - [:div.flex.flex-col.flex-auto.w-full {:class "lg:w-4/5 xl:w-3/5"} - [:div.flex.justify-center.items-center.my-4.mx-2 - [:div.m-4 - [:h1.text-2xl id]]] - [items/related-streams related-streams next-page-url]])])) + [layout/content-container + [:div.flex.items-center.mt-6.mx-2 + [:h1.text-3xl.font-nunito-semibold id]] + [items/related-streams related-streams next-page-url]])) diff --git a/src/frontend/tubo/views/playlist.cljs b/src/frontend/tubo/views/playlist.cljs index d8e8d49..b42fb6b 100644 --- a/src/frontend/tubo/views/playlist.cljs +++ b/src/frontend/tubo/views/playlist.cljs @@ -3,8 +3,8 @@ [re-frame.core :as rf] [reitit.frontend.easy :as rfe] [tubo.components.items :as items] + [tubo.components.layout :as layout] [tubo.components.loading :as loading] - [tubo.components.navigation :as navigation] [tubo.events :as events])) (defn playlist @@ -14,34 +14,20 @@ next-page related-streams]} @(rf/subscribe [:playlist]) next-page-url (:url next-page) service-color @(rf/subscribe [:service-color]) - page-loading? @(rf/subscribe [:show-page-loading]) scrolled-to-bottom? @(rf/subscribe [:scrolled-to-bottom])] (when scrolled-to-bottom? (rf/dispatch [::events/playlist-pagination url next-page-url])) - [:div.flex.flex-col.items-center.px-5.pt-4.flex-auto - (if page-loading? - [loading/loading-icon service-color "text-5xl"] - [:div.flex.flex-col.flex-auto.w-full {:class "ml:w-4/5 xl:w-3/5"} - [navigation/back-button service-color] - (when banner-url - [:div - [:img {:src banner-url}]]) - [:div.flex.flex-col.justify-center.my-4.mx-2 - [:div.flex.justify-between.items-center.mb-4 - [:div - [:h1.text-2xl.font-bold.line-clamp-1 {:title name} name]] - [:div.whitespace-nowrap.ml-4 - [:button - {:on-click #(rf/dispatch [::events/enqueue-related-streams related-streams service-color])} - [:i.mx-3.fa-solid.fa-headphones] - [:span.text-neutral-600.dark:text-neutral-300 "Background"]]]] - [:div.flex.items-center.justify-between - [:div.flex.items-center.my-4.mr-2 - [:div.flex.items-center.py-3.pr-3.box-border.h-12 - [:div.w-12 - [:a {:href (rfe/href :tubo.routes/channel nil {:url uploader-url}) :title uploader-name} - [:img.rounded-full.object-cover.min-h-full.min-w-full {:src uploader-avatar :alt uploader-name}]]]] - [:a {:href (rfe/href :tubo.routes/channel nil {:url uploader-url}) :title uploader-name} - uploader-name]] - [:span.ml-2.whitespace-nowrap (str stream-count " streams")]]] - [items/related-streams related-streams next-page-url]])])) + [layout/content-container + [:div.flex.flex-col.justify-center.my-4.mx-2 + [:div.flex.justify-between.items-center.mb-4 + [:h1.text-2xl.font-bold.line-clamp-1.pr-2 {:title name} name] + [layout/primary-button "Enqueue" + #(rf/dispatch [::events/enqueue-related-streams related-streams service-color]) "fa-solid fa-headphones"]] + [:div.flex.items-center.justify-between + [:div.flex.items-center.my-4.mr-2 + [layout/uploader-avatar uploader-avatar uploader-name uploader-url] + [:div + [:a {:href (rfe/href :tubo.routes/channel nil {:url uploader-url}) :title uploader-name} + uploader-name]]] + [:span.ml-2.whitespace-nowrap (str stream-count " streams")]]] + [items/related-streams related-streams next-page-url]])) diff --git a/src/frontend/tubo/views/search.cljs b/src/frontend/tubo/views/search.cljs index a121be2..551fbfb 100644 --- a/src/frontend/tubo/views/search.cljs +++ b/src/frontend/tubo/views/search.cljs @@ -4,6 +4,7 @@ [reitit.frontend.easy :as rfe] [tubo.components.items :as items] [tubo.components.loading :as loading] + [tubo.components.layout :as layout] [tubo.events :as events])) (defn search @@ -12,13 +13,8 @@ next-page-url (:url next-page) services @(rf/subscribe [:services]) service-id @(rf/subscribe [:service-id]) - service-color @(rf/subscribe [:service-color]) - page-loading? @(rf/subscribe [:show-page-loading]) scrolled-to-bottom? @(rf/subscribe [:scrolled-to-bottom])] (when scrolled-to-bottom? (rf/dispatch [::events/search-pagination q serviceId next-page-url])) - [:div.flex.flex-col.items-center.flex-auto - (if page-loading? - [loading/loading-icon service-color "text-5xl"] - [:div.flex.flex-col.flex-auto.w-full {:class "lg:w-4/5 xl:w-3/5"} - [items/related-streams items next-page-url]])])) + [layout/content-container + [items/related-streams items next-page-url]])) diff --git a/src/frontend/tubo/views/settings.cljs b/src/frontend/tubo/views/settings.cljs index c39d35e..8d929d5 100644 --- a/src/frontend/tubo/views/settings.cljs +++ b/src/frontend/tubo/views/settings.cljs @@ -1,8 +1,8 @@ (ns tubo.views.settings (:require [re-frame.core :as rf] - [tubo.events :as events] - [tubo.components.navigation :as navigation])) + [tubo.components.layout :as layout] + [tubo.events :as events])) (defn boolean-input [label key value] @@ -10,26 +10,28 @@ [:label label] [:input {:type "checkbox" - :checked value - :value value + :checked value + :value value :on-change #(rf/dispatch [::events/change-setting key (not value)])}]]) +(defn select-input + [label key value options] + [:div.w-full.flex.justify-between.items-center.py-2 + [:label label] + [:select.focus:ring-transparent.bg-transparent.font-bold.font-nunito + {:value value + :on-change #(rf/dispatch [::events/change-setting key (.. % -target -value)])} + (for [[i option] (map-indexed vector options)] + [:option.dark:bg-neutral-900.border-none {:value option :key i} option])]]) + (defn settings-page [] (let [{:keys [current-theme themes show-comments show-related show-description]} @(rf/subscribe [:settings]) - service-color @(rf/subscribe [:service-color])] - [:div.flex.flex-col.items-center.px-5.py-2.flex-auto - [:div.flex.flex-col.flex-auto.w-full {:class "ml:w-4/5 xl:w-3/5"} - [navigation/back-button service-color] - [:h1.text-2xl.font-bold.py-6 "Settings"] - [:form.flex.flex-wrap - [:div.w-full.flex.justify-between.items-center.py-2 - [:label "Theme"] - [:select.focus:ring-transparent.bg-transparent.font-bold.font-nunito - {:value current-theme - :on-change #(rf/dispatch [::events/change-setting :current-theme (.. % -target -value)])} - (for [[i theme] (map-indexed vector themes)] - [:option.dark:bg-neutral-900.border-none {:value theme :key i} theme])]] - [boolean-input "Show description?" :show-description show-description] - [boolean-input "Show comments?" :show-comments show-comments] - [boolean-input "Show related videos?" :show-related show-related]]]])) + service-color @(rf/subscribe [:service-color])] + [layout/content-container + [:h1.text-2xl.font-bold.py-6 "Settings"] + [:form.flex.flex-wrap + [select-input "Theme" :current-theme current-theme themes] + [boolean-input "Show description?" :show-description show-description] + [boolean-input "Show comments?" :show-comments show-comments] + [boolean-input "Show related videos?" :show-related show-related]]])) diff --git a/src/frontend/tubo/views/stream.cljs b/src/frontend/tubo/views/stream.cljs index e13fa39..4f9bf4f 100644 --- a/src/frontend/tubo/views/stream.cljs +++ b/src/frontend/tubo/views/stream.cljs @@ -4,8 +4,8 @@ [reitit.frontend.easy :as rfe] [tubo.events :as events] [tubo.components.items :as items] + [tubo.components.layout :as layout] [tubo.components.loading :as loading] - [tubo.components.navigation :as navigation] [tubo.components.comments :as comments] [tubo.components.video-player :as player] [tubo.util :as util])) @@ -26,126 +26,118 @@ page-loading? @(rf/subscribe [:show-page-loading]) service-color @(rf/subscribe [:service-color]) bookmarks @(rf/subscribe [:bookmarks])] - [:div.flex.flex-col.items-center.justify-center.dark:text-white.flex-auto - (if page-loading? - [loading/loading-icon service-color "text-5xl"] - [:div.w-full.pb-4.relative.w-full {:class "ml:w-4/5 xl:w-3/5"} - [navigation/back-button service-color] - [:div.flex.justify-center.relative - {:class "h-[300px] ml:h-[450px] lg:h-[600px]"} - (when stream-format - [player/player {"sources" [{"src" content "type" "video/mp4"} - {"src" content "type" "video/webm"}] - "poster" thumbnail-url - "controls" true - "responsive" true - "fill" true} - content])] - [:div.px-4.ml:p-0.overflow-x-hidden - [:div.flex.flex.w-full.my-4.justify-center - [:button.sm:px-2.py-1.text-sm.sm:text-base.text-neutral-600.dark:text-neutral-300 - {:on-click #(rf/dispatch [::events/switch-to-audio-player stream service-color])} - [:i.fa-solid.fa-headphones] - [:span.mx-3 "Background"]] - (if (some #(= (:url %) url) bookmarks) - [:button.sm:px-2.py-1.text-sm.sm:text-base.text-neutral-600.dark:text-neutral-300 - {:on-click #(rf/dispatch [::events/remove-from-bookmarks stream])} - [:i.fa-solid.fa-bookmark] - [:span.mx-3 "Bookmarked"]] - [:button.sm:px-2.py-1.text-sm.sm:text-base.text-neutral-600.dark:text-neutral-300 - {:on-click #(rf/dispatch [::events/add-to-bookmarks stream])} - [:i.fa-regular.fa-bookmark] - [:span.mx-3 "Bookmark"]]) - [:button.sm:px-2.py-1.text-sm.sm:text-base.text-neutral-600.dark:text-neutral-300 - [:a.block.sm:inline-block {:href url} - [:i.fa-solid.fa-external-link-alt]] - [:span.mx-3 "Original"]] - (when stream-format - [:div.relative.flex.flex-col.items-center.justify-center.text-neutral-600.dark:text-neutral-300 - [:select.border-none.focus:ring-transparent.dark:bg-blend-color-dodge.pr-8.w-full.text-ellipsis.text-sm.sm:text-base - {:on-change #(rf/dispatch [::events/change-stream-format (.. % -target -value)]) - :value id - :style {:background "transparent"}} - (when available-streams - (for [[i {:keys [id format resolution averageBitrate]}] (map-indexed vector available-streams)] - [:option.dark:bg-neutral-900.border-none {:value id :key i} - (str (or resolution "audio-only") " " format (when-not resolution (str " " averageBitrate "kbit/s")))]))] - [:div.flex.absolute.min-h-full.top-0.right-4.items-center.justify-end - [:i.fa-solid.fa-caret-down]]])] - [:div.flex.flex-col - [:div.min-w-full.pb-3 - [:h1.text-2xl.font-extrabold.line-clamp-1 name]] - [:div.flex.justify-between.py-2 - [:div.flex.items-center - (when uploader-avatar - [:div.relative.w-16.h-16 - [:a {:href (rfe/href :tubo.routes/channel nil {:url uploader-url}) :title uploader-name} - [:img.rounded-full.object-cover.max-w-full.min-h-full {:src uploader-avatar :alt uploader-name}]]]) - [:div.mx-3 - [:a {:href (rfe/href :tubo.routes/channel nil {:url uploader-url})} uploader-name] - (when subscriber-count - [:div.flex.my-2.items-center - [:i.fa-solid.fa-users.text-xs] - [:p.mx-2 (util/format-quantity subscriber-count)]])]] - [:div.flex.flex-col.items-end.flex-auto - (when view-count - [:div.sm:text-base.text-sm.mb-1 - [:i.fa-solid.fa-eye] - [:span.ml-2 (.toLocaleString view-count)]]) - [:div.flex - (when like-count - [:div.items-center.sm:text-base.text-sm - [:i.fa-solid.fa-thumbs-up] - [:span.ml-2 (.toLocaleString like-count)]]) - (when dislike-count - [:div.ml-2.items-center.sm:text-base.text-sm - [:i.fa-solid.fa-thumbs-down] - [:span.ml-2 dislike-count]])] - (when upload-date - [:div.sm:text-base.text-sm.mt-1.whitespace-nowrap - [:i.fa-solid.fa-calendar.mx-2] - [:span - (-> upload-date - js/Date.parse - js/Date. - .toDateString)]])]] - (when (and show-description? description) - [:div.py-3.flex.flex-wrap.min-w-full - [:div {:dangerouslySetInnerHTML {:__html description} - :class (when (not show-description) "line-clamp-2")}] - [:div.flex.justify-center.font-bold.min-w-full.pt-4.cursor-pointer - [:button - {:on-click #(rf/dispatch [::events/toggle-stream-layout :show-description])} - (if (not show-description) "Show More" "Show Less")]]]) - (when (and comments-page (not (empty? (:comments comments-page))) show-comments?) - [:div.py-6 - [:div.flex.items-center - [:i.fa-solid.fa-comments] - [:p.px-2.py-4 "Comments"] - (if show-comments - [:i.fa-solid.fa-chevron-up.cursor-pointer - {:on-click #(rf/dispatch [::events/toggle-stream-layout :show-comments])}] - [:i.fa-solid.fa-chevron-down.cursor-pointer - {:on-click #(if (or show-comments comments-page) - (rf/dispatch [::events/toggle-stream-layout :show-comments]) - (rf/dispatch [::events/get-comments url]))}])] - [:div - (if show-comments-loading - [loading/loading-icon service-color "text-2xl"] - (when (and show-comments comments-page) - [comments/comments comments-page uploader-name uploader-avatar url]))]]) - (when (and show-related? (not (empty? related-streams))) - [:div.py-6 - [:div.flex.justify-between - [:div.flex.items-center.text-sm.sm:text-base - [:i.fa-solid.fa-list] - [:h1.px-2.text-lg.bold "Suggested"] - [:i.fa-solid.fa-chevron-up.cursor-pointer - {:class (if (not show-related) "fa-chevron-up" "fa-chevron-down") - :on-click #(rf/dispatch [::events/toggle-stream-layout :show-related])}]] - [:button - {:on-click #(rf/dispatch [::events/enqueue-related-streams related-streams service-color])} - [:i.mx-2.fa-solid.fa-headphones] - [:span.text-neutral-600.dark:text-neutral-300 "Background"]]] - (when (not show-related) - [items/related-streams related-streams nil])])]]])])) + [layout/content-container + [:div.flex.justify-center.relative + {:class "h-[300px] lg:h-[450px] lg:h-[600px]"} + (when stream-format + [player/player {"sources" [{"src" content "type" "video/mp4"} + {"src" content "type" "video/webm"}] + "poster" thumbnail-url + "controls" true + "responsive" true + "fill" true} + content])] + [:div.px-4.lg:p-0.overflow-x-hidden + [:div.flex.flex.w-full.my-4.justify-center + [:button.sm:px-2.py-1.text-sm.sm:text-base.text-neutral-600.dark:text-neutral-300 + {:on-click #(rf/dispatch [::events/switch-to-audio-player stream service-color])} + [:i.fa-solid.fa-headphones] + [:span.mx-3 "Background"]] + (if (some #(= (:url %) url) bookmarks) + [:button.sm:px-2.py-1.text-sm.sm:text-base.text-neutral-600.dark:text-neutral-300 + {:on-click #(rf/dispatch [::events/remove-from-bookmarks stream])} + [:i.fa-solid.fa-bookmark] + [:span.mx-3 "Bookmarked"]] + [:button.sm:px-2.py-1.text-sm.sm:text-base.text-neutral-600.dark:text-neutral-300 + {:on-click #(rf/dispatch [::events/add-to-bookmarks stream])} + [:i.fa-regular.fa-bookmark] + [:span.mx-3 "Bookmark"]]) + [:button.sm:px-2.py-1.text-sm.sm:text-base.text-neutral-600.dark:text-neutral-300 + [:a.block.sm:inline-block {:href url} + [:i.fa-solid.fa-external-link-alt] + [:span.mx-3 "Original"]]] + (when stream-format + [:div.relative.flex.flex-col.items-center.justify-center.text-neutral-600.dark:text-neutral-300 + [:select.border-none.focus:ring-transparent.dark:bg-blend-color-dodge.pr-8.w-full.text-ellipsis.text-sm.sm:text-base + {:on-change #(rf/dispatch [::events/change-stream-format (.. % -target -value)]) + :value id + :style {:background "transparent"}} + (when available-streams + (for [[i {:keys [id format resolution averageBitrate]}] (map-indexed vector available-streams)] + [:option.dark:bg-neutral-900.border-none {:value id :key i} + (str (or resolution "audio-only") " " format (when-not resolution (str " " averageBitrate "kbit/s")))]))] + [:div.flex.absolute.min-h-full.top-0.right-4.items-center.justify-end + [:i.fa-solid.fa-caret-down]]])] + [:div.flex.flex-col + [:div.min-w-full.pb-3 + [:h1.text-2xl.font-extrabold.line-clamp-1 name]] + [:div.flex.justify-between.py-2 + [:div.flex.items-center + [layout/uploader-avatar uploader-avatar uploader-name + (rfe/href :tubo.routes/channel nil {:url uploader-url})] + [:div.mx-3 + [:a.line-clamp-1 {:href (rfe/href :tubo.routes/channel nil {:url uploader-url})} uploader-name] + (when subscriber-count + [:div.flex.my-2.items-center + [:i.fa-solid.fa-users.text-xs] + [:p.mx-2 (util/format-quantity subscriber-count)]])]] + [:div.flex.flex-col.items-end.flex-auto + (when view-count + [:div.sm:text-base.text-sm.mb-1 + [:i.fa-solid.fa-eye] + [:span.ml-2 (.toLocaleString view-count)]]) + [:div.flex + (when like-count + [:div.items-center.sm:text-base.text-sm + [:i.fa-solid.fa-thumbs-up] + [:span.ml-2 (.toLocaleString like-count)]]) + (when dislike-count + [:div.ml-2.items-center.sm:text-base.text-sm + [:i.fa-solid.fa-thumbs-down] + [:span.ml-2 dislike-count]])] + (when upload-date + [:div.sm:text-base.text-sm.mt-1.whitespace-nowrap + [:i.fa-solid.fa-calendar.mx-2] + [:span + (-> upload-date + js/Date.parse + js/Date. + .toDateString)]])]] + (when (and show-description? description) + [:div.py-3.flex.flex-wrap.min-w-full + [:div {:dangerouslySetInnerHTML {:__html description} + :class (when (not show-description) "line-clamp-2")}] + [:div.flex.justify-center.font-bold.min-w-full.pt-4.cursor-pointer + [layout/secondary-button + (if (not show-description) "Show More" "Show Less") + #(rf/dispatch [::events/toggle-stream-layout :show-description])]]]) + (when (and comments-page (not (empty? (:comments comments-page))) show-comments?) + [:div + [:div.flex.items-center + [:i.fa-solid.fa-comments.w-6] + [:h2.mx-4.text-lg "Comments"] + (if show-comments + [:i.fa-solid.fa-chevron-up.cursor-pointer.text-sm + {:on-click #(rf/dispatch [::events/toggle-stream-layout :show-comments])}] + [:i.fa-solid.fa-chevron-down.cursor-pointer.text-sm.ml-2 + {:on-click #(if (or show-comments comments-page) + (rf/dispatch [::events/toggle-stream-layout :show-comments]) + (rf/dispatch [::events/get-comments url]))}])] + (if show-comments-loading + [loading/loading-icon service-color "text-2xl"] + (when (and show-comments comments-page) + [comments/comments comments-page uploader-name uploader-avatar url]))]) + (when (and show-related? (not (empty? related-streams))) + [:div.pt-2 + [:div.flex.justify-between + [:div.flex.items-center.text-sm.sm:text-base + [:i.fa-solid.fa-list.w-6] + [:h2.mx-4.text-lg "Suggested"] + [:i.fa-solid.fa-chevron-up.cursor-pointer.text-sm + {:class (if (not show-related) "fa-chevron-up" "fa-chevron-down") + :on-click #(rf/dispatch [::events/toggle-stream-layout :show-related])}]] + [layout/primary-button "Enqueue" + #(rf/dispatch [::events/enqueue-related-streams related-streams service-color]) + "fa-solid fa-headphones"]] + (when (not show-related) + [items/related-streams related-streams nil])])]]])) -- cgit v1.2.3