diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/frontend/tubo/channel/views.cljs | 6 | ||||
-rw-r--r-- | src/frontend/tubo/items/views.cljs | 182 | ||||
-rw-r--r-- | src/frontend/tubo/kiosks/views.cljs | 27 | ||||
-rw-r--r-- | src/frontend/tubo/playlist/views.cljs | 36 | ||||
-rw-r--r-- | src/frontend/tubo/search/views.cljs | 23 |
5 files changed, 182 insertions, 92 deletions
diff --git a/src/frontend/tubo/channel/views.cljs b/src/frontend/tubo/channel/views.cljs index 3ee4518..bd033f7 100644 --- a/src/frontend/tubo/channel/views.cljs +++ b/src/frontend/tubo/channel/views.cljs @@ -38,7 +38,8 @@ (defn channel [_] - (let [!show-description? (r/atom false)] + (let [!show-description? (r/atom false) + !layout (r/atom :list)] (fn [{{:keys [url]} :query-params}] (let [{:keys [banner description next-page related-streams] :as channel} @(rf/subscribe [:channel]) @@ -57,4 +58,5 @@ (when-not (empty? description) [layout/show-more-container @!show-description? description #(reset! !show-description? (not @!show-description?))]) - [items/related-streams related-streams next-page-url]]])))) + [items/layout-switcher !layout] + [items/related-streams related-streams next-page-url !layout]]])))) diff --git a/src/frontend/tubo/items/views.cljs b/src/frontend/tubo/items/views.cljs index 343b3c2..7470d65 100644 --- a/src/frontend/tubo/items/views.cljs +++ b/src/frontend/tubo/items/views.cljs @@ -59,73 +59,143 @@ [layout/popover-menu !menu-active? items :extra-classes [:pr-0 :pl-4] :menu-styles {:right "15px"}]))))) -(defn item-content +(defn grid-item-content [{:keys [url name uploader-url uploader-name subscriber-count view-count - stream-count verified?] + stream-count verified? thumbnail-url duration] :as item} route bookmarks] - [:div - (when name - [:div.flex.items-center.my-2 - [:a {:href route :title name} - [:h1.line-clamp-2.my-1 {:class "[overflow-wrap:anywhere]"} name]] - (when (and verified? (not uploader-url)) - [:i.fa-solid.fa-circle-check.pl-2])]) - [:div.flex.justify-between - [:div.flex.items-center.my-2 - (conj - (when uploader-url - [:a - {:href (rfe/href :channel-page nil {:url uploader-url}) - :title uploader-name - :key url}]) - [:h1.text-neutral-800.dark:text-gray-300.font-semibold.pr-2.line-clamp-1.break-all - {:class "[overflow-wrap:anywhere]" :title uploader-name :key url} - uploader-name]) - (when (and uploader-url verified?) - [:i.fa-solid.fa-circle-check])] - [item-popover item bookmarks]] - (when subscriber-count - [:div.flex.items-center - [:i.fa-solid.fa-users.text-xs] - [:span.mx-2 (utils/format-quantity subscriber-count)]]) - (when stream-count - [:div.flex.items-center - [:i.fa-solid.fa-video.text-xs] - [:span.mx-2 (utils/format-quantity stream-count)]]) - [:div.flex.my-1.justify-between - [:span (utils/format-date-ago (:upload-date item))] - (when view-count - [:div.flex.items-center.h-full.pl-2 - [:i.fa-solid.fa-eye.text-xs] - [:p.pl-1.5 (utils/format-quantity view-count)]])]]) + [:div.w-full + [:div.flex.flex-col.max-w-full.min-h-full.max-h-full + [layout/thumbnail thumbnail-url route name duration + :classes [:py-2 :h-44 "xs:h-28"] :rounded? true] + [:div + (when name + [:div.flex.items-center.my-2 + [:a {:href route :title name} + [:h1.line-clamp-2.my-1 {:class "[overflow-wrap:anywhere]"} name]] + (when (and verified? (not uploader-url)) + [:i.fa-solid.fa-circle-check.pl-2])]) + [:div.flex.justify-between + [:div.flex.items-center.my-2 + (conj + (when uploader-url + [:a + {:href (rfe/href :channel-page nil {:url uploader-url}) + :title uploader-name + :key url}]) + [:h1.text-neutral-800.dark:text-gray-300.font-semibold.pr-2.line-clamp-1.break-all + {:class "[overflow-wrap:anywhere]" :title uploader-name :key url} + uploader-name]) + (when (and uploader-url verified?) + [:i.fa-solid.fa-circle-check])] + [item-popover item bookmarks]] + (when subscriber-count + [:div.flex.items-center + [:i.fa-solid.fa-users.text-xs] + [:span.mx-2 (utils/format-quantity subscriber-count)]]) + (when stream-count + [:div.flex.items-center + [:i.fa-solid.fa-video.text-xs] + [:span.mx-2 (utils/format-quantity stream-count)]]) + [:div.flex.my-1.justify-between + [:span (utils/format-date-ago (:upload-date item))] + (when view-count + [:div.flex.items-center.h-full.pl-2 + [:i.fa-solid.fa-eye.text-xs] + [:p.pl-1.5 (utils/format-quantity view-count)]])]]]]) -(defn generic-item - [{:keys [url name thumbnail-url duration] :as item} bookmarks] - (let [item-url (case (:type item) - "stream" (rfe/href :stream-page nil {:url url}) - "channel" (rfe/href :channel-page nil {:url url}) - "playlist" (rfe/href :playlist-page nil {:url url}) - url)] - [:div.w-full - [:div.flex.flex-col.max-w-full.min-h-full.max-h-full - [layout/thumbnail thumbnail-url item-url name duration - :classes [:py-2 :h-44 "xs:h-28"] :rounded? true] - [item-content item item-url bookmarks]]])) +(defn list-item-content + [{:keys [url name uploader-url uploader-name subscriber-count view-count + stream-count verified? thumbnail-url duration] + :as item} route bookmarks] + [:div.w-full + [:div.flex.gap-x-5 + [layout/thumbnail thumbnail-url route name duration + :classes + [:py-2 :h-28 "xs:h-36" "min-w-[150px]" "max-w-[150px]" "sm:min-w-[250px]" + "sm:max-w-[250px]"] :rounded? + true] + [:div.flex-auto.mr-2 + (when name + [:div.flex.items-center.justify-between.mt-2 + [:a {:href route :title name} + [:h1.line-clamp-1.text-xl + {:class "[overflow-wrap:anywhere]"} + name + (when (and verified? (not uploader-url)) + [:i.fa-solid.fa-circle-check.pl-3.text-sm])]] + [item-popover item bookmarks]]) + [:div.flex.justify-between + [:div.flex.flex-col + [:div.flex.text-neutral-900.dark:text-neutral-400.text-sm.flex-col.xs:flex-row + (when view-count + [:<> + [:div.flex.items-center.h-full + [:p (str (utils/format-quantity view-count) " views")]] + [:span.px-2.hidden.xs:inline-block + {:dangerouslySetInnerHTML {:__html "•"}}]]) + [:span (utils/format-date-ago (:upload-date item))]] + [:div.my-2.xs:my-4.flex.gap-2.items-center + [layout/uploader-avatar item :classes ["w-6" "h-6"]] + (conj + (when uploader-url + [:a + {:href (rfe/href :channel-page nil {:url uploader-url}) + :title uploader-name + :key url}]) + [:h1.text-neutral-900.dark:text-neutral-400.font-bold.line-clamp-1.break-all.text-sm + {:class "[overflow-wrap:anywhere]" :title uploader-name :key url} + uploader-name]) + (when (and uploader-url verified?) + [:i.fa-solid.fa-circle-check.text-sm.text-neutral-400])]]] + [:div.flex.text-neutral-800.dark:text-gray-300.text-sm.flex-col.xs:flex-row + (when subscriber-count + [:<> + [:div.flex.items-center.h-full + [:p (str (utils/format-quantity subscriber-count) " subscribers")]] + [:span.px-2.hidden.xs:inline-block + {:dangerouslySetInnerHTML {:__html "•"}}]]) + (when stream-count + [:span (str (utils/format-quantity stream-count) " streams")])]]]]) (defn related-streams - [related-streams next-page-url] + [related-streams next-page-url !layout] (let [service-color @(rf/subscribe [:service-color]) pagination-loading? @(rf/subscribe [:show-pagination-loading]) - bookmarks @(rf/subscribe [:bookmarks])] - [:div.flex.flex-col.items-center.flex-auto.my-2.md:my-8 + bookmarks @(rf/subscribe [:bookmarks]) + item-url #(rfe/href (case (:type %) + "stream" :stream-page + "channel" :channel-page + "playlist" :playlist-page + (:url %)) + nil + {:url (:url %)})] + [:div.flex.flex-col.flex-auto.my-2.md:my-8 [modals/modal] (if (empty? related-streams) [:div.flex.items-center.flex-auto.flex-col.justify-center.gap-y-4 [:i.fa-solid.fa-ghost.text-3xl] [:p.text-lg "No available streams"]] - [:div.grid.w-full.gap-x-10.gap-y-6 - {:class "xs:grid-cols-[repeat(auto-fill,_minmax(165px,_1fr))]"} - (for [[i item] (map-indexed vector related-streams)] - ^{:key i} [generic-item item bookmarks])]) + (if (and !layout (= @!layout :grid)) + [:div.grid.w-full.gap-x-10.gap-y-6 + {:class "xs:grid-cols-[repeat(auto-fill,_minmax(165px,_1fr))]"} + (for [[i item] (map-indexed vector related-streams)] + ^{:key i} [grid-item-content item (item-url item) bookmarks])] + [:div.flex.flex-wrap.w-full.gap-x-10.gap-y-6 + (for [[i item] (map-indexed vector related-streams)] + ^{:key i} [list-item-content item (item-url item) bookmarks])])) (when (and pagination-loading? (seq next-page-url)) [layout/loading-icon service-color :text-md])])) + +(defn layout-switcher + [!layout] + [:div.gap-x-6.text-lg.flex.justify-end + [:button + {:on-click #(reset! !layout :list) + :title "Switch to list layout"} + [:i.fa-solid.fa-list + {:class (when-not (= @!layout :list) :text-neutral-500)}]] + [:button + {:on-click #(reset! !layout :grid) + :title "Switch to grid layout"} + [:i.fa-solid.fa-grip + {:class (when-not (= @!layout :grid) :text-neutral-500)}]]]) diff --git a/src/frontend/tubo/kiosks/views.cljs b/src/frontend/tubo/kiosks/views.cljs index e42719c..fd9b07e 100644 --- a/src/frontend/tubo/kiosks/views.cljs +++ b/src/frontend/tubo/kiosks/views.cljs @@ -1,5 +1,6 @@ (ns tubo.kiosks.views (:require + [reagent.core :as r] [re-frame.core :as rf] [reitit.frontend.easy :as rfe] [tubo.items.views :as items] @@ -32,14 +33,18 @@ kiosk]])]) (defn kiosk - [{{:keys [serviceId]} :query-params}] - (let [{:keys [id related-streams next-page]} - @(rf/subscribe [:kiosk]) - next-page-url (:url next-page) - service-id (or @(rf/subscribe [:service-id]) serviceId) - scrolled-to-bottom? @(rf/subscribe [:scrolled-to-bottom])] - (when scrolled-to-bottom? - (rf/dispatch [:kiosks/fetch-paginated service-id id next-page-url])) - [layout/content-container - [layout/content-header id] - [items/related-streams related-streams next-page-url]])) + [_] + (let [!layout (r/atom :list)] + (fn [{{:keys [serviceId]} :query-params}] + (let [{:keys [id related-streams next-page]} + @(rf/subscribe [:kiosk]) + next-page-url (:url next-page) + service-id (or @(rf/subscribe [:service-id]) serviceId) + scrolled-to-bottom? @(rf/subscribe [:scrolled-to-bottom])] + (when scrolled-to-bottom? + (rf/dispatch [:kiosks/fetch-paginated service-id id next-page-url])) + [layout/content-container + [layout/content-header + id + [items/layout-switcher !layout]] + [items/related-streams related-streams next-page-url !layout]])))) diff --git a/src/frontend/tubo/playlist/views.cljs b/src/frontend/tubo/playlist/views.cljs index ad2be19..065a3e0 100644 --- a/src/frontend/tubo/playlist/views.cljs +++ b/src/frontend/tubo/playlist/views.cljs @@ -7,10 +7,25 @@ [tubo.items.views :as items] [tubo.layout.views :as layout])) -(defn playlist - [{{:keys [url]} :query-params}] +(defn metadata-popover + [_] (let [!menu-active? (r/atom nil)] - (fn [] + (fn [{:keys [related-streams]}] + (when related-streams + [layout/popover-menu !menu-active? + [{:label "Add to queue" + :icon [:i.fa-solid.fa-headphones] + :on-click #(rf/dispatch [:queue/add-n related-streams true])} + {:label "Add to playlist" + :icon [:i.fa-solid.fa-plus] + :on-click #(rf/dispatch [:modals/open + [modals/add-to-bookmark + related-streams]])}]])))) + +(defn playlist + [_] + (let [!layout (r/atom :list)] + (fn [{{:keys [url]} :query-params}] (let [{:keys [name next-page uploader-name uploader-url related-streams stream-count] :as playlist} @@ -22,16 +37,8 @@ [layout/content-container [:div.flex.flex-col.justify-center [layout/content-header name - (when related-streams - [layout/popover-menu !menu-active? - [{:label "Add to queue" - :icon [:i.fa-solid.fa-headphones] - :on-click #(rf/dispatch [:queue/add-n related-streams true])} - {:label "Add to playlist" - :icon [:i.fa-solid.fa-plus] - :on-click #(rf/dispatch [:modals/open - [modals/add-to-bookmark - related-streams]])}]])] + [:div.hidden.lg:block + [metadata-popover playlist]]] [:div.flex.items-center.justify-between.my-4.gap-x-4 [:div.flex.items-center [layout/uploader-avatar playlist] @@ -40,4 +47,5 @@ :title uploader-name} uploader-name]] [:span.text-sm.whitespace-nowrap (str stream-count " streams")]]] - [items/related-streams related-streams next-page-url]])))) + [items/layout-switcher !layout] + [items/related-streams related-streams next-page-url !layout]])))) diff --git a/src/frontend/tubo/search/views.cljs b/src/frontend/tubo/search/views.cljs index e97a627..5896d01 100644 --- a/src/frontend/tubo/search/views.cljs +++ b/src/frontend/tubo/search/views.cljs @@ -1,16 +1,21 @@ (ns tubo.search.views (:require [re-frame.core :as rf] + [reagent.core :as r] [tubo.items.views :as items] [tubo.layout.views :as layout])) (defn search - [{{:keys [q serviceId]} :query-params}] - (let [{:keys [items next-page]} @(rf/subscribe [:search/results]) - next-page-url (:url next-page) - service-id (or @(rf/subscribe [:service-id]) serviceId) - scrolled-to-bottom? @(rf/subscribe [:scrolled-to-bottom])] - (when scrolled-to-bottom? - (rf/dispatch [:search/fetch-paginated q service-id next-page-url])) - [layout/content-container - [items/related-streams items next-page-url]])) + [_] + (let [!layout (r/atom :list)] + (fn [{{:keys [q serviceId]} :query-params}] + (let [{:keys [items next-page]} @(rf/subscribe [:search/results]) + next-page-url (:url next-page) + service-id (or @(rf/subscribe [:service-id]) + serviceId) + scrolled-to-bottom? @(rf/subscribe [:scrolled-to-bottom])] + (when scrolled-to-bottom? + (rf/dispatch [:search/fetch-paginated q service-id next-page-url])) + [layout/content-container + [items/layout-switcher !layout] + [items/related-streams items next-page-url !layout]])))) |