aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMiguel Ángel Moreno <mail@migalmoreno.com>2023-10-30 01:02:17 +0100
committerMiguel Ángel Moreno <mail@migalmoreno.com>2023-10-30 01:02:17 +0100
commit7ddf157e974e9df2dccb2c45ca31c15e4b3cee74 (patch)
tree734bd5e86fb207e456febe6725362fb931ee8227 /src
parent10393b747485e128b2002c4eed37162ecc0b4825 (diff)
feat: add initial support for bookmarks
Diffstat (limited to 'src')
-rw-r--r--src/backend/tubo/routes.clj1
-rw-r--r--src/frontend/tubo/components/items.cljs117
-rw-r--r--src/frontend/tubo/events.cljs19
-rw-r--r--src/frontend/tubo/routes.cljs5
-rw-r--r--src/frontend/tubo/subs.cljs5
-rw-r--r--src/frontend/tubo/views.cljs11
-rw-r--r--src/frontend/tubo/views/bookmarks.cljs21
-rw-r--r--src/frontend/tubo/views/stream.cljs16
8 files changed, 130 insertions, 65 deletions
diff --git a/src/backend/tubo/routes.clj b/src/backend/tubo/routes.clj
index e22f35f..2bf56f8 100644
--- a/src/backend/tubo/routes.clj
+++ b/src/backend/tubo/routes.clj
@@ -20,6 +20,7 @@
["/playlist" handler/index]
["/kiosk" handler/index]
["/settings" handler/index]
+ ["/bookmarks" handler/index]
["/api"
["/services"
["" {:get handler/services}]
diff --git a/src/frontend/tubo/components/items.cljs b/src/frontend/tubo/components/items.cljs
index 1881d06..6d2663e 100644
--- a/src/frontend/tubo/components/items.cljs
+++ b/src/frontend/tubo/components/items.cljs
@@ -7,8 +7,8 @@
[tubo.util :as util]))
(defn thumbnail
- [thumbnail-url route url name duration]
- [:div.flex.py-2.box-border.h-44.xs:h-28
+ [thumbnail-url route url name duration & {:keys [classes] :or {classes "h-44 xs:h-28"}}]
+ [:div.flex.py-2.box-border {:class classes}
[:div.relative.min-w-full
[:a.absolute.min-w-full.min-h-full.z-10 {:href route :title name}]
[:img.rounded.object-cover.min-h-full.max-h-full.min-w-full {:src thumbnail-url}]
@@ -23,88 +23,89 @@
[{:keys [type url name thumbnail-url description subscriber-count
stream-count verified? key uploader-name uploader-url
uploader-avatar upload-date short-description view-count
- duration]}
- item-route service-color]
- [:<>
- (when name
- [:div.flex.items-center.my-2
- [:a {:href item-route :title name}
- [:h1.line-clamp-2.my-1.break-words 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
- (if uploader-url
- [:a {:href (rfe/href :tubo.routes/channel nil {:url uploader-url}) :title uploader-name}
- [:h1.line-clamp-1.text-neutral-800.dark:text-gray-300.font-bold.pr-2.break-all uploader-name]]
- [:h1.line-clamp-1.text-neutral-800.dark:text-gray-300.font-bold.pr-2 uploader-name])
- (when (and uploader-url verified?)
- [:i.fa-solid.fa-circle-check])]
- (when (= type "stream")
- [:button.pl-4.focus:outline-none
- {:on-click #(rf/dispatch [::events/switch-to-audio-player
- {:duration duration
- :thumbnail-url thumbnail-url
- :uploader-name uploader-name
- :uploader-url uploader-url
- :name name
- :url url
- :service-color service-color}])}
- [:i.fa-solid.fa-headphones]])]
- (when subscriber-count
+ duration audio-streams video-streams] :as item}
+ item-route service-color bookmarks]
+ (let [stream? (or (= type "stream") audio-streams video-streams)]
+ [:<>
+ (when name
+ [:div.flex.items-center.my-2
+ [:a {:href item-route :title name}
+ [:h1.line-clamp-2.my-1.break-words {:style {:wordBreak "break-word"}} 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
+ (if uploader-url
+ [:a {:href (rfe/href :tubo.routes/channel nil {:url uploader-url}) :title uploader-name}
+ [:h1.line-clamp-1.text-neutral-800.dark:text-gray-300.font-bold.pr-2.break-all uploader-name]]
+ [:h1.line-clamp-1.text-neutral-800.dark:text-gray-300.font-bold.pr-2 uploader-name])
+ (when (and uploader-url verified?)
+ [:i.fa-solid.fa-circle-check])]
[:div.flex.items-center
- [:i.fa-solid.fa-users.text-xs]
- [:p.mx-2 (util/format-quantity subscriber-count)]])
- (when stream-count
- [:div.flex.items-center
- [:i.fa-solid.fa-video.text-xs]
- [:p.mx-2 (util/format-quantity stream-count)]])
- [:div.flex.my-1.justify-between
- [:p (util/format-date upload-date)]
- (when view-count
- [:div.flex.items-center.h-full.pl-2
- [:i.fa-solid.fa-eye.text-xs]
- [:p.pl-1.5 (util/format-quantity view-count)]])]])
+ (when stream?
+ [:button.ml-2.focus:outline-none
+ {:on-click #(rf/dispatch [::events/switch-to-audio-player item service-color])}
+ [:i.fa-solid.fa-headphones]])
+ (when (some #{(dissoc item :key)} bookmarks)
+ [:button.ml-4.focus:outline-none
+ {:on-click #(rf/dispatch [::events/remove-from-bookmarks (dissoc item :key)])}
+ [:i.fa-solid.fa-trash]])]]
+ (when (and subscriber-count (not stream?))
+ [:div.flex.items-center
+ [:i.fa-solid.fa-users.text-xs]
+ [:p.mx-2 (util/format-quantity subscriber-count)]])
+ (when stream-count
+ [:div.flex.items-center
+ [:i.fa-solid.fa-video.text-xs]
+ [:p.mx-2 (util/format-quantity stream-count)]])
+ [:div.flex.my-1.justify-between
+ [:p (util/format-date upload-date)]
+ (when view-count
+ [:div.flex.items-center.h-full.pl-2
+ [:i.fa-solid.fa-eye.text-xs]
+ [:p.pl-1.5 (util/format-quantity view-count)]])]]))
(defn stream-item
- [{:keys [url name thumbnail-url duration] :as item} service-color]
+ [{:keys [url name thumbnail-url duration] :as item} service-color bookmarks]
[:<>
[thumbnail thumbnail-url (rfe/href :tubo.routes/stream nil {:url url}) url name duration]
- [item-content item (rfe/href :tubo.routes/stream nil {:url url}) service-color]])
+ [item-content item (rfe/href :tubo.routes/stream nil {:url url}) service-color bookmarks]])
(defn channel-item
- [{:keys [url name thumbnail-url] :as item} service-color]
+ [{:keys [url name thumbnail-url] :as item} service-color bookmarks]
[:<>
[thumbnail thumbnail-url (rfe/href :tubo.routes/channel nil {:url url}) url name nil]
- [item-content item (rfe/href :tubo.routes/channel nil {:url url}) service-color]])
+ [item-content item (rfe/href :tubo.routes/channel nil {:url url}) service-color bookmarks]])
(defn playlist-item
- [{:keys [url name thumbnail-url] :as item} service-color]
+ [{:keys [url name thumbnail-url] :as item} service-color bookmarks]
[:<>
[thumbnail thumbnail-url (rfe/href :tubo.routes/playlist nil {:url url}) url name nil]
- [item-content item (rfe/href :tubo.routes/playlist nil {:url url}) service-color]])
+ [item-content item (rfe/href :tubo.routes/playlist nil {:url url}) service-color bookmarks]])
(defn generic-item
- [item service-color]
+ [item service-color bookmarks]
[:div.w-full.h-80.xs:h-72.my-2 {:key key}
[:div.px-5.py-2.m-2.flex.flex-col.max-w-full.min-h-full.max-h-full
(case (:type item)
- "stream" [stream-item item service-color]
- "channel" [channel-item item service-color]
- "playlist" [playlist-item item service-color])]])
+ "stream" [stream-item item service-color bookmarks]
+ "channel" [channel-item item service-color bookmarks]
+ "playlist" [playlist-item item service-color bookmarks]
+ [stream-item item service-color bookmarks])]])
(defn related-streams
[related-streams next-page-url]
(let [service-color @(rf/subscribe [:service-color])
- pagination-loading? @(rf/subscribe [:show-pagination-loading])]
- [:div.flex.flex-col.justify-center.items-center.flex-auto.my-2.md:my-8
+ pagination-loading? @(rf/subscribe [:show-pagination-loading])
+ bookmarks @(rf/subscribe [:bookmarks])]
+ [:div.flex.flex-col.items-center.flex-auto.my-2.md:my-8
(if (empty? related-streams)
- [:div.flex.items-center
+ [:div.flex.items-center.flex-auto
[:p "No available streams"]]
[:div.grid.w-full
- {:class "grid-cols-[repeat(auto-fit,_minmax(200px,_1fr))]"}
+ {:class "grid-cols-[repeat(auto-fill,_minmax(200px,_1fr))]"}
(for [[i item] (map-indexed vector related-streams)
:let [keyed-item (assoc item :key i)]]
- [generic-item keyed-item service-color])])
+ [generic-item keyed-item service-color bookmarks])])
(when-not (empty? next-page-url)
[loading/loading-icon service-color "text-2xl" (when-not pagination-loading? "invisible")])]))
diff --git a/src/frontend/tubo/events.cljs b/src/frontend/tubo/events.cljs
index cebd707..8254cd4 100644
--- a/src/frontend/tubo/events.cljs
+++ b/src/frontend/tubo/events.cljs
@@ -15,7 +15,7 @@
(fn [{:keys [store]} _]
(let [{:keys [current-theme show-comments show-related show-description
media-queue media-queue-pos show-audio-player
- loop-file loop-playlist]} store]
+ loop-file loop-playlist volume-level muted bookmarks]} store]
{:db
{:search-query ""
:service-id 0
@@ -27,6 +27,7 @@
:media-queue (if (nil? media-queue) [] media-queue)
:media-queue-pos (if (nil? media-queue-pos) 0 media-queue-pos)
:volume-level (if (nil? volume-level) 100 volume-level)
+ :bookmarks (if (nil? bookmarks) [] bookmarks)
:muted (if (nil? muted) false muted)
:current-match nil
:show-audio-player (if (nil? show-audio-player) false show-audio-player)
@@ -291,6 +292,22 @@
(conj {:service-color service-color} %)]])
streams))}))
+(rf/reg-event-fx
+ ::add-to-bookmarks
+ [(rf/inject-cofx :store)]
+ (fn [{:keys [db store]} [_ bookmark]]
+ (when-not (some #(= (:url %) (:url bookmark)) (:bookmarks db))
+ (let [updated-db (update db :bookmarks conj bookmark)]
+ {:db updated-db
+ :store (assoc store :bookmarks (:bookmarks updated-db))}))))
+
+(rf/reg-event-fx
+ ::remove-from-bookmarks
+ [(rf/inject-cofx :store)]
+ (fn [{:keys [db store]} [_ bookmark]]
+ (let [updated-db (update db :bookmarks #(remove (fn [item] (= (:url item) (:url bookmark))) %))]
+ {:db updated-db
+ :store (assoc store :bookmarks (:bookmarks updated-db))})))
(rf/reg-event-db
::load-services
diff --git a/src/frontend/tubo/routes.cljs b/src/frontend/tubo/routes.cljs
index 1049812..4cf97c3 100644
--- a/src/frontend/tubo/routes.cljs
+++ b/src/frontend/tubo/routes.cljs
@@ -4,6 +4,7 @@
[reitit.frontend.easy :as rfe]
[re-frame.core :as rf]
[tubo.events :as events]
+ [tubo.views.bookmarks :as bookmarks]
[tubo.views.channel :as channel]
[tubo.views.kiosk :as kiosk]
[tubo.views.playlist :as playlist]
@@ -43,7 +44,9 @@
:start (fn [{{:keys [serviceId kioskId]} :query}]
(rf/dispatch [::events/get-kiosk-page serviceId kioskId]))}]}]
["/settings" {:view settings/settings-page
- :name ::settings}]]))
+ :name ::settings}]
+ ["/bookmarks" {:view bookmarks/bookmarks-page
+ :name ::bookmarks}]]))
(defn on-navigate
[new-match]
diff --git a/src/frontend/tubo/subs.cljs b/src/frontend/tubo/subs.cljs
index 622984a..017697c 100644
--- a/src/frontend/tubo/subs.cljs
+++ b/src/frontend/tubo/subs.cljs
@@ -157,6 +157,11 @@
(and (not-empty queue) (nth queue pos))))
(rf/reg-sub
+ :bookmarks
+ (fn [db _]
+ (:bookmarks db)))
+
+(rf/reg-sub
:show-audio-player
(fn [db _]
(:show-audio-player db)))
diff --git a/src/frontend/tubo/views.cljs b/src/frontend/tubo/views.cljs
index 8f052ca..28a3e82 100644
--- a/src/frontend/tubo/views.cljs
+++ b/src/frontend/tubo/views.cljs
@@ -51,7 +51,11 @@
[:span.ml-7 kiosk]]])]]
[:div.relative.dark:border-neutral-800.border-gray-300.pt-4
{:class "border-t-[1px]"}
- [:ul.flex.font-roboto
+ [:ul.flex.flex-col.font-roboto
+ [:li.px-5.py-2
+ [:a {:href (rfe/href ::routes/bookmarks)}
+ [:i.fa-solid.fa-bookmark.text-neutral-600.dark:text-neutral-300]
+ [:span.ml-8 "Bookmarks"]]]
[:li.px-5.py-2
[:a {:href (rfe/href ::routes/settings)}
[:i.fa-solid.fa-cog.text-neutral-600.dark:text-neutral-300]
@@ -113,7 +117,10 @@
[:i.fa-solid.fa-search]]
[:a.mx-2.hidden.ml:block
{:href (rfe/href ::routes/settings)}
- [:i.fa-solid.fa-cog]]]]
+ [:i.fa-solid.fa-cog]]
+ [:a.mx-2.hidden.ml:block
+ {:href (rfe/href ::routes/bookmarks)}
+ [:i.fa-solid.fa-bookmark]]]]
[:div.cursor-pointer.px-2.ml:hidden.text-white
{:on-click #(rf/dispatch [::events/toggle-mobile-nav])}
[:i.fa-solid.fa-bars]]
diff --git a/src/frontend/tubo/views/bookmarks.cljs b/src/frontend/tubo/views/bookmarks.cljs
new file mode 100644
index 0000000..cae383c
--- /dev/null
+++ b/src/frontend/tubo/views/bookmarks.cljs
@@ -0,0 +1,21 @@
+(ns tubo.views.bookmarks
+ (:require
+ [re-frame.core :as rf]
+ [tubo.components.items :as items]
+ [tubo.components.navigation :as navigation]
+ [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 {: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.mx-2.text-neutral-600.dark:text-neutral-300 "Background"]]]
+ [items/related-streams bookmarks]]]))
diff --git a/src/frontend/tubo/views/stream.cljs b/src/frontend/tubo/views/stream.cljs
index 1727da1..c4546b8 100644
--- a/src/frontend/tubo/views/stream.cljs
+++ b/src/frontend/tubo/views/stream.cljs
@@ -24,7 +24,8 @@
available-streams (apply conj audio-streams video-streams)
{:keys [content id] :as stream-format} @(rf/subscribe [:stream-format])
page-loading? @(rf/subscribe [:show-page-loading])
- service-color @(rf/subscribe [:service-color])]
+ 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"]
@@ -45,8 +46,17 @@
[: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.text-neutral-600.dark:text-neutral-300 "Background"]]
- [:button.px-3.py-1.mx-2
+ [: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"]]