aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMiguel Ángel Moreno <mail@migalmoreno.com>2024-01-22 00:42:28 +0100
committerMiguel Ángel Moreno <mail@migalmoreno.com>2024-01-22 00:42:28 +0100
commitec7d69bb7267f37e139d9300da782adbbca60ae6 (patch)
tree986f38d951b38d15ee5d7fc419da6a634e734b92 /src
parent50a5c82aafb57f586a1cf5c4460f4a0dab10a88d (diff)
feat(frontend): add common layout components for views
Diffstat (limited to 'src')
-rw-r--r--src/frontend/tubo/components/comments.cljs10
-rw-r--r--src/frontend/tubo/components/layout.cljs60
-rw-r--r--src/frontend/tubo/views.cljs16
-rw-r--r--src/frontend/tubo/views/bookmarks.cljs18
-rw-r--r--src/frontend/tubo/views/channel.cljs48
-rw-r--r--src/frontend/tubo/views/kiosk.cljs15
-rw-r--r--src/frontend/tubo/views/playlist.cljs44
-rw-r--r--src/frontend/tubo/views/search.cljs10
-rw-r--r--src/frontend/tubo/views/settings.cljs42
-rw-r--r--src/frontend/tubo/views/stream.cljs240
10 files changed, 259 insertions, 244 deletions
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])])]]]))