diff options
Diffstat (limited to 'src')
44 files changed, 1109 insertions, 767 deletions
diff --git a/src/backend/tubo/core.clj b/src/backend/tubo/core.clj index a587909..48a8897 100644 --- a/src/backend/tubo/core.clj +++ b/src/backend/tubo/core.clj @@ -3,10 +3,6 @@ (:require [tubo.http :as http])) -(defn -main - [& _] - (http/start-server!)) +(defn -main [& _] (http/start-server!)) -(defn reset - [] - (http/stop-server!)) +(defn reset [] (http/stop-server!)) diff --git a/src/backend/tubo/downloader_impl.clj b/src/backend/tubo/downloader_impl.clj index e22a2da..7cb0872 100644 --- a/src/backend/tubo/downloader_impl.clj +++ b/src/backend/tubo/downloader_impl.clj @@ -1,33 +1,36 @@ (ns tubo.downloader-impl (:import - [org.schabi.newpipe.extractor.downloader Response Request] + [org.schabi.newpipe.extractor.downloader Response] [okhttp3 Request$Builder OkHttpClient$Builder RequestBody])) (gen-class - :name tubo.DownloaderImpl + :name tubo.DownloaderImpl :constructors {[okhttp3.OkHttpClient$Builder] []} - :extends org.schabi.newpipe.extractor.downloader.Downloader - :init downloader-impl) + :extends org.schabi.newpipe.extractor.downloader.Downloader + :init downloader-impl) (gen-class - :name tubo.DownloaderImpl + :name tubo.DownloaderImpl :constructors {[okhttp3.OkHttpClient$Builder] []} - :extends org.schabi.newpipe.extractor.downloader.Downloader - :prefix "-" - :main false - :state state - :init downloader-impl - :methods [#^{:static true} [init [] tubo.DownloaderImpl] - #^{:static true} [getInstance [] tubo.DownloaderImpl]]) + :extends org.schabi.newpipe.extractor.downloader.Downloader + :prefix "-" + :main false + :state state + :init downloader-impl + :methods [#^{:static true} [init [] tubo.DownloaderImpl] + #^{:static true} [getInstance [] tubo.DownloaderImpl]]) -(def user-agent "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0") +(def user-agent + "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0") (def instance (atom nil)) -(defn -downloader-impl [builder] - [[] (atom {:client - (.. builder - (readTimeout 30 (java.util.concurrent.TimeUnit/SECONDS)) - (build))})]) +(defn -downloader-impl + [builder] + [[] + (atom {:client + (.. builder + (readTimeout 30 (java.util.concurrent.TimeUnit/SECONDS)) + (build))})]) (defn -init ([] @@ -35,33 +38,37 @@ ([builder] (reset! instance (tubo.DownloaderImpl. builder)))) -(defn -getInstance [] +(defn -getInstance + [] (or @instance (-init))) -(defn -execute [this request] - (let [http-method (.httpMethod request) - url (.url request) - headers (.headers request) - data-to-send (.dataToSend request) - request-body (and data-to-send (RequestBody/create nil data-to-send)) +(defn -execute + [this request] + (let [http-method (.httpMethod request) + url (.url request) + headers (.headers request) + data-to-send (.dataToSend request) + request-body (and data-to-send (RequestBody/create nil data-to-send)) request-builder (.. (Request$Builder.) (method http-method request-body) (url url) (addHeader "User-Agent" user-agent))] (doseq [pair (.entrySet headers)] - (let [header-name (.getKey pair) + (let [header-name (.getKey pair) header-value-list (.getValue pair)] (if (> (.size header-value-list) 1) (do (.removeHeader request-builder header-name) (doseq [header-value header-value-list] (.addHeader request-builder header-name header-value))) - (if (= (.size header-value-list) 1) + (when (= (.size header-value-list) 1) (.header request-builder header-name (.get header-value-list 0)))))) - (let [response (.. (@(.state this) :client) (newCall (.build request-builder)) (execute)) - body (.body response) + (let [response (.. (@(.state this) :client) + (newCall (.build request-builder)) + (execute)) + body (.body response) response-body-to-return (and body (.string body)) - latest-url (.. response (request) (url) (toString))] + latest-url (.. response (request) (url) (toString))] (when (= (.code response) 429) (.close response)) (Response. (.code response) @@ -70,4 +77,5 @@ response-body-to-return latest-url)))) -(comment (compile 'tubo.downloader-impl)) +(comment + (compile 'tubo.downloader-impl)) diff --git a/src/backend/tubo/handler.clj b/src/backend/tubo/handler.clj index 4b37494..ab66d0f 100644 --- a/src/backend/tubo/handler.clj +++ b/src/backend/tubo/handler.clj @@ -3,11 +3,7 @@ [clojure.string :as str] [hiccup.page :as hiccup] [ring.util.response :refer [response]] - [tubo.api.streams :as streams] - [tubo.api.channels :as channels] - [tubo.api.playlists :as playlists] - [tubo.api.comments :as comments] - [tubo.api.services :as services])) + [tubo.api :as api])) (defn index [_] @@ -17,9 +13,10 @@ [:meta {:charset "UTF-8"}] [:meta {:name "viewport" - :content "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"}] + :content + "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"}] [:title "Tubo"] - [:link {:rel "icon" :type "image/png" :href "/icons/tubo.svg"}] + [:link {:rel "icon" :type "image/svg+xml" :href "/icons/tubo.svg"}] (hiccup/include-css "/styles/index.css")] [:body [:div#app] @@ -28,46 +25,54 @@ (defn search [{:keys [parameters] :as req}] - (let [{:keys [service-id]} (:path parameters) - {:keys [q]} (:query parameters) + (let [{:keys [service-id]} (:path parameters) + {:keys [q]} (:query parameters) {:strs [contentFilters sortFilter nextPage]} (:query-params req) - content-filters (and contentFilters (str/split contentFilters #","))] + content-filters (and contentFilters + (str/split + contentFilters + #","))] (response (if nextPage - (services/search service-id q contentFilters sortFilter nextPage) - (services/search service-id q contentFilters sortFilter))))) + (api/get-search service-id q contentFilters sortFilter nextPage) + (api/get-search service-id q contentFilters sortFilter))))) (defn channel [{{:keys [url]} :path-params {:strs [nextPage]} :query-params}] (response (if nextPage - (channels/get-channel url nextPage) - (channels/get-channel url)))) + (api/get-channel url nextPage) + (api/get-channel url)))) (defn playlist [{{:keys [url]} :path-params {:strs [nextPage]} :query-params}] (response (if nextPage - (playlists/get-playlist url nextPage) - (playlists/get-playlist url)))) + (api/get-playlist url nextPage) + (api/get-playlist url)))) (defn comments [{{:keys [url]} :path-params {:strs [nextPage]} :query-params}] (response (if nextPage - (comments/get-comments url nextPage) - (comments/get-comments url)))) + (api/get-comments url nextPage) + (api/get-comments url)))) (defn services [_] - (response (services/get-services))) + (response (api/get-services))) (defn kiosks [{{{:keys [service-id]} :path} :parameters}] - (response (services/get-kiosks service-id))) + (response (api/get-kiosks service-id))) (defn kiosk - [{{{:keys [kiosk-id service-id]} :path} :parameters {:strs [nextPage]} :query-params}] + [{{{:keys [kiosk-id service-id]} :path} :parameters + {:strs [nextPage]} :query-params}] (response (cond - (and kiosk-id service-id nextPage) (services/get-kiosk kiosk-id service-id nextPage) - (and kiosk-id service-id) (services/get-kiosk kiosk-id service-id) - :else (services/get-kiosk service-id)))) + (and kiosk-id service-id nextPage) (api/get-kiosk kiosk-id + service-id + nextPage) + (and kiosk-id service-id) (api/get-kiosk kiosk-id + service-id) + :else (api/get-kiosk service-id)))) -(defn stream [{{:keys [url]} :path-params}] - (response (streams/get-stream url))) +(defn stream + [{{:keys [url]} :path-params}] + (response (api/get-stream url))) diff --git a/src/backend/tubo/http.clj b/src/backend/tubo/http.clj index 9ee87f3..200b52e 100644 --- a/src/backend/tubo/http.clj +++ b/src/backend/tubo/http.clj @@ -10,15 +10,10 @@ (defonce server (atom nil)) (defn start-server! - ([] - (start-server! 3000)) + ([] (start-server! 3000)) ([port] (NewPipe/init (DownloaderImpl/init) (Localization. "en" "US")) (reset! server (run-server #'routes/app {:port port})) (println "Server running in port" port))) -(defn stop-server! - [] - (when @server - (@server :timeout 100) - (reset! server nil))) +(defn stop-server! [] (when @server (@server :timeout 100) (reset! server nil))) diff --git a/src/backend/tubo/routes.clj b/src/backend/tubo/routes.clj index edf4876..7a3a456 100644 --- a/src/backend/tubo/routes.clj +++ b/src/backend/tubo/routes.clj @@ -1,14 +1,11 @@ (ns tubo.routes (:require - [malli.experimental.lite :as l] [reitit.ring :as ring] - [reitit.coercion :as coercion] [reitit.ring.coercion :as rrc] [reitit.coercion.malli] [ring.middleware.reload :refer [wrap-reload]] [ring.middleware.params :refer [wrap-params]] [ring.middleware.json :refer [wrap-json-response]] - [ring.middleware.cors :refer [wrap-cors]] [tubo.handler :as handler])) (def router @@ -26,21 +23,24 @@ ["/services" ["" {:get handler/services}] ["/:service-id/search" - {:get {:coercion reitit.coercion.malli/coercion - :parameters {:path {:service-id int?} + {:get {:coercion reitit.coercion.malli/coercion + :parameters {:path {:service-id int?} :query {:q string?}} - :handler handler/search}}] + :handler handler/search}}] ["/:service-id" - ["/default-kiosk" {:get {:coercion reitit.coercion.malli/coercion - :parameters {:path {:service-id int?}} - :handler handler/kiosk}}] + ["/default-kiosk" + {:get {:coercion reitit.coercion.malli/coercion + :parameters {:path {:service-id int?}} + :handler handler/kiosk}}] ["/kiosks" - ["" {:get {:coercion reitit.coercion.malli/coercion - :parameters {:path {:service-id int?}} - :handler handler/kiosks}}] - ["/:kiosk-id" {:get {:coercion reitit.coercion.malli/coercion - :parameters {:path {:service-id int? :kiosk-id string?}} - :handler handler/kiosk}}]]]] + ["" + {:get {:coercion reitit.coercion.malli/coercion + :parameters {:path {:service-id int?}} + :handler handler/kiosks}}] + ["/:kiosk-id" + {:get {:coercion reitit.coercion.malli/coercion + :parameters {:path {:service-id int? :kiosk-id string?}} + :handler handler/kiosk}}]]]] ["/streams/:url" {:get handler/stream}] ["/channels/:url" {:get handler/channel}] ["/playlists/:url" {:get handler/playlist}] @@ -55,7 +55,7 @@ (ring/routes (ring/create-resource-handler {:path "/"}) (ring/create-default-handler - {:not-found (constantly {:status 404, :body "Not found"})})) + {:not-found (constantly {:status 404 :body "Not found"})})) {:middleware [wrap-params [wrap-json-response {:pretty true}] wrap-reload]})) diff --git a/src/frontend/tubo/api.cljs b/src/frontend/tubo/api.cljs index 41ca410..fea688a 100644 --- a/src/frontend/tubo/api.cljs +++ b/src/frontend/tubo/api.cljs @@ -6,10 +6,10 @@ ([uri on-success on-failure] (get-request uri on-success on-failure {})) ([uri on-success on-failure params] - {:http-xhrio {:method :get - :uri (str "/api/v1" uri) - :params params - :format (ajax/json-request-format) + {:http-xhrio {:method :get + :uri (str "/api/v1" uri) + :params params + :format (ajax/json-request-format) :response-format (ajax/json-response-format {:keywords? true}) - :on-success on-success - :on-failure on-failure}})) + :on-success on-success + :on-failure on-failure}})) diff --git a/src/frontend/tubo/bookmarks/events.cljs b/src/frontend/tubo/bookmarks/events.cljs index e0f2f2b..decb107 100644 --- a/src/frontend/tubo/bookmarks/events.cljs +++ b/src/frontend/tubo/bookmarks/events.cljs @@ -2,14 +2,15 @@ (:require [nano-id.core :refer [nano-id]] [promesa.core :as p] - [re-frame.core :as rf] - [tubo.bookmarks.modals :as modals])) + [re-frame.core :as rf])) (rf/reg-event-fx :bookmarks/add [(rf/inject-cofx :store)] (fn [{:keys [db store]} [_ bookmark notify?]] - (let [updated-db (update db :bookmarks conj + (let [updated-db (update db + :bookmarks + conj (if (:id bookmark) bookmark (assoc bookmark :id (nano-id))))] @@ -17,26 +18,31 @@ :store (assoc store :bookmarks (:bookmarks updated-db)) :fx [[:dispatch [:modals/close]] (when notify? - [:dispatch [:notifications/add - {:status-text - (str "Added playlist \"" (:name bookmark) "\"") - :failure :success}]])]}))) + [:dispatch + [:notifications/add + {:status-text + (str "Added playlist \"" (:name bookmark) "\"") + :failure :success}]])]}))) (rf/reg-event-fx :bookmarks/remove [(rf/inject-cofx :store)] (fn [{:keys [db store]} [_ id notify?]] (let [bookmark (first (filter #(= (:id %) id) (:bookmarks db))) - updated-db (update db :bookmarks - #(into [] (remove (fn [bookmark] - (= (:id bookmark) id)) %)))] + updated-db (update db + :bookmarks + #(into [] + (remove (fn [bookmark] + (= (:id bookmark) id)) + %)))] {:db updated-db :store (assoc store :bookmarks (:bookmarks updated-db)) :fx (if notify? - [[:dispatch [:notifications/add - {:status-text - (str "Removed playlist \"" (:name bookmark) "\"") - :failure :success}]]] + [[:dispatch + [:notifications/add + {:status-text + (str "Removed playlist \"" (:name bookmark) "\"") + :failure :success}]]] [])}))) (rf/reg-event-fx @@ -49,63 +55,82 @@ (map (fn [item] [:dispatch [:likes/remove item]]) (:items (first (:bookmarks db))))) - [:dispatch [:notifications/add - {:status-text "Cleared all playlists" - :failure :success}]])})) + [:dispatch + [:notifications/add + {:status-text "Cleared all playlists" + :failure :success}]])})) (rf/reg-event-fx :likes/add-n (fn [_ [_ items]] {:fx (conj (map (fn [item] - [:dispatch [:likes/add item]]) items) - [:dispatch [:notifications/add - {:status-text (str "Added " (count items) - " items to likes") - :failure :success}]])})) + [:dispatch [:likes/add item]]) + items) + [:dispatch + [:notifications/add + {:status-text (str "Added " + (count items) + " items to likes") + :failure :success}]])})) (rf/reg-event-fx :likes/add [(rf/inject-cofx :store)] (fn [{:keys [db store]} [_ item notify?]] (when-not (some #(= (:url %) (:url item)) - (-> db :bookmarks first :items)) - (let [updated-db (update-in db [:bookmarks 0 :items] + (-> db + :bookmarks + first + :items)) + (let [updated-db (update-in db + [:bookmarks 0 :items] #(into [] (conj (into [] %1) %2)) - (assoc item :bookmark-id - (-> db :bookmarks first :id)))] + (assoc item + :bookmark-id + (-> db + :bookmarks + first + :id)))] {:db updated-db :store (assoc store :bookmarks (:bookmarks updated-db)) :fx (if notify? - [[:dispatch [:notifications/add - {:status-text "Added to favorites" - :failure :success}]]] + [[:dispatch + [:notifications/add + {:status-text "Added to favorites" + :failure :success}]]] [])})))) (rf/reg-event-fx :likes/remove [(rf/inject-cofx :store)] (fn [{:keys [db store]} [_ item notify?]] - (let [updated-db (update-in db [:bookmarks 0 :items] + (let [updated-db (update-in db + [:bookmarks 0 :items] (fn [items] (remove #(= (:url %) (:url item)) items)))] {:db updated-db :store (assoc store :bookmarks (:bookmarks updated-db)) :fx (if notify? - [[:dispatch [:notifications/add - {:status-text "Removed from favorites" - :failure :success}]]] + [[:dispatch + [:notifications/add + {:status-text "Removed from favorites" + :failure :success}]]] [])}))) (rf/reg-event-fx :bookmark/add-n (fn [_ [_ bookmark items]] {:fx (conj (map (fn [item] - [:dispatch [:bookmark/add bookmark item]]) items) - [:dispatch [:notifications/add - {:status-text (str "Added " (count items) - " items to playlist \"" - (:name bookmark) "\"") - :failure :success}]])})) + [:dispatch [:bookmark/add bookmark item]]) + items) + [:dispatch + [:notifications/add + {:status-text (str "Added " + (count items) + " items to playlist \"" + (:name bookmark) + "\"") + :failure :success}]])})) (rf/reg-event-fx :bookmark/add @@ -115,17 +140,20 @@ pos (.indexOf (:bookmarks db) selected) updated-db (if (some #(= (:url %) (:url item)) (:items selected)) db - (update-in db [:bookmarks pos :items] + (update-in db + [:bookmarks pos :items] #(into [] (conj (into [] %1) %2)) (assoc item :bookmark-id (:id bookmark))))] {:db updated-db :store (assoc store :bookmarks (:bookmarks updated-db)) :fx [[:dispatch [:modals/close]] (when notify? - [:dispatch [:notifications/add - {:status-text (str "Added to playlist \"" - (:name selected) "\"") - :failure :success}]])]}))) + [:dispatch + [:notifications/add + {:status-text (str "Added to playlist \"" + (:name selected) + "\"") + :failure :success}]])]}))) (rf/reg-event-fx :bookmark/remove @@ -134,28 +162,32 @@ (let [selected (first (filter #(= (:id %) (:bookmark-id bookmark)) (:bookmarks db))) pos (.indexOf (:bookmarks db) selected) - updated-db (update-in db [:bookmarks pos :items] + updated-db (update-in db + [:bookmarks pos :items] #(remove (fn [item] (= (:url item) (:url bookmark))) %))] {:db updated-db :store (assoc store :bookmarks (:bookmarks updated-db)) - :fx [[:dispatch [:notifications/add - {:status-text (str "Removed from playlist \"" - (:name selected) "\"") - :failure :success}]]]}))) + :fx [[:dispatch + [:notifications/add + {:status-text (str "Removed from playlist \"" + (:name selected) + "\"") + :failure :success}]]]}))) (rf/reg-event-fx :bookmarks/add-imported - (fn [{:keys [db]} [_ bookmarks]] + (fn [_ [_ bookmarks]] {:fx (conj (map-indexed (fn [i bookmark] (if (= i 0) [:dispatch [:likes/add-n (:items bookmark)]] [:dispatch [:bookmarks/add bookmark]])) bookmarks) - [:dispatch [:notifications/add - {:status-text "Imported playlists successfully" - :failure :success}]])})) + [:dispatch + [:notifications/add + {:status-text "Imported playlists successfully" + :failure :success}]])})) (defn fetch-imported-bookmarks-items [bookmarks] @@ -172,17 +204,18 @@ (rf/reg-event-fx :bookmarks/process-import - (fn [{:keys [db]} [_ bookmarks]] + (fn [_ [_ bookmarks]] {:promise {:call #(-> (fetch-imported-bookmarks-items bookmarks) (p/then (fn [res] (js->clj res :keywordize-keys true)))) :on-success-n [[:notifications/clear] [:bookmarks/add-imported]]} - :fx [[:dispatch [:notifications/add - {:status-text "Importing playlists..." - :failure :success} - false]]]})) + :fx [[:dispatch + [:notifications/add + {:status-text "Importing playlists..." + :failure :success} + false]]]})) (rf/reg-fx :bookmarks/import! @@ -194,14 +227,14 @@ (rf/dispatch [:bookmarks/process-import (:playlists res)]) (throw (js/Error. "Format not supported"))))) (p/catch js/Error - (fn [error] - (rf/dispatch [:notifications/add - {:status-text (.-message error) - :failure :error}])))))) + (fn [error] + (rf/dispatch [:notifications/add + {:status-text (.-message error) + :failure :error}])))))) (rf/reg-event-fx :bookmarks/import - (fn [{:keys [db]} [_ files]] + (fn [_ [_ files]] {:fx (map (fn [file] [:bookmarks/import! file]) files)})) (rf/reg-event-fx @@ -212,16 +245,17 @@ :mime-type "application/json" :data (.stringify js/JSON - (clj->js {:format "Tubo" + (clj->js {:format "Tubo" :version 1 :playlists (map (fn [bookmark] {:name (:name bookmark) :items (map :url (:items bookmark))}) (:bookmarks db))}))} - :fx [[:dispatch [:notifications/add - {:status-text "Exported playlists" - :failure :success}]]]})) + :fx [[:dispatch + [:notifications/add + {:status-text "Exported playlists" + :failure :success}]]]})) (rf/reg-event-fx :bookmarks/fetch-page diff --git a/src/frontend/tubo/bookmarks/modals.cljs b/src/frontend/tubo/bookmarks/modals.cljs index 6edc1ce..f8a1738 100644 --- a/src/frontend/tubo/bookmarks/modals.cljs +++ b/src/frontend/tubo/bookmarks/modals.cljs @@ -2,16 +2,19 @@ (:require [reagent.core :as r] [re-frame.core :as rf] - [reitit.frontend.easy :as rfe] [tubo.components.layout :as layout] [tubo.modals.views :as modals])) (defn bookmark-item - [{:keys [items id name] :as bookmark} item] + [{:keys [items name] :as bookmark} item] [:div.flex.w-full.h-24.rounded.px-2.cursor-pointer.hover:bg-gray-100.dark:hover:bg-stone-800 - {:on-click #(rf/dispatch [(if (vector? item) :bookmark/add-n :bookmark/add) bookmark item])} + {:on-click #(rf/dispatch [(if (vector? item) :bookmark/add-n :bookmark/add) + bookmark item])} [:div.w-24 - [layout/thumbnail (-> items first :thumbnail-url) nil name nil + [layout/thumbnail + (-> items + first + :thumbnail-url) nil name nil :classes [:h-24 :py-2] :rounded? true]] [:div.flex.flex-col.py-4.px-4 [:h1.line-clamp-1.font-bold {:title name} name] diff --git a/src/frontend/tubo/bookmarks/views.cljs b/src/frontend/tubo/bookmarks/views.cljs index c25a0b4..4bfd96e 100644 --- a/src/frontend/tubo/bookmarks/views.cljs +++ b/src/frontend/tubo/bookmarks/views.cljs @@ -11,14 +11,18 @@ [] (let [!menu-active? (r/atom nil)] (fn [] - (let [color @(rf/subscribe [:service-color]) - bookmarks @(rf/subscribe [:bookmarks]) + (let [bookmarks @(rf/subscribe [:bookmarks]) items (map #(assoc % - :url (rfe/href :bookmark-page nil {:id (:id %)}) - :thumbnail-url (-> % :items first :thumbnail-url) - :stream-count (count (:items %)) - :bookmark-id (:id %)) + :stream-count (count (:items %)) + :bookmark-id (:id %) + :url (rfe/href :bookmark-page + nil + {:id (:id %)}) + :thumbnail-url (-> % + :items + first + :thumbnail-url)) bookmarks)] [layout/content-container [layout/content-header "Bookmarked Playlists" @@ -32,10 +36,12 @@ :type "file" :multiple "multiple" :on-click #(reset! !menu-active? true) - :on-change #(rf/dispatch [:bookmarks/import (.. % -target -files)])}] + :on-change #(rf/dispatch [:bookmarks/import + (.. % -target -files)])}] [:label.whitespace-nowrap.cursor-pointer.w-full.h-full.absolute.right-0.top-0 {:for "file-selector"}] - [:span.text-xs.w-10.min-w-4.w-4.flex.items-center [:i.fa-solid.fa-file-import]] + [:span.text-xs.w-10.min-w-4.w-4.flex.items-center + [:i.fa-solid.fa-file-import]] [:span "Import"]] {:label "Export" :icon [:i.fa-solid.fa-file-export] @@ -50,9 +56,9 @@ (let [!menu-active? (r/atom nil)] (fn [] (let [bookmarks @(rf/subscribe [:bookmarks]) - service-color @(rf/subscribe [:service-color]) {{:keys [id]} :query-params} @(rf/subscribe [:current-match]) - {:keys [items name]} (first (filter #(= (:id %) id) bookmarks))] + {:keys [items name]} (first (filter #(= (:id %) id) + bookmarks))] [layout/content-container [layout/content-header name (when-not (empty? items) @@ -62,5 +68,7 @@ :on-click #(rf/dispatch [:queue/add-n items true])} {:label "Add to playlist" :icon [:i.fa-solid.fa-plus] - :on-click #(rf/dispatch [:modals/open [modals/add-to-bookmark items]])}]])] - [items/related-streams (map #(assoc % :type "stream" :bookmark-id id) items)]])))) + :on-click #(rf/dispatch [:modals/open + [modals/add-to-bookmark items]])}]])] + [items/related-streams + (map #(assoc % :type "stream" :bookmark-id id) items)]])))) diff --git a/src/frontend/tubo/channel/events.cljs b/src/frontend/tubo/channel/events.cljs index 5036e73..b8e5fb0 100644 --- a/src/frontend/tubo/channel/events.cljs +++ b/src/frontend/tubo/channel/events.cljs @@ -7,16 +7,18 @@ (rf/reg-event-fx :channel/fetch - (fn [{:keys [db]} [_ uri on-success on-error]] + (fn [_ [_ uri on-success on-error]] (api/get-request (str "/channels/" (js/encodeURIComponent uri)) - on-success on-error))) + on-success + on-error))) (rf/reg-event-fx :channel/load-page (fn [{:keys [db]} [_ res]] (let [channel-res (js->clj res :keywordize-keys true)] - {:db (assoc db :channel channel-res + {:db (assoc db + :channel channel-res :show-page-loading false) :fx [[:dispatch [:services/fetch channel-res]] [:document-title (:name channel-res)]]}))) @@ -24,15 +26,17 @@ (rf/reg-event-fx :channel/bad-page-response (fn [{:keys [db]} [_ uri res]] - {:fx [[:dispatch [:change-view #(layout/error res [:channel/fetch-page uri])]]] + {:fx [[:dispatch + [:change-view #(layout/error res [:channel/fetch-page uri])]]] :db (assoc db :show-page-loading false)})) (rf/reg-event-fx :channel/fetch-page (fn [{:keys [db]} [_ uri]] {:fx [[:dispatch [:change-view channel/channel]] - [:dispatch [:channel/fetch uri [:channel/load-page] - [:channel/bad-page-response uri]]]] + [:dispatch + [:channel/fetch uri [:channel/load-page] + [:channel/bad-page-response uri]]]] :db (assoc db :show-page-loading true)})) (rf/reg-event-db @@ -44,7 +48,8 @@ (assoc-in [:channel :next-page] nil) (assoc :show-pagination-loading false)) (-> db - (update-in [:channel :related-streams] #(apply conj %1 %2) + (update-in [:channel :related-streams] + #(apply conj %1 %2) (:related-streams channel-res)) (assoc-in [:channel :next-page] (:next-page channel-res)) (assoc :show-pagination-loading false)))))) @@ -62,7 +67,9 @@ {:db (assoc db :show-pagination-loading false)} (assoc (api/get-request - (str "/channels/" (js/encodeURIComponent uri) ) - [:channel/load-paginated] [:channel/bad-paginated-response] + (str "/channels/" (js/encodeURIComponent uri)) + [:channel/load-paginated] + [:channel/bad-paginated-response] {:nextPage (js/encodeURIComponent next-page-url)}) - :db (assoc db :show-pagination-loading true))))) + :db + (assoc db :show-pagination-loading true))))) diff --git a/src/frontend/tubo/channel/views.cljs b/src/frontend/tubo/channel/views.cljs index 6994318..9986ca1 100644 --- a/src/frontend/tubo/channel/views.cljs +++ b/src/frontend/tubo/channel/views.cljs @@ -7,16 +7,16 @@ [tubo.components.layout :as layout])) (defn channel - [query-params] - (let [!menu-active? (r/atom nil) + [_] + (let [!menu-active? (r/atom nil) !show-description? (r/atom false)] (fn [{{:keys [url]} :query-params}] (let [{:keys [banner avatar name description subscriber-count next-page - related-streams]} @(rf/subscribe [:channel]) - next-page-url (:url next-page) - service-color @(rf/subscribe [:service-color]) - scrolled-to-bottom? @(rf/subscribe [:scrolled-to-bottom]) - page-loading? @(rf/subscribe [:show-page-loading])] + related-streams]} + @(rf/subscribe [:channel]) + next-page-url (:url next-page) + scrolled-to-bottom? @(rf/subscribe [:scrolled-to-bottom]) + page-loading? @(rf/subscribe [:show-page-loading])] (when (and next-page-url scrolled-to-bottom?) (rf/dispatch [:channel/fetch-paginated url next-page-url])) [:<> @@ -27,7 +27,8 @@ [layout/content-container [:div.flex.items-center.justify-between [:div.flex.items-center.my-4.mx-2 - [layout/uploader-avatar {:uploader-avatar avatar :uploader-name name}] + [layout/uploader-avatar + {:uploader-avatar avatar :uploader-name name}] [:div.m-4 [:h1.text-2xl.line-clamp-1.font-semibold {:title name} name] (when subscriber-count @@ -41,7 +42,9 @@ :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]])}]])] + :on-click #(rf/dispatch [:modals/open + [modals/add-to-bookmark + related-streams]])}]])] (when-not (empty? description) [layout/show-more-container @!show-description? description #(reset! !show-description? (not @!show-description?))]) diff --git a/src/frontend/tubo/comments/events.cljs b/src/frontend/tubo/comments/events.cljs index 1d78af3..f8122cd 100644 --- a/src/frontend/tubo/comments/events.cljs +++ b/src/frontend/tubo/comments/events.cljs @@ -5,9 +5,12 @@ (rf/reg-event-fx :comments/fetch - (fn [{:keys [db]} [_ url on-success on-error params]] - (api/get-request (str "/comments/" (js/encodeURIComponent url)) - on-success on-error params))) + (fn [_ [_ url on-success on-error params]] + (api/get-request (str "/comments/" + (js/encodeURIComponent url)) + on-success + on-error + params))) (rf/reg-event-db :comments/load-page @@ -19,8 +22,9 @@ (rf/reg-event-fx :comments/fetch-page (fn [{:keys [db]} [_ url]] - {:fx [[:dispatch [:comments/fetch url - [:comments/load-page] [:bad-response]]]] + {:fx [[:dispatch + [:comments/fetch url + [:comments/load-page] [:bad-response]]]] :db (-> db (assoc-in [:stream :show-comments-loading] true) (assoc-in [:stream :show-comments] true))})) @@ -28,7 +32,8 @@ (rf/reg-event-db :comments/toggle-replies (fn [db [_ comment-id]] - (update-in db [:stream :comments-page :comments] + (update-in db + [:stream :comments-page :comments] (fn [comments] (map #(if (= (:id %) comment-id) (assoc % :show-replies (not (:show-replies %))) @@ -39,7 +44,8 @@ :comments/load-paginated (fn [db [_ res]] (-> db - (update-in [:stream :comments-page :comments] #(apply conj %1 %2) + (update-in [:stream :comments-page :comments] + #(apply conj %1 %2) (:comments (js->clj res :keywordize-keys true))) (assoc-in [:stream :comments-page :next-page] (:next-page (js->clj res :keywordize-keys true))) @@ -51,6 +57,7 @@ (if (empty? next-page-url) {:db (assoc db :show-pagination-loading false)} {:db (assoc db :show-pagination-loading true) - :fx [[:dispatch [:comments/fetch url - [:comments/load-paginated] [:bad-response] - {:nextPage (js/encodeURIComponent next-page-url)}]]]}))) + :fx [[:dispatch + [:comments/fetch url + [:comments/load-paginated] [:bad-response] + {:nextPage (js/encodeURIComponent next-page-url)}]]]}))) diff --git a/src/frontend/tubo/comments/views.cljs b/src/frontend/tubo/comments/views.cljs index e9b5867..e1f6472 100644 --- a/src/frontend/tubo/comments/views.cljs +++ b/src/frontend/tubo/comments/views.cljs @@ -6,23 +6,28 @@ [tubo.utils :as utils])) (defn comment-top-metadata - [{:keys [pinned? uploader-name uploader-url uploader-verified? stream-position]}] + [{:keys [pinned? uploader-name uploader-url uploader-verified? + stream-position]}] [:div.flex.items-center (when pinned? [:i.fa-solid.fa-thumbtack.mr-2.text-xs]) (when uploader-name [:div.flex.items-stretch - [:a {:href (rfe/href :channel-page nil {:url uploader-url}) - :title uploader-name} - [:h1.text-neutral-800.dark:text-gray-300.font-bold.line-clamp-1 uploader-name]] + [:a + {:href (rfe/href :channel-page nil {:url uploader-url}) + :title uploader-name} + [:h1.text-neutral-800.dark:text-gray-300.font-bold.line-clamp-1 + uploader-name]] (when stream-position [:div.text-neutral-600.dark:text-neutral-300 - [:span.mx-2.text-xs.whitespace-nowrap (utils/format-duration stream-position)]])]) + [:span.mx-2.text-xs.whitespace-nowrap + (utils/format-duration stream-position)]])]) (when uploader-verified? [:i.fa-solid.fa-circle-check.ml-2])]) (defn comment-bottom-metadata - [{:keys [upload-date like-count hearted-by-uploader? author-avatar author-name]}] + [{:keys [upload-date like-count hearted-by-uploader? author-avatar + author-name]}] [:div.flex.items-center.my-2 [:div.mr-4 [:p (utils/format-date-ago upload-date)]] @@ -34,8 +39,8 @@ [:div.relative.w-4.h-4.mx-2 [:i.fa-solid.fa-heart.absolute.-bottom-1.-right-1.text-xs.text-red-500] [:img.rounded-full.object-covermax-w-full.min-h-full - {:src author-avatar :title (str author-name " hearted this comment")}]])]) - + {:src author-avatar + :title (str author-name " hearted this comment")}]])]) (defn comment-item [{:keys [id text replies reply-count show-replies] :as comment}] @@ -44,7 +49,9 @@ [:div [comment-top-metadata comment] [:div.my-2 - [:p {:dangerouslySetInnerHTML {:__html text} :class "[overflow-wrap:anywhere]"}]] + [:p + {:dangerouslySetInnerHTML {:__html text} + :class "[overflow-wrap:anywhere]"}]] [comment-bottom-metadata comment] [:div.flex.items-center.cursor-pointer {:on-click #(rf/dispatch [:comments/toggle-replies id])} @@ -54,23 +61,33 @@ [:p.font-bold "Hide replies"] [:i.fa-solid.fa-turn-up.mx-2.text-xs]] [:<> - [:p.font-bold (str reply-count (if (= reply-count 1) " reply" " replies"))] + [:p.font-bold + (str reply-count (if (= reply-count 1) " reply" " replies"))] [:i.fa-solid.fa-turn-down.mx-2.text-xs]]))]]]) (defn comments - [{:keys [comments next-page disabled?]} {:keys [uploader-name uploader-avatar url]}] + [{:keys [comments next-page]} + {:keys [uploader-name uploader-avatar url]}] (let [pagination-loading? @(rf/subscribe [:show-pagination-loading]) - service-color @(rf/subscribe [:service-color])] + service-color @(rf/subscribe [:service-color])] [:div.flex.flex-col [:div - (for [[i {:keys [replies show-replies] :as comment}] (map-indexed vector comments)] + (for [[i {:keys [replies show-replies] :as comment}] + (map-indexed vector comments)] [:div.flex.flex-col {:key i} [:div.flex - [comment-item (assoc comment :author-name uploader-name :author-avatar uploader-avatar)]] + [comment-item + (assoc comment + :author-name uploader-name + :author-avatar uploader-avatar)]] (when (and replies show-replies) [:div {:style {:marginLeft "32px"}} (for [[i reply] (map-indexed vector (:items replies))] - ^{:key i} [comment-item (assoc reply :author-name uploader-name :author-avatar uploader-avatar)])])])] + ^{:key i} + [comment-item + (assoc reply + :author-name uploader-name + :author-avatar uploader-avatar)])])])] (when (:url next-page) (if pagination-loading? [layout/loading-icon service-color] diff --git a/src/frontend/tubo/components/items.cljs b/src/frontend/tubo/components/items.cljs index e23b823..605169d 100644 --- a/src/frontend/tubo/components/items.cljs +++ b/src/frontend/tubo/components/items.cljs @@ -11,41 +11,58 @@ (defn item-popover [_ _] (let [!menu-active? (r/atom nil)] - (fn [{:keys [service-id audio-streams video-streams type url bookmark-id uploader-url] :as item} bookmarks] - (let [liked? (some #(= (:url %) url) (-> bookmarks first :items)) - items (if (or (= type "stream") audio-streams video-streams) - [{:label "Add to queue" - :icon [:i.fa-solid.fa-headphones] - :on-click #(rf/dispatch [:player/switch-to-background item true])} - {:label "Play radio" - :icon [:i.fa-solid.fa-tower-cell] - :on-click #(rf/dispatch [:player/start-radio item])} - {:label (if liked? "Remove favorite" "Favorite") - :icon [:i.fa-solid.fa-heart (when (and liked? service-id) - {:style {:color (utils/get-service-color service-id)}})] - :on-click #(rf/dispatch [(if liked? :likes/remove :likes/add) item true])} - {:label "Add to playlist" - :icon [:i.fa-solid.fa-plus] - :on-click #(rf/dispatch [:modals/open [bookmarks/add-to-bookmark item]])} - (when (some #(= (:url %) url) (:items (first (filter #(= (:id %) bookmark-id) bookmarks)))) - {:label "Remove from playlist" - :icon [:i.fa-solid.fa-trash] - :on-click #(rf/dispatch [:bookmark/remove item])}) - {:label "Show channel details" - :icon [:i.fa-solid.fa-user] - :on-click #(rf/dispatch [:navigate - {:name :channel-page - :params {} - :query {:url uploader-url}}])}] - [(when (and bookmarks (some #(= (:id %) bookmark-id) (rest bookmarks))) - {:label "Remove playlist" - :icon [:i.fa-solid.fa-trash] - :on-click #(rf/dispatch [:bookmarks/remove bookmark-id true])})])] + (fn [{:keys [service-id audio-streams video-streams type url bookmark-id + uploader-url] + :as item} bookmarks] + (let [liked? (some #(= (:url %) url) + (-> bookmarks + first + :items)) + items + (if (or (= type "stream") audio-streams video-streams) + [{:label "Add to queue" + :icon [:i.fa-solid.fa-headphones] + :on-click #(rf/dispatch [:player/switch-to-background item + true])} + {:label "Play radio" + :icon [:i.fa-solid.fa-tower-cell] + :on-click #(rf/dispatch [:player/start-radio item])} + {:label (if liked? "Remove favorite" "Favorite") + :icon [:i.fa-solid.fa-heart + (when (and liked? service-id) + {:style {:color (utils/get-service-color + service-id)}})] + :on-click #(rf/dispatch [(if liked? :likes/remove :likes/add) + item true])} + {:label "Add to playlist" + :icon [:i.fa-solid.fa-plus] + :on-click #(rf/dispatch [:modals/open + [bookmarks/add-to-bookmark item]])} + (when (some #(= (:url %) url) + (:items (first (filter #(= (:id %) bookmark-id) + bookmarks)))) + {:label "Remove from playlist" + :icon [:i.fa-solid.fa-trash] + :on-click #(rf/dispatch [:bookmark/remove item])}) + {:label "Show channel details" + :icon [:i.fa-solid.fa-user] + :on-click #(rf/dispatch [:navigate + {:name :channel-page + :params {} + :query {:url uploader-url}}])}] + [(when (and bookmarks + (some #(= (:id %) bookmark-id) (rest bookmarks))) + {:label "Remove playlist" + :icon [:i.fa-solid.fa-trash] + :on-click #(rf/dispatch [:bookmarks/remove bookmark-id + true])})])] (when (not-empty (remove nil? items)) [layout/popover-menu !menu-active? items]))))) (defn item-content - [{:keys [url name uploader-url uploader-name subscriber-count view-count stream-count verified?] :as item} route bookmarks] + [{:keys [url name uploader-url uploader-name subscriber-count view-count + stream-count verified?] + :as item} route bookmarks] [:div (when name [:div.flex.items-center.my-2 @@ -57,9 +74,10 @@ [: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}]) + [: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]) @@ -109,5 +127,5 @@ {:class "xs:grid-cols-[repeat(auto-fill,_minmax(165px,_1fr))]"} (for [[i item] (map-indexed vector related-streams)] ^{:key i} [generic-item item bookmarks])]) - (when (and pagination-loading? (not (empty? next-page-url))) + (when (and pagination-loading? (seq next-page-url)) [layout/loading-icon service-color :text-md])])) diff --git a/src/frontend/tubo/components/layout.cljs b/src/frontend/tubo/components/layout.cljs index d0bed45..c542574 100644 --- a/src/frontend/tubo/components/layout.cljs +++ b/src/frontend/tubo/components/layout.cljs @@ -1,5 +1,6 @@ (ns tubo.components.layout (:require + [clojure.string :as str] [re-frame.core :as rf] [reitit.frontend.easy :as rfe] [reagent.core :as r] @@ -12,11 +13,13 @@ [:div.relative.min-w-full [:a.absolute.min-w-full.min-h-full.z-10 {:href route :title name}] (if thumbnail-url - [:img.object-cover.min-h-full.max-h-full.min-w-full {:src thumbnail-url :class (when rounded? :rounded)}] + [:img.object-cover.min-h-full.max-h-full.min-w-full + {:src thumbnail-url :class (when rounded? :rounded)}] [:div.bg-gray-300.flex.min-h-full.min-w-full.justify-center.items-center.rounded [:i.fa-solid.fa-image.text-3xl.text-white]]) (when duration - [:div.rounded.p-2.absolute {:style {:bottom 5 :right 5 :background "rgba(0,0,0,.7)" :zIndex "0"}} + [:div.rounded.p-2.absolute + {:style {:bottom 5 :right 5 :background "rgba(0,0,0,.7)" :zIndex "0"}} [:p.text-white.text-md (if (= duration 0) "LIVE" @@ -66,14 +69,15 @@ (conj (when uploader-url [:a.flex-auto.flex.min-h-full.min-w-full.max-h-full.max-w-full - {:href (rfe/href :channel-page nil {:url uploader-url}) + {:href (rfe/href :channel-page nil {:url uploader-url}) :title uploader-name - :key uploader-url}]) + :key uploader-url}]) [:img.flex-auto.rounded-full.object-cover.max-w-full.min-h-full {:src uploader-avatar :alt uploader-name :key uploader-name}])])) (defn button - [label on-click left-icon right-icon & {:keys [button-classes label-classes icon-classes]}] + [label on-click left-icon right-icon & + {:keys [button-classes label-classes icon-classes]}] [:button.px-4.rounded-3xl.py-1.outline-none.focus:ring-transparent.whitespace-nowrap {:on-click on-click :class button-classes} (when left-icon @@ -86,21 +90,24 @@ [label on-click left-icon right-icon] [button label on-click left-icon right-icon :button-classes ["bg-stone-800" "dark:bg-white"] - :label-classes ["text-neutral-300" "dark:text-neutral-900"]]) + :label-classes ["text-neutral-300" "dark:text-neutral-900"]]) (defn secondary-button [label on-click left-icon right-icon] [button label on-click left-icon right-icon - :button-classes ["bg-neutral-100" "dark:bg-transparent" "border" "border-neutral-300" "dark:border-stone-700"] + :button-classes + ["bg-neutral-100" "dark:bg-transparent" "border" "border-neutral-300" + "dark:border-stone-700"] :label-classes ["text-neutral-500" "dark:text-white"]]) -(defn generic-input [label & children] +(defn generic-input + [label & children] [:div.w-full.flex.justify-between.items-center.py-2.gap-x-4 [:label label] (map-indexed #(with-meta %2 {:key %1}) children)]) (defn text-input - [label key value on-change placeholder] + [label _key value on-change placeholder] [generic-input label [:input.text-black {:type "text" @@ -109,7 +116,7 @@ :placeholder placeholder}]]) (defn boolean-input - [label key value on-change] + [label _key value on-change] [generic-input label [:input {:type "checkbox" @@ -118,27 +125,34 @@ :on-change on-change}]]) (defn select-input - [label key value options on-change] + [label _key value options on-change] [generic-input label [:select.focus:ring-transparent.bg-transparent.font-bold {:value value :on-change on-change} (for [[i option] (map-indexed vector options)] - ^{:key i} [:option.dark:bg-neutral-900.border-none {:value option :key i} option])]]) + ^{:key i} + [:option.dark:bg-neutral-900.border-none {:value option :key i} + option])]]) (defn menu-item [{:keys [label icon on-click link] :as item}] (let [content [:<> - [:span.text-xs.min-w-4.w-4.flex.justify-center.items-center icon] + [:span.text-xs.min-w-4.w-4.flex.justify-center.items-center + icon] [:span.whitespace-nowrap label]] - classes ["relative" "flex" "items-center" "gap-x-3" "hover:bg-neutral-200" + classes ["relative" "flex" "items-center" "gap-x-3" + "hover:bg-neutral-200" "dark:hover:bg-stone-800" "py-2" "px-3" "rounded"]] (if link - [:a {:href (:route link) :target (when (:external? link) "_blank") - :class (clojure.string/join " " classes)} + [:a + {:href (:route link) + :target (when (:external? link) "_blank") + :class (str/join " " classes)} content] - [:li {:on-click on-click - :class (clojure.string/join " " classes)} + [:li + {:on-click on-click + :class (str/join " " classes)} (if (vector? item) item content)]))) (defn menu @@ -176,7 +190,7 @@ (map-indexed #(with-meta %2 {:key %1}) content))]) (defn show-more-container - [open? text on-open] + [_open? _text _on-open] (let [!text-container (atom nil) !resize-observer (atom nil) text-clamped? (r/atom nil)] @@ -186,11 +200,11 @@ (when @!text-container (.observe (reset! !resize-observer - (js/ResizeObserver. - #(let [target (.-target (first %))] - (reset! text-clamped? - (> (.-scrollHeight target) - (.-clientHeight target)))))) + (js/ResizeObserver. + #(let [target (.-target (first %))] + (reset! text-clamped? + (> (.-scrollHeight target) + (.-clientHeight target)))))) @!text-container))) :component-will-unmount #(when (and @!resize-observer @!text-container) @@ -200,17 +214,20 @@ [:div.py-3.min-w-full [:span.text-clip.pr-2 {:dangerouslySetInnerHTML {:__html text} - :class (when-not open? "line-clamp-2") - :ref #(reset! !text-container %)}] + :class (when-not open? "line-clamp-2") + :ref #(reset! !text-container %)}] (when (or @text-clamped? open?) - [:button.font-bold {:on-click on-open} (str "show " (if open? "less" "more"))])])}))) + [:button.font-bold {:on-click on-open} + (str "show " (if open? "less" "more"))])])}))) -(defn error [{:keys [failure parse-error status status-text]} cb] +(defn error + [{:keys [_failure parse-error status status-text]} cb] [:div.flex.flex-auto.h-full.items-center.justify-center.p-5 [:div.flex.flex-col.gap-y-6.border-border-neutral-300.rounded.dark:border-stone-700.bg-neutral-300.dark:bg-neutral-800.p-5 [:div.flex.items-center.gap-2.text-xl [:i.fa-solid.fa-circle-exclamation] - [:h3.font-bold (str status (when (and status status-text) ": ") status-text)]] + [:h3.font-bold + (str status (when (and status status-text) ": ") status-text)]] (when parse-error [:span (:status-text parse-error)]) [:div.flex.justify-center.gap-x-3 diff --git a/src/frontend/tubo/components/navigation.cljs b/src/frontend/tubo/components/navigation.cljs index 72f733f..c5e58ed 100644 --- a/src/frontend/tubo/components/navigation.cljs +++ b/src/frontend/tubo/components/navigation.cljs @@ -1,7 +1,6 @@ (ns tubo.components.navigation (:require [re-frame.core :as rf] - [reagent.core :as r] [reitit.frontend.easy :as rfe] [tubo.components.layout :as layout] [tubo.kiosks.views :as kiosks] @@ -17,35 +16,43 @@ [:span {:class (when active? "font-bold")} label]]]) (defn mobile-nav - [show-mobile-nav? service-color services available-kiosks & {:keys [service-id] :as kiosk-args}] + [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}} + [: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))])]] + ^{: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"]]]]]) + [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]) + 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]) + 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 diff --git a/src/frontend/tubo/components/player.cljs b/src/frontend/tubo/components/player.cljs index e59d3b5..0373c27 100644 --- a/src/frontend/tubo/components/player.cljs +++ b/src/frontend/tubo/components/player.cljs @@ -1,19 +1,22 @@ (ns tubo.components.player (:require + [clojure.string :as str] [reagent.core :as r] [re-frame.core :as rf] - [reagent.core :as r] - [reagent.dom :as rdom] ["@vidstack/react" :refer (MediaPlayer MediaProvider Poster)] - ["@vidstack/react/player/layouts/default" :refer (defaultLayoutIcons DefaultVideoLayout DefaultAudioLayout)])) + ["@vidstack/react/player/layouts/default" :refer + (defaultLayoutIcons DefaultVideoLayout DefaultAudioLayout)])) (defn get-video-player-sources [available-streams service-id] (if available-streams (if (= service-id 3) - (map (fn [{:keys [content]}] {:src content :type "video/mp4"}) (reverse available-streams)) + (map (fn [{:keys [content]}] {:src content :type "video/mp4"}) + (reverse available-streams)) (->> available-streams - (filter #(and (not= (:format %) "WEBMA_OPUS") (not= (:format %) "OPUS") (not= (:format %) "M4A"))) + (filter #(and (not= (:format %) "WEBMA_OPUS") + (not= (:format %) "OPUS") + (not= (:format %) "M4A"))) (sort-by :bitrate) (#(if (empty? (filter (fn [x] (= (:format x) "MP3")) %)) (reverse %) @@ -23,28 +26,34 @@ [])) (defn video-player - [stream !player] - (let [!elapsed-time @(rf/subscribe [:elapsed-time]) + [_stream _!player] + (let [!elapsed-time @(rf/subscribe [:elapsed-time]) !main-player-first? (r/atom true)] (r/create-class {:component-will-unmount #(rf/dispatch [:main-player/ready false]) :reagent-render - (fn [{:keys [name video-streams audio-streams thumbnail-url service-id]} !player] + (fn [{:keys [name video-streams audio-streams thumbnail-url service-id]} + !player] (let [show-main-player? @(rf/subscribe [:main-player/show])] [:> MediaPlayer {:title name - :src (get-video-player-sources (into video-streams audio-streams) service-id) + :src (get-video-player-sources (into video-streams + audio-streams) + service-id) :poster thumbnail-url :class "w-full xl:w-3/5 overflow-hidden" :playsInline true :ref #(reset! !player %) - :loop (when show-main-player? (= @(rf/subscribe [:loop-playback]) :stream)) + :loop (when show-main-player? + (= @(rf/subscribe [:loop-playback]) :stream)) :onSeeked (when show-main-player? #(reset! !elapsed-time (.-currentTime @!player))) :onTimeUpdate (when show-main-player? #(reset! !elapsed-time (.-currentTime @!player))) :onEnded #(when show-main-player? - (rf/dispatch [:queue/change-pos (inc @(rf/subscribe [:queue-pos]))]) + (rf/dispatch [:queue/change-pos + (inc @(rf/subscribe + [:queue-pos]))]) (reset! !elapsed-time 0)) :onLoadedData (fn [] (when show-main-player? @@ -56,9 +65,10 @@ :onSourceChange #(when-not @!main-player-first? (reset! !elapsed-time 0))} [:> MediaProvider - [:> Poster {:src thumbnail-url - :alt name - :class :vds-poster}]] + [:> Poster + {:src thumbnail-url + :alt name + :class :vds-poster}]] [:> DefaultVideoLayout {:icons defaultLayoutIcons}]]))}))) (defn get-audio-player-sources @@ -71,13 +81,13 @@ [])) (defn audio-player - [stream !player] + [_stream _!player] (let [!elapsed-time @(rf/subscribe [:elapsed-time]) !bg-player-first? (r/atom nil)] (r/create-class - {:component-will-unmount #(rf/dispatch [:background-player/ready false]) + {:component-will-unmount #(rf/dispatch [:bg-player/ready false]) :reagent-render - (fn [{:keys [name audio-streams thumbnail-url]} !player] + (fn [{:keys [name audio-streams]} !player] [:> MediaPlayer {:title name :class "invisible fixed" @@ -86,19 +96,20 @@ :viewType "audio" :ref #(reset! !player %) :loop (= @(rf/subscribe [:loop-playback]) :stream) - :onCanPlay #(rf/dispatch [:background-player/ready true]) + :onCanPlay #(rf/dispatch [:bg-player/ready true]) :onSeeked #(reset! !elapsed-time (.-currentTime @!player)) :onTimeUpdate #(reset! !elapsed-time (.-currentTime @!player)) :onEnded (fn [] - (rf/dispatch [:queue/change-pos (inc @(rf/subscribe [:queue-pos]))]) + (rf/dispatch [:queue/change-pos + (inc @(rf/subscribe [:queue-pos]))]) (reset! !elapsed-time 0)) - :onPlay #(rf/dispatch [:background-player/play]) + :onPlay #(rf/dispatch [:bg-player/play]) :onReplay (fn [] - (rf/dispatch [:background-player/set-paused false]) + (rf/dispatch [:bg-player/set-paused false]) (reset! !elapsed-time 0)) - :onPause #(rf/dispatch [:background-player/set-paused true]) + :onPause #(rf/dispatch [:bg-player/set-paused true]) :onLoadedData (fn [] - (rf/dispatch [:background-player/start]) + (rf/dispatch [:bg-player/start]) (when-not @!bg-player-first? (reset! !bg-player-first? true))) :onSourceChange #(when @!bg-player-first? @@ -107,7 +118,8 @@ [:> DefaultAudioLayout {:icons defaultLayoutIcons}]])}))) (defonce base-slider-classes - ["h-2" "cursor-pointer" "appearance-none" "bg-neutral-300" "dark:bg-neutral-600" + ["h-2" "cursor-pointer" "appearance-none" "bg-neutral-300" + "dark:bg-neutral-600" "rounded-full" "overflow-hidden" "focus:outline-none" "[&::-webkit-slider-thumb]:appearance-none" "[&::-webkit-slider-thumb]:border-0" @@ -124,34 +136,50 @@ (defn get-slider-shadow-classes [service-color] (case service-color - "#cc0000" ["[&::-webkit-slider-thumb]:shadow-[#cc0000]" "[&::-moz-range-thumb]:shadow-[#cc0000]"] - "#ff7700" ["[&::-webkit-slider-thumb]:shadow-[#ff7700]" "[&::-moz-range-thumb]:shadow-[#ff7700]"] - "#333333" ["[&::-webkit-slider-thumb]:shadow-[#333333]" "[&::-moz-range-thumb]:shadow-[#333333]"] - "#F2690D" ["[&::-webkit-slider-thumb]:shadow-[#F2690D]" "[&::-moz-range-thumb]:shadow-[#F2690D]"] - "#629aa9" ["[&::-webkit-slider-thumb]:shadow-[#629aa9]" "[&::-moz-range-thumb]:shadow-[#629aa9]"] - ["[&::-webkit-slider-thumb]:shadow-neutral-300" "[&::-moz-range-thumb]:shadow-neutral-300"])) + "#cc0000" ["[&::-webkit-slider-thumb]:shadow-[#cc0000]" + "[&::-moz-range-thumb]:shadow-[#cc0000]"] + "#ff7700" ["[&::-webkit-slider-thumb]:shadow-[#ff7700]" + "[&::-moz-range-thumb]:shadow-[#ff7700]"] + "#333333" ["[&::-webkit-slider-thumb]:shadow-[#333333]" + "[&::-moz-range-thumb]:shadow-[#333333]"] + "#F2690D" ["[&::-webkit-slider-thumb]:shadow-[#F2690D]" + "[&::-moz-range-thumb]:shadow-[#F2690D]"] + "#629aa9" ["[&::-webkit-slider-thumb]:shadow-[#629aa9]" + "[&::-moz-range-thumb]:shadow-[#629aa9]"] + ["[&::-webkit-slider-thumb]:shadow-neutral-300" + "[&::-moz-range-thumb]:shadow-neutral-300"])) (defn get-slider-bg-classes [service-color] (case service-color - "#cc0000" ["[&::-webkit-slider-thumb]:bg-[#cc0000]" "[&::-moz-range-thumb]:bg-[#cc0000]"] - "#ff7700" ["[&::-webkit-slider-thumb]:bg-[#ff7700]" "[&::-moz-range-thumb]:bg-[#ff7700]"] - "#333333" ["[&::-webkit-slider-thumb]:bg-[#333333]" "[&::-moz-range-thumb]:bg-[#333333]"] - "#F2690D" ["[&::-webkit-slider-thumb]:bg-[#F2690D]" "[&::-moz-range-thumb]:bg-[#F2690D]"] - "#629aa9" ["[&::-webkit-slider-thumb]:bg-[#629aa9]" "[&::-moz-range-thumb]:bg-[#629aa9]"] - ["[&::-webkit-slider-thumb]:bg-neutral-300" "[&::-moz-range-thumb]:bg-neutral-300"])) + "#cc0000" ["[&::-webkit-slider-thumb]:bg-[#cc0000]" + "[&::-moz-range-thumb]:bg-[#cc0000]"] + "#ff7700" ["[&::-webkit-slider-thumb]:bg-[#ff7700]" + "[&::-moz-range-thumb]:bg-[#ff7700]"] + "#333333" ["[&::-webkit-slider-thumb]:bg-[#333333]" + "[&::-moz-range-thumb]:bg-[#333333]"] + "#F2690D" ["[&::-webkit-slider-thumb]:bg-[#F2690D]" + "[&::-moz-range-thumb]:bg-[#F2690D]"] + "#629aa9" ["[&::-webkit-slider-thumb]:bg-[#629aa9]" + "[&::-moz-range-thumb]:bg-[#629aa9]"] + ["[&::-webkit-slider-thumb]:bg-neutral-300" + "[&::-moz-range-thumb]:bg-neutral-300"])) -(defn time-slider [!player !elapsed-time service-color] - (let [styles (concat base-slider-classes - (get-slider-bg-classes service-color) - (get-slider-shadow-classes service-color)) - bg-player-ready? @(rf/subscribe [:background-player/ready])] +(defn time-slider + [!player !elapsed-time service-color] + (let [styles (concat base-slider-classes + (get-slider-bg-classes service-color) + (get-slider-shadow-classes service-color)) + bg-player-ready? @(rf/subscribe [:bg-player/ready])] [:input.w-full {:class styles :type "range" :on-input #(reset! !elapsed-time (.. % -target -value)) - :on-change #(when (and bg-player-ready? @!player) (set! (.-currentTime @!player) @!elapsed-time)) - :max (if (and bg-player-ready? @!player (not (js/isNaN (.-duration @!player)))) + :on-change #(when (and bg-player-ready? @!player) + (set! (.-currentTime @!player) @!elapsed-time)) + :max (if (and bg-player-ready? + @!player + (not (js/isNaN (.-duration @!player)))) (.floor js/Math (.-duration @!player)) 100) :value @!elapsed-time}])) @@ -159,29 +187,30 @@ (defn button [& {:keys [icon on-click disabled? show-on-mobile? extra-classes]}] [:button.outline-none.focus:ring-transparent.px-2.pt-1 - {:class (into (into (when disabled? [:opacity-50 :cursor-auto]) - (when-not show-on-mobile? [:hidden :lg:block])) - extra-classes) + {:class (into (into (when disabled? [:opacity-50 :cursor-auto]) + (when-not show-on-mobile? [:hidden :lg:block])) + extra-classes) :on-click on-click} icon]) (defn loop-button [loop-playback color show-on-mobile?] [button - :icon [:div.relative.flex.items-center - [:i.fa-solid.fa-repeat - {:style {:color (when loop-playback color)}}] - (when (= loop-playback :stream) - [:div.absolute.w-full.h-full.flex.justify-center.items-center.font-bold - {:class "text-[6px]" - :style {:color (when loop-playback color)}} - "1"])] + :icon + [:div.relative.flex.items-center + [:i.fa-solid.fa-repeat + {:style {:color (when loop-playback color)}}] + (when (= loop-playback :stream) + [:div.absolute.w-full.h-full.flex.justify-center.items-center.font-bold + {:class "text-[6px]" + :style {:color (when loop-playback color)}} + "1"])] :on-click #(rf/dispatch [:player/loop]) :extra-classes [:text-sm] :show-on-mobile? show-on-mobile?]) (defn volume-slider - [player volume-level muted? service-color] + [_player _volume-level _muted? _service-color] (let [show-slider? (r/atom nil)] (fn [player volume-level muted? service-color] (let [styles (concat ["rotate-[270deg]"] @@ -192,13 +221,15 @@ {:on-mouse-over #(reset! show-slider? true) :on-mouse-out #(reset! show-slider? false)} [button - :icon (if muted? [:i.fa-solid.fa-volume-xmark] [:i.fa-solid.fa-volume-low]) - :on-click #(rf/dispatch [:background-player/mute (not muted?) player]) + :icon + (if muted? [:i.fa-solid.fa-volume-xmark] [:i.fa-solid.fa-volume-low]) + :on-click #(rf/dispatch [:bg-player/mute (not muted?) player]) :extra-classes [:pl-3 :pr-2]] (when @show-slider? [:input.absolute.w-24.ml-2.m-1.bottom-16 - {:class (clojure.string/join " " styles) + {:class (str/join " " styles) :type "range" - :on-input #(rf/dispatch [:player/change-volume (.. % -target -value) player]) + :on-input #(rf/dispatch [:player/change-volume + (.. % -target -value) player]) :max 100 :value volume-level}])])))) diff --git a/src/frontend/tubo/core.cljs b/src/frontend/tubo/core.cljs index 2153e50..c9f7e7c 100644 --- a/src/frontend/tubo/core.cljs +++ b/src/frontend/tubo/core.cljs @@ -16,7 +16,4 @@ (routes/start-routes!) (.render root (r/as-element [(fn [] views/app)]))) -(defn ^:export init - [] - (rf/dispatch-sync [:initialize-db]) - (mount-root)) +(defn ^:export init [] (rf/dispatch-sync [:initialize-db]) (mount-root)) diff --git a/src/frontend/tubo/events.cljs b/src/frontend/tubo/events.cljs index d9740f9..cfa5a78 100644 --- a/src/frontend/tubo/events.cljs +++ b/src/frontend/tubo/events.cljs @@ -30,26 +30,31 @@ (fn [{:keys [store]} _] (let [if-nil #(if (nil? %1) %2 %1)] {:db - {:paused true - :muted (:muted store) - :queue (if-nil (:queue store) []) - :service-id (if-nil (:service-id store) 0) - :loop-playback (if-nil (:loop-playback store) :playlist) - :queue-pos (if-nil (:queue-pos store) 0) - :volume-level (if-nil (:volume-level store) 100) - :background-player/show (:background-player/show store) - :bookmarks - (if-nil (:bookmarks store) [{:id (nano-id) :name "Liked Streams"}]) - :settings - {:theme (if-nil (:theme store) "auto") - :show-comments (if-nil (:show-comments store) true) - :show-related (if-nil (:show-related store) true) - :show-description (if-nil (:show-description store) true) - :default-service (if-nil (:default-service store) - {:service-id 0 - :id "YouTube" - :default-kiosk "Trending" - :available-kiosks ["Trending"]})}}}))) + {:paused true + :muted (:muted store) + :queue (if-nil (:queue store) []) + :service-id (if-nil (:service-id store) 0) + :loop-playback (if-nil (:loop-playback store) :playlist) + :queue-pos (if-nil (:queue-pos store) 0) + :volume-level (if-nil (:volume-level store) 100) + :bg-player/show (:bg-player/show store) + :bookmarks (if-nil (:bookmarks store) + [{:id (nano-id) :name "Liked Streams"}]) + :settings {:theme (if-nil (:theme store) "auto") + :show-comments (if-nil (:show-comments store) + true) + :show-related (if-nil (:show-related store) + true) + :show-description (if-nil (:show-description + store) + true) + :default-service (if-nil + (:default-service store) + {:service-id 0 + :id "YouTube" + :default-kiosk "Trending" + :available-kiosks + ["Trending"]})}}}))) (rf/reg-fx :scroll-to-top @@ -74,7 +79,7 @@ (rf/reg-event-fx :scroll-into-view - (fn [{:keys [db]} [_ element]] + (fn [_ [_ element]] {:scroll-into-view! element})) (rf/reg-fx @@ -118,10 +123,12 @@ :fx [(when (:main-player/show db) [:dispatch [:player/switch-from-main]]) [:dispatch [:queue/show false]] - [:dispatch [:services/fetch-all - [:services/load] [:bad-response]]] - [:dispatch [:kiosks/fetch-all (:service-id db) - [:kiosks/load] [:bad-response]]]]}))) + [:dispatch + [:services/fetch-all + [:services/load] [:bad-response]]] + [:dispatch + [:kiosks/fetch-all (:service-id db) + [:kiosks/load] [:bad-response]]]]}))) (defonce timeouts! (r/atom {})) @@ -132,18 +139,20 @@ (js/clearTimeout existing) (swap! timeouts! dissoc id)) (when (some? event) - (swap! timeouts! assoc id - (js/setTimeout #(rf/dispatch event) time))))) + (swap! timeouts! assoc + id + (js/setTimeout #(rf/dispatch event) time))))) (rf/reg-event-fx :bad-response - (fn [{:keys [db]} [_ res]] + (fn [_ [_ res]] {:fx [[:dispatch [:notifications/add res]]]})) (rf/reg-fx :file-download (fn [{:keys [data name mime-type]}] - (let [file (.createObjectURL js/URL (js/Blob. (array data) {:type mime-type})) + (let [file (.createObjectURL js/URL + (js/Blob. (array data) {:type mime-type})) !link (.createElement js/document "a")] (set! (.-href !link) file) (set! (.-download !link) name) @@ -155,15 +164,20 @@ (fn [{:keys [db]} [_ res]] (let [updated-db (assoc db :services (js->clj res :keywordize-keys true)) service-id (:id (first - (filter #(= (-> db :settings :default-service :id) - (-> % :info :name)) + (filter #(= (-> db + :settings + :default-service + :id) + (-> % + :info + :name)) (:services updated-db))))] {:fx [[:dispatch [:kiosks/fetch-default-page service-id]] [:dispatch [:services/change-id service-id]]]}))) (rf/reg-event-fx :fetch-homepage - (fn [{:keys [db]} _] + (fn [_ _] {:fx [[:dispatch [:services/fetch-all [:load-homepage] [:bad-response]]]]})) (rf/reg-event-fx diff --git a/src/frontend/tubo/kiosks/events.cljs b/src/frontend/tubo/kiosks/events.cljs index 5b0518b..720715e 100644 --- a/src/frontend/tubo/kiosks/events.cljs +++ b/src/frontend/tubo/kiosks/events.cljs @@ -11,28 +11,34 @@ (rf/reg-event-fx :kiosks/fetch - (fn [{:keys [db]} [_ service-id kiosk-id on-success on-error params]] - (api/get-request (str "/services/" service-id "/kiosks/" + (fn [_ [_ service-id kiosk-id on-success on-error params]] + (api/get-request (str "/services/" service-id + "/kiosks/" (js/encodeURIComponent kiosk-id)) - on-success on-error params))) + on-success + on-error + params))) (rf/reg-event-fx :kiosks/fetch-default - (fn [{:keys [db]} [_ service-id on-success on-error]] + (fn [_ [_ service-id on-success on-error]] (api/get-request (str "/services/" service-id "/default-kiosk") - on-success on-error))) + on-success + on-error))) (rf/reg-event-fx :kiosks/fetch-all - (fn [{:keys [db]} [_ id on-success on-error]] + (fn [_ [_ id on-success on-error]] (api/get-request (str "/services/" id "/kiosks") - on-success on-error))) + on-success + on-error))) (rf/reg-event-fx :kiosks/load-page (fn [{:keys [db]} [_ res]] (let [kiosk-res (js->clj res :keywordize-keys true)] - {:db (assoc db :kiosk kiosk-res + {:db (assoc db + :kiosk kiosk-res :show-page-loading false) :fx [[:dispatch [:services/fetch kiosk-res]] [:document-title (:id kiosk-res)]]}))) @@ -40,11 +46,13 @@ (rf/reg-event-fx :kiosks/bad-page-response (fn [{:keys [db]} [_ service-id kiosk-id res]] - {:fx [[:dispatch [:change-view - #(layout/error - res (if kiosk-id - [:kiosks/fetch-page service-id kiosk-id] - [:kiosks/fetch-default-page service-id]))]]] + {:fx [[:dispatch + [:change-view + #(layout/error + res + (if kiosk-id + [:kiosks/fetch-page service-id kiosk-id] + [:kiosks/fetch-default-page service-id]))]]] :db (assoc db :show-page-loading false)})) (rf/reg-event-fx @@ -52,38 +60,50 @@ (fn [{:keys [db]} [_ service-id kiosk-id]] {:db (assoc db :show-page-loading true - :kiosk nil) - :fx [[:dispatch (if kiosk-id - [:kiosks/fetch service-id kiosk-id - [:kiosks/load-page] [:kiosks/bad-page-response service-id kiosk-id]] - [:kiosks/fetch-default-page service-id])]]})) + :kiosk nil) + :fx [[:dispatch + (if kiosk-id + [:kiosks/fetch service-id kiosk-id + [:kiosks/load-page] + [:kiosks/bad-page-response service-id kiosk-id]] + [:kiosks/fetch-default-page service-id])]]})) (rf/reg-event-fx :kiosks/fetch-default-page (fn [{:keys [db]} [_ service-id]] (let [default-kiosk-id (when (= (js/parseInt service-id) - (-> db :settings :default-service :service-id)) - (-> db :settings :default-service :default-kiosk))] - {:fx [[:dispatch (if default-kiosk-id - [:kiosks/fetch-page service-id default-kiosk-id] - [:kiosks/fetch-default service-id - [:kiosks/load-page] [:kiosks/bad-page-response service-id nil]])]]}))) + (-> db + :settings + :default-service + :service-id)) + (-> db + :settings + :default-service + :default-kiosk))] + {:fx [[:dispatch + (if default-kiosk-id + [:kiosks/fetch-page service-id default-kiosk-id] + [:kiosks/fetch-default service-id + [:kiosks/load-page] + [:kiosks/bad-page-response service-id nil]])]]}))) (rf/reg-event-fx :kiosks/change-page - (fn [{:keys [db]} [_ service-id]] + (fn [_ [_ service-id]] {:fx [[:dispatch [:services/change-id service-id]] [:dispatch - [:navigate {:name :kiosk-page - :params {} - :query {:serviceId service-id}}]]]})) + [:navigate + {:name :kiosk-page + :params {} + :query {:serviceId service-id}}]]]})) (rf/reg-event-db :kiosks/load-paginated (fn [db [_ res]] (-> db - (update-in [:kiosk :related-streams] #(into %1 %2) + (update-in [:kiosk :related-streams] + #(into %1 %2) (:related-streams (js->clj res :keywordize-keys true))) (assoc-in [:kiosk :next-page] (:next-page (js->clj res :keywordize-keys true))) @@ -95,6 +115,7 @@ (if (empty? next-page-url) {:db (assoc db :show-pagination-loading false)} {:db (assoc db :show-pagination-loading true) - :fx [[:dispatch [:kiosks/fetch service-id kiosk-id - [:kiosks/load-paginated] [:bad-response] - {:nextPage (js/encodeURIComponent next-page-url)}]]]}))) + :fx [[:dispatch + [:kiosks/fetch service-id kiosk-id + [:kiosks/load-paginated] [:bad-response] + {:nextPage (js/encodeURIComponent next-page-url)}]]]}))) diff --git a/src/frontend/tubo/kiosks/views.cljs b/src/frontend/tubo/kiosks/views.cljs index 17e0e76..bd6c284 100644 --- a/src/frontend/tubo/kiosks/views.cljs +++ b/src/frontend/tubo/kiosks/views.cljs @@ -7,7 +7,7 @@ (defn kiosk-active? [& {:keys [kiosk kiosk-id service-id default-service default-kiosk path]}] - (or (and (= kiosk-id kiosk)) + (or (= kiosk-id kiosk) (and (= path "/kiosk") (not kiosk-id) (not= (js/parseInt service-id) @@ -22,19 +22,21 @@ [:ul.flex.items-center.px-4.text-white (for [kiosk kiosks] [:li.px-3 {:key kiosk} - [:a {:href (rfe/href :kiosk-page nil {:serviceId service-id - :kioskId kiosk}) - :class (when (kiosk-active? (assoc kiosk-args :kiosk kiosk)) - :font-bold)} + [:a + {:href (rfe/href :kiosk-page + nil + {:serviceId service-id + :kioskId kiosk}) + :class (when (kiosk-active? (assoc kiosk-args :kiosk kiosk)) + :font-bold)} kiosk]])]) (defn kiosk - [{{:keys [serviceId kioskId]} :query-params}] - (let [{:keys [id url related-streams - next-page]} @(rf/subscribe [:kiosk]) - next-page-url (:url next-page) - service-color @(rf/subscribe [:service-color]) - service-id (or @(rf/subscribe [:service-id]) serviceId) + [{{: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])) diff --git a/src/frontend/tubo/modals/events.cljs b/src/frontend/tubo/modals/events.cljs index 4a88a5b..971fcac 100644 --- a/src/frontend/tubo/modals/events.cljs +++ b/src/frontend/tubo/modals/events.cljs @@ -16,21 +16,28 @@ (rf/reg-event-fx :modals/hide (fn [{:keys [db]} _] - {:db (update db :modals - #(map-indexed (fn [i modal] - (if (= i (- (count %) 1)) - (assoc modal :show? false :child nil))) - %)) + {:db (update db + :modals + #(map-indexed + (fn [i modal] + (when (= i (- (count %) 1)) + (assoc modal :show? false :child nil))) + %)) :body-overflow false})) (rf/reg-event-fx :modals/close (fn [{:keys [db]} _] - {:fx [[:dispatch [:modals/delete (-> (:modals db) last :id)]]] + {:fx [[:dispatch + [:modals/delete + (-> (:modals db) + last + :id)]]] :body-overflow false})) (rf/reg-event-fx :modals/open (fn [_ [_ child]] - {:fx [[:dispatch [:modals/add {:show? true :child child :id (nano-id)}]]] + {:fx [[:dispatch + [:modals/add {:show? true :child child :id (nano-id)}]]] :body-overflow true})) diff --git a/src/frontend/tubo/modals/views.cljs b/src/frontend/tubo/modals/views.cljs index e4c3aef..df59034 100644 --- a/src/frontend/tubo/modals/views.cljs +++ b/src/frontend/tubo/modals/views.cljs @@ -28,7 +28,7 @@ (defn modal [] (fn [] - (let [modals @(rf/subscribe [:modals]) + (let [modals @(rf/subscribe [:modals]) visible-modal (last (filter :show? modals))] (when visible-modal [modal-panel visible-modal])))) diff --git a/src/frontend/tubo/notifications/events.cljs b/src/frontend/tubo/notifications/events.cljs index 2ecbf2e..120e896 100644 --- a/src/frontend/tubo/notifications/events.cljs +++ b/src/frontend/tubo/notifications/events.cljs @@ -6,21 +6,27 @@ (rf/reg-event-fx :notifications/add (fn [{:keys [db]} [_ data time]] - (let [id (nano-id) - updated-db (update db :notifications #(into [] (conj %1 %2)) + (let [id (nano-id) + updated-db (update db + :notifications + #(into [] (conj %1 %2)) (assoc data :id id))] {:db updated-db :fx (if (false? time) - [] - [[:timeout {:id id - :event [:notifications/remove id] - :time (or time 2000)}]])}))) + [] + [[:timeout + {:id id + :event [:notifications/remove id] + :time (or time 2000)}]])}))) (rf/reg-event-db :notifications/remove (fn [db [_ id]] - (update db :notifications #(remove (fn [notification] - (= (:id notification) id)) %)))) + (update db + :notifications + #(remove (fn [notification] + (= (:id notification) id)) + %)))) (rf/reg-event-db :notifications/clear diff --git a/src/frontend/tubo/notifications/views.cljs b/src/frontend/tubo/notifications/views.cljs index 6c8a09a..7d0ba98 100644 --- a/src/frontend/tubo/notifications/views.cljs +++ b/src/frontend/tubo/notifications/views.cljs @@ -9,7 +9,8 @@ {:class (case failure :success ["bg-green-600/90" :text-white] :error ["bg-red-600/90" :text-white] - ["dark:bg-stone-800" "dark:text-white" :bg-neutral-300 :text-neutral-800])} + ["dark:bg-stone-800" "dark:text-white" :bg-neutral-300 + :text-neutral-800])} [:div.flex.items-center.gap-x-4 (case failure :success [:i.fa-solid.fa-circle-check] @@ -20,7 +21,8 @@ {:on-click #(rf/dispatch [:notifications/remove (:id notification)])} [:i.fa-solid.fa-close]] - [:span.font-bold (str status (when (and status status-text) ": ") status-text)] + [:span.font-bold + (str status (when (and status status-text) ": ") status-text)] (when parse-error [:span.line-clamp-1 (:status-text parse-error)])]]])) diff --git a/src/frontend/tubo/player/events.cljs b/src/frontend/tubo/player/events.cljs index f8476c3..375c055 100644 --- a/src/frontend/tubo/player/events.cljs +++ b/src/frontend/tubo/player/events.cljs @@ -1,7 +1,5 @@ (ns tubo.player.events (:require - [tubo.stream.views :as stream] - [tubo.utils :as utils] [goog.object :as gobj] [re-frame.core :as rf] [vimsical.re-frame.cofx.inject :as inject])) @@ -20,7 +18,7 @@ (rf/reg-fx :src - (fn [{:keys [player src current-pos]}] + (fn [{:keys [player src]}] (set! (.-source @player) (clj->js src)))) (rf/reg-fx @@ -34,16 +32,16 @@ (set! (.-currentTime @player) time))) (rf/reg-event-fx - :background-player/seek + :bg-player/seek [(rf/inject-cofx ::inject/sub [:player])] (fn [{:keys [db player]} [_ time]] - (when (:background-player/ready db) + (when (:bg-player/ready db) {:current-time {:time time :player player}}))) (rf/reg-event-fx :main-player/seek [(rf/inject-cofx ::inject/sub [:main-player])] - (fn [{:keys [db main-player]} [_ time]] + (fn [{:keys [main-player]} [_ time]] {:current-time {:time time :player main-player}})) (rf/reg-fx @@ -53,14 +51,14 @@ (set! (.-paused @player) paused?)))) (rf/reg-event-db - :background-player/set-paused + :bg-player/set-paused (fn [db [_ val]] (assoc db :paused val))) (rf/reg-event-fx - :background-player/pause + :bg-player/pause [(rf/inject-cofx ::inject/sub [:player])] - (fn [{:keys [db player]} [_ paused?]] + (fn [{:keys [player]} [_ paused?]] {:pause! {:paused? paused? :player player}})) @@ -73,12 +71,12 @@ :player main-player}}))) (rf/reg-event-fx - :background-player/play + :bg-player/play [(rf/inject-cofx ::inject/sub [:elapsed-time]) (rf/inject-cofx ::inject/sub [:main-player])] (fn [{:keys [db elapsed-time main-player]}] - {:fx [[:dispatch [:background-player/set-paused false]] - [:dispatch [:background-player/seek @elapsed-time]] + {:fx [[:dispatch [:bg-player/set-paused false]] + [:dispatch [:bg-player/seek @elapsed-time]] (when (and (:main-player/ready db) @main-player) [:dispatch [:main-player/pause true]])]})) @@ -86,23 +84,23 @@ :main-player/play [(rf/inject-cofx ::inject/sub [:elapsed-time]) (rf/inject-cofx ::inject/sub [:player])] - (fn [{:keys [db elapsed-time player]}] - {:fx [(when (and (:background-player/ready db) @player) - [:dispatch [:background-player/pause true]])]})) + (fn [{:keys [db player]}] + {:fx [(when (and (:bg-player/ready db) @player) + [:dispatch [:bg-player/pause true]])]})) (rf/reg-event-fx - :background-player/stop - (fn [{:keys [db]}] - {:fx [[:dispatch [:background-player/pause true]] - [:dispatch [:background-player/seek 0]]]})) + :bg-player/stop + (fn [_] + {:fx [[:dispatch [:bg-player/pause true]] + [:dispatch [:bg-player/seek 0]]]})) (rf/reg-event-fx - :background-player/start + :bg-player/start [(rf/inject-cofx ::inject/sub [:player]) (rf/inject-cofx ::inject/sub [:elapsed-time])] - (fn [{:keys [db player elapsed-time]} _] - {:fx [[:dispatch [:background-player/set-paused true]] - [:dispatch [:background-player/pause false]] + (fn [{:keys [db player]} _] + {:fx [[:dispatch [:bg-player/set-paused true]] + [:dispatch [:bg-player/pause false]] [:dispatch [:player/change-volume (:volume-level db) player]]]})) (rf/reg-event-fx @@ -110,7 +108,7 @@ [(rf/inject-cofx ::inject/sub [:elapsed-time])] (fn [{:keys [db elapsed-time]} _] {:fx [[:dispatch [:main-player/pause false]] - (when (and (:main-player/show db) (not (:background-player/ready db))) + (when (and (:main-player/show db) (not (:bg-player/ready db))) [:dispatch [:main-player/seek @elapsed-time]])]})) (rf/reg-fx @@ -122,7 +120,7 @@ (rf/reg-fx :media-session-handlers - (fn [{:keys [current-pos player stream]}] + (fn [{:keys [current-pos player]}] (when (gobj/containsKey js/navigator "mediaSession") (let [current-time (and @player (.-currentTime @player)) update-position @@ -136,11 +134,16 @@ "pause" #(.pause @player) "previoustrack" #(rf/dispatch [:queue/change-pos (dec current-pos)]) "nexttrack" #(rf/dispatch [:queue/change-pos (inc current-pos)]) - "seekbackward" (fn [^js/navigator.MediaSessionActionDetails details] - (seek (- current-time (or (.-seekOffset details) 10)))) - "seekforward" (fn [^js/navigator.MediaSessionActionDetails details] - (seek (+ current-time (or (.-seekOffset details) 10)))) - "seekto" (fn [^js/navigator.MediaSessionActionDetails details] + "seekbackward" (fn [^js/navigator.MediaSessionActionDetails + details] + (seek (- current-time + (or (.-seekOffset details) 10)))) + "seekforward" (fn [^js/navigator.MediaSessionActionDetails + details] + (seek (+ current-time + (or (.-seekOffset details) 10)))) + "seekto" (fn [^js/navigator.MediaSessionActionDetails + details] (seek (.-seekTime details))) "stop" #(seek 0)}] (doseq [[action cb] events] @@ -155,7 +158,7 @@ :volume {:player player :volume value}})) (rf/reg-event-fx - :background-player/mute + :bg-player/mute [(rf/inject-cofx :store)] (fn [{:keys [db store]} [_ value player]] {:db (assoc db :muted value) @@ -163,11 +166,11 @@ :mute {:player player :muted? value}})) (rf/reg-event-fx - :background-player/hide + :bg-player/hide [(rf/inject-cofx :store)] (fn [{:keys [db store]} _] - {:db (assoc db :background-player/show false) - :store (assoc store :background-player/show false)})) + {:db (assoc db :bg-player/show false) + :store (assoc store :bg-player/show false)})) (rf/reg-event-fx :player/loop @@ -181,7 +184,7 @@ :store (assoc store :loop-playback loop-state)}))) (rf/reg-event-fx - :background-player/dispose + :bg-player/dispose [(rf/inject-cofx :store)] (fn [{:keys [db store]} _] (let [remove-entries @@ -191,14 +194,14 @@ (assoc :queue-pos 0)))] {:db (remove-entries db) :store (remove-entries store) - :fx [[:dispatch [:background-player/pause true]] - [:dispatch [:background-player/seek 0]] - [:dispatch [:background-player/hide]]]}))) + :fx [[:dispatch [:bg-player/pause true]] + [:dispatch [:bg-player/seek 0]] + [:dispatch [:bg-player/hide]]]}))) (rf/reg-event-db - :background-player/ready + :bg-player/ready (fn [db [_ ready]] - (assoc db :background-player/ready ready))) + (assoc db :bg-player/ready ready))) (rf/reg-event-db :main-player/ready @@ -213,38 +216,40 @@ idx (.indexOf (:queue updated-db) stream)] {:db updated-db :store (assoc store :queue (:queue updated-db)) - :fx [[:dispatch [:player/fetch-stream - (:url stream) idx (= (count (:queue db)) 0)]] + :fx [[:dispatch + [:player/fetch-stream + (:url stream) idx (= (count (:queue db)) 0)]] (when (and notify? (not (= (count (:queue db)) 0))) - [:dispatch [:notifications/add - {:status-text "Added stream to queue" - :failure :info}]])]}))) + [:dispatch + [:notifications/add + {:status-text "Added stream to queue" + :failure :info}]])]}))) (rf/reg-event-fx :player/show-main-player (fn [{:keys [db]} [_ val]] - {:db (assoc db :main-player/show val) + {:db (assoc db :main-player/show val) :body-overflow val})) (rf/reg-event-fx :player/switch-from-main [(rf/inject-cofx ::inject/sub [:elapsed-time])] - (fn [{:keys [db elapsed-time]} [_ stream]] - {:db (assoc db :background-player/show true) + (fn [{:keys [db]} _] + {:db (assoc db :bg-player/show true) :fx [[:dispatch [:player/show-main-player false]] [:dispatch [:main-player/pause true]]]})) (rf/reg-event-fx :player/switch-to-main [(rf/inject-cofx :store)] - (fn [{:keys [db]} [_ stream]] - {:fx [[:dispatch [:player/show-main-player true]]] - :db (assoc db :background-player/show false) + (fn [{:keys [db]} _] + {:fx [[:dispatch [:player/show-main-player true]]] + :db (assoc db :bg-player/show false) :scroll-to-top nil})) (rf/reg-event-fx :player/load-related-streams - (fn [{:keys [db]} [_ res]] + (fn [_ [_ res]] (let [{:keys [related-streams]} (js->clj res :keywordize-keys true)] {:fx [[:dispatch [:queue/add-n related-streams]]]}))) @@ -254,45 +259,53 @@ (rf/inject-cofx ::inject/sub [:player])] (fn [{:keys [db store player]} [_ idx play? res]] (let [stream-res (js->clj res :keywordize-keys true)] - {:db (assoc db - :background-player/show (not (:main-player/show db)) - :background-player/loading false) - :store (assoc store :background-player/show (not (:main-player/show db))) - :fx (apply conj [(when play? [:dispatch [:queue/change-stream stream-res idx]])] - (when (and (:background-player/ready db) play?) - [[:media-session-metadata - {:title (:name stream-res) - :artist (:uploader-name stream-res) - :artwork [{:src (:thumbnail-url stream-res)}]}] - [:media-session-handlers - {:current-pos idx - :player player}]]))}))) + {:db (assoc db + :bg-player/show (not (:main-player/show db)) + :bg-player/loading false) + :store (assoc store :bg-player/show (not (:main-player/show db))) + :fx (apply conj + [(when play? + [:dispatch [:queue/change-stream stream-res idx]])] + (when (and (:bg-player/ready db) play?) + [[:media-session-metadata + {:title (:name stream-res) + :artist (:uploader-name stream-res) + :artwork [{:src (:thumbnail-url stream-res)}]}] + [:media-session-handlers + {:current-pos idx + :player player}]]))}))) (rf/reg-event-fx :player/bad-response (fn [{:keys [db]} [_ idx play? res]] {:db (assoc db - :background-player/loading false) + :bg-player/loading + false) :fx [[:dispatch [:bad-response res]] (when play? - (if (> (-> db :queue count) 1) + (if (> (-> db + :queue + count) + 1) [:dispatch [:queue/change-pos (inc idx)]] - [:dispatch [:background-player/dispose]]))]})) + [:dispatch [:bg-player/dispose]]))]})) (rf/reg-event-fx :player/fetch-related-streams (fn [{:keys [db]} [_ url]] - {:fx [[:dispatch [:stream/fetch url - [:player/load-related-streams]] [:bad-response]]] - :db (assoc db :background-player/loading true)})) + {:fx [[:dispatch + [:stream/fetch url + [:player/load-related-streams]] [:bad-response]]] + :db (assoc db :bg-player/loading true)})) (rf/reg-event-fx :player/fetch-stream (fn [{:keys [db]} [_ url idx play?]] - {:fx [[:dispatch [:stream/fetch url - [:player/load-stream idx play?] - [:player/bad-response idx play?]]]] - :db (assoc db :background-player/loading play?)})) + {:fx [[:dispatch + [:stream/fetch url + [:player/load-stream idx play?] + [:player/bad-response idx play?]]]] + :db (assoc db :bg-player/loading play?)})) (rf/reg-event-fx :player/start-radio @@ -301,12 +314,14 @@ (when (not= (count (:queue db)) 0) [:dispatch [:queue/change-pos (count (:queue db))]]) [:dispatch [:player/fetch-related-streams (:url stream)]] - [:dispatch [:notifications/add - {:status-text "Started stream radio" - :failure :info}]]]})) + [:dispatch + [:notifications/add + {:status-text "Started stream radio" + :failure :info}]]]})) (rf/reg-event-db :main-player/toggle-layout (fn [db [_ layout]] - (assoc-in db [:queue (:queue-pos db) layout] - (not (get-in db [:queue (:queue-pos db) layout]))))) + (assoc-in db + [:queue (:queue-pos db) layout] + (not (get-in db [:queue (:queue-pos db) layout]))))) diff --git a/src/frontend/tubo/player/subs.cljs b/src/frontend/tubo/player/subs.cljs index 0b0f93c..c61473a 100644 --- a/src/frontend/tubo/player/subs.cljs +++ b/src/frontend/tubo/player/subs.cljs @@ -9,18 +9,18 @@ (rf/reg-sub :player - (fn [db _] + (fn [_ _] !player)) (rf/reg-sub :main-player - (fn [db _] + (fn [_ _] !main-player)) (rf/reg-sub - :background-player/ready + :bg-player/ready (fn [db _] - (:background-player/ready db))) + (:bg-player/ready db))) (rf/reg-sub :main-player/ready @@ -28,14 +28,14 @@ (:main-player/ready db))) (rf/reg-sub - :background-player/show + :bg-player/show (fn [db _] - (:background-player/show db))) + (:bg-player/show db))) (rf/reg-sub - :background-player/loading + :bg-player/loading (fn [db _] - (:background-player/loading db))) + (:bg-player/loading db))) (rf/reg-sub :loop-playback @@ -59,7 +59,7 @@ (rf/reg-sub :elapsed-time - (fn [db _] + (fn [_ _] !elapsed-time)) (rf/reg-sub diff --git a/src/frontend/tubo/player/views.cljs b/src/frontend/tubo/player/views.cljs index e98a676..1cd8c86 100644 --- a/src/frontend/tubo/player/views.cljs +++ b/src/frontend/tubo/player/views.cljs @@ -8,8 +8,7 @@ [tubo.components.player :as player] [tubo.queue.views :as queue] [tubo.stream.views :as stream] - [tubo.utils :as utils] - ["@vidstack/react" :refer (useStore MediaPlayerInstance)])) + [tubo.utils :as utils])) (defn stream-metadata [{:keys [thumbnail-url url name uploader-url uploader-name]}] @@ -29,15 +28,13 @@ (defn main-controls [!player color] - (let [queue @(rf/subscribe [:queue]) - queue-pos @(rf/subscribe [:queue-pos]) - loading? @(rf/subscribe [:background-player/loading]) - loop-playback @(rf/subscribe [:loop-playback]) - !main-player @(rf/subscribe [:main-player]) - bg-player-ready? @(rf/subscribe [:background-player/ready]) - main-player-ready? @(rf/subscribe [:main-player/ready]) - paused? @(rf/subscribe [:paused]) - !elapsed-time @(rf/subscribe [:elapsed-time])] + (let [queue @(rf/subscribe [:queue]) + queue-pos @(rf/subscribe [:queue-pos]) + loading? @(rf/subscribe [:bg-player/loading]) + loop-playback @(rf/subscribe [:loop-playback]) + bg-player-ready? @(rf/subscribe [:bg-player/ready]) + paused? @(rf/subscribe [:paused]) + !elapsed-time @(rf/subscribe [:elapsed-time])] [:div.flex.flex-col.items-center.ml-auto [:div.flex.justify-end [player/loop-button loop-playback color] @@ -47,33 +44,39 @@ :disabled? (not (and queue (not= queue-pos 0)))] [player/button :icon [:i.fa-solid.fa-backward] - :on-click #(rf/dispatch [:background-player/seek (- @!elapsed-time 5)])] + :on-click #(rf/dispatch [:bg-player/seek (- @!elapsed-time 5)])] [player/button - :icon (if (and (not loading?) @!player) - (if paused? - [:i.fa-solid.fa-play] - [:i.fa-solid.fa-pause]) - [layout/loading-icon color "lg:text-2xl"]) - :on-click #(rf/dispatch [:background-player/pause (not (.-paused @!player))]) + :icon + (if (and (not loading?) @!player) + (if paused? + [:i.fa-solid.fa-play] + [:i.fa-solid.fa-pause]) + [layout/loading-icon color "lg:text-2xl"]) + :on-click + #(rf/dispatch [:bg-player/pause (not (.-paused @!player))]) :show-on-mobile? true :extra-classes ["lg:text-2xl"]] [player/button :icon [:i.fa-solid.fa-forward] - :on-click #(rf/dispatch [:background-player/seek (+ @!elapsed-time 5)])] + :on-click #(rf/dispatch [:bg-player/seek (+ @!elapsed-time 5)])] [player/button :icon [:i.fa-solid.fa-forward-step] :on-click #(rf/dispatch [:queue/change-pos (inc queue-pos)]) :disabled? (not (and queue (< (inc queue-pos) (count queue))))]] [:div.hidden.lg:flex.items-center.text-sm [:span.mx-2 - (if (and bg-player-ready? @!player @!elapsed-time) (utils/format-duration @!elapsed-time) "--:--")] + (if (and bg-player-ready? @!player @!elapsed-time) + (utils/format-duration @!elapsed-time) + "--:--")] [:div.w-20.lg:w-64.mx-2.flex.items-center [player/time-slider !player !elapsed-time color]] [:span.mx-2 - (if (and bg-player-ready? @!player) (utils/format-duration (.-duration @!player)) "--:--")]]])) + (if (and bg-player-ready? @!player) + (utils/format-duration (.-duration @!player)) + "--:--")]]])) (defn extra-controls - [!player {:keys [url uploader-url] :as stream} color] + [_!player _stream _color] (let [!menu-active? (r/atom nil)] (fn [!player {:keys [url uploader-url] :as stream} color] (let [muted? @(rf/subscribe [:muted]) @@ -81,7 +84,10 @@ queue @(rf/subscribe [:queue]) queue-pos @(rf/subscribe [:queue-pos]) bookmarks @(rf/subscribe [:bookmarks]) - liked? (some #(= (:url %) url) (-> bookmarks first :items)) + liked? (some #(= (:url %) url) + (-> bookmarks + first + :items)) bookmark #(rf/dispatch [:modals/open [modals/add-to-bookmark %]])] [:div.flex.lg:justify-end.lg:flex-1 [player/volume-slider !player volume muted? color] @@ -92,8 +98,10 @@ :extra-classes [:!pl-4 :!pr-3]] [layout/popover-menu !menu-active? [{:label (if liked? "Remove favorite" "Favorite") - :icon [:i.fa-solid.fa-heart (when liked? {:style {:color color}})] - :on-click #(rf/dispatch [(if liked? :likes/remove :likes/add) stream])} + :icon [:i.fa-solid.fa-heart + (when liked? {:style {:color color}})] + :on-click #(rf/dispatch [(if liked? :likes/remove :likes/add) + stream])} {:label "Play radio" :icon [:i.fa-solid.fa-tower-cell] :on-click #(rf/dispatch [:player/start-radio stream])} @@ -117,22 +125,30 @@ :query {:url uploader-url}}])} {:label "Close player" :icon [:i.fa-solid.fa-close] - :on-click #(rf/dispatch [:background-player/dispose])}] + :on-click #(rf/dispatch [:bg-player/dispose])}] :menu-styles {:bottom "30px" :top nil :right "10px"} :extra-classes [:pt-1 :!pl-4 :px-3]]])))) (defn background-player [] - (let [!player @(rf/subscribe [:player]) - stream @(rf/subscribe [:queue-stream]) - show-queue? @(rf/subscribe [:show-queue]) - show-player? @(rf/subscribe [:background-player/show]) - dark-theme? @(rf/subscribe [:dark-theme]) - muted? @(rf/subscribe [:muted]) - loop-playback @(rf/subscribe [:loop-playback]) - color (-> stream :service-id utils/get-service-color) - bg-color (str "rgba(" (if dark-theme? "23,23,23" "255,255,255") ",0.95)") - bg-image (str "linear-gradient(" bg-color "," bg-color "),url(" (:thumbnail-url stream) ")")] + (let [!player @(rf/subscribe [:player]) + stream @(rf/subscribe [:queue-stream]) + show-queue? @(rf/subscribe [:show-queue]) + show-player? @(rf/subscribe [:bg-player/show]) + dark-theme? @(rf/subscribe [:dark-theme]) + color (-> stream + :service-id + utils/get-service-color) + bg-color (str "rgba(" + (if dark-theme? "23,23,23" "255,255,255") + ",0.95)") + bg-image (str "linear-gradient(" + bg-color + "," + bg-color + "),url(" + (:thumbnail-url stream) + ")")] (when show-player? [:div.sticky.absolute.left-0.bottom-0.z-10.p-3.transition-all.ease-in.relative {:style @@ -148,15 +164,17 @@ [main-controls !player color] [extra-controls !player stream color]]]))) -(defn main-player [] - (let [queue @(rf/subscribe [:queue]) - queue-pos @(rf/subscribe [:queue-pos]) - bookmarks @(rf/subscribe [:bookmarks]) - !player @(rf/subscribe [:main-player]) - {:keys [service-id] :as stream} @(rf/subscribe [:queue-stream]) - show-player? @(rf/subscribe [:main-player/show])] +(defn main-player + [] + (let [queue @(rf/subscribe [:queue]) + queue-pos @(rf/subscribe [:queue-pos]) + bookmarks @(rf/subscribe [:bookmarks]) + !player @(rf/subscribe [:main-player]) + stream @(rf/subscribe [:queue-stream]) + show-player? @(rf/subscribe [:main-player/show])] [: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")]} + {: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])} diff --git a/src/frontend/tubo/playlist/events.cljs b/src/frontend/tubo/playlist/events.cljs index 5a96209..3451d2f 100644 --- a/src/frontend/tubo/playlist/events.cljs +++ b/src/frontend/tubo/playlist/events.cljs @@ -5,15 +5,18 @@ (rf/reg-event-fx :playlist/fetch - (fn [{:keys [db]} [_ url on-success on-error params]] + (fn [_ [_ url on-success on-error params]] (api/get-request (str "/playlists/" (js/encodeURIComponent url)) - on-success on-error params))) + on-success + on-error + params))) (rf/reg-event-db :playlist/load-paginated (fn [db [_ res]] (-> db - (update-in [:playlist :related-streams] #(apply conj %1 %2) + (update-in [:playlist :related-streams] + #(apply conj %1 %2) (:related-streams (js->clj res :keywordize-keys true))) (assoc-in [:playlist :next-page] (:next-page (js->clj res :keywordize-keys true))) @@ -24,16 +27,18 @@ (fn [{:keys [db]} [_ url next-page-url]] (if (empty? next-page-url) {:db (assoc db :show-pagination-loading false)} - {:fx [[:dispatch [:playlist/fetch url - [:playlist/load-paginated] [:bad-response] - {:nextPage (js/encodeURIComponent next-page-url)}]]] + {:fx [[:dispatch + [:playlist/fetch url + [:playlist/load-paginated] [:bad-response] + {:nextPage (js/encodeURIComponent next-page-url)}]]] :db (assoc db :show-pagination-loading true)}))) (rf/reg-event-fx :playlist/load-page (fn [{:keys [db]} [_ res]] (let [playlist-res (js->clj res :keywordize-keys true)] - {:db (assoc db :playlist playlist-res + {:db (assoc db + :playlist playlist-res :show-page-loading false) :fx [[:dispatch [:services/fetch playlist-res]] [:document-title (:name playlist-res)]]}))) @@ -41,8 +46,9 @@ (rf/reg-event-fx :playlist/fetch-page (fn [{:keys [db]} [_ url]] - {:fx [[:dispatch [:playlist/fetch url - [:playlist/load-page] [:bad-response]]]] + {:fx [[:dispatch + [:playlist/fetch url + [:playlist/load-page] [:bad-response]]]] :db (assoc db :show-page-loading true - :playlist nil)})) + :playlist nil)})) diff --git a/src/frontend/tubo/playlist/views.cljs b/src/frontend/tubo/playlist/views.cljs index 39adb88..c26bdd3 100644 --- a/src/frontend/tubo/playlist/views.cljs +++ b/src/frontend/tubo/playlist/views.cljs @@ -11,11 +11,11 @@ [{{:keys [url]} :query-params}] (let [!menu-active? (r/atom nil)] (fn [] - (let [{:keys [id name playlist-type thumbnail-url banner-url next-page - uploader-name uploader-url related-streams - stream-count]} @(rf/subscribe [:playlist]) - next-page-url (:url next-page) - scrolled-to-bottom? @(rf/subscribe [:scrolled-to-bottom])] + (let [{:keys [name next-page uploader-name uploader-url related-streams + stream-count]} + @(rf/subscribe [:playlist]) + next-page-url (:url next-page) + scrolled-to-bottom? @(rf/subscribe [:scrolled-to-bottom])] (when scrolled-to-bottom? (rf/dispatch [:playlist/fetch-paginated url next-page-url])) [layout/content-container @@ -28,7 +28,9 @@ :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]])}]])] + :on-click #(rf/dispatch [:modals/open + [modals/add-to-bookmark + related-streams]])}]])] [:div.flex.items-center.justify-between.my-4.gap-x-4 [:div.flex.items-center [layout/uploader-avatar playlist] diff --git a/src/frontend/tubo/queue/events.cljs b/src/frontend/tubo/queue/events.cljs index 0154cef..a174623 100644 --- a/src/frontend/tubo/queue/events.cljs +++ b/src/frontend/tubo/queue/events.cljs @@ -16,29 +16,38 @@ {:db updated-db :store (assoc store :queue (:queue updated-db)) :fx (if notify? - [[:dispatch [:notifications/add - {:status-text "Added stream to queue" - :failure :info}]]] + [[:dispatch + [:notifications/add + {:status-text "Added stream to queue" + :failure :info}]]] [])}))) (rf/reg-event-fx :queue/add-n [(rf/inject-cofx :store)] - (fn [{:keys [db store]} [_ streams notify?]] - {:fx (into (map (fn [stream] [:dispatch [:queue/add stream]]) streams) - [[:dispatch [:player/fetch-stream (-> streams first :url) - (count (:queue db)) (= (count (:queue db)) 0)]] - (when notify? - [:dispatch [:notifications/add - {:status-text (str "Added " (count streams) - " streams to queue") - :failure :info}]])])})) + (fn [{:keys [db]} [_ streams notify?]] + {:fx (into (map (fn [stream] [:dispatch [:queue/add stream]]) streams) + [[:dispatch + [:player/fetch-stream + (-> streams + first + :url) + (count (:queue db)) (= (count (:queue db)) 0)]] + (when notify? + [:dispatch + [:notifications/add + {:status-text (str "Added " + (count streams) + " streams to queue") + :failure :info}]])])})) (rf/reg-event-fx :queue/remove [(rf/inject-cofx :store)] (fn [{:keys [db store]} [_ pos]] - (let [updated-db (update db :queue #(into (subvec % 0 pos) (subvec % (inc pos)))) + (let [updated-db (update db + :queue + #(into (subvec % 0 pos) (subvec % (inc pos)))) queue-pos (:queue-pos db) queue-length (count (:queue updated-db))] {:db updated-db @@ -48,11 +57,12 @@ (or (< pos queue-pos) (= pos queue-pos) (= queue-pos queue-length))) - [[:dispatch [:queue/change-pos - (cond - (= pos queue-length) 0 - (= pos queue-pos) pos - :else (dec queue-pos))]]] + [[:dispatch + [:queue/change-pos + (cond + (= pos queue-length) 0 + (= pos queue-pos) pos + :else (dec queue-pos))]]] (= (count (:queue updated-db)) 0) [[:dispatch [:player/dispose]] [:dispatch [:queue/show false]]] @@ -61,7 +71,7 @@ (rf/reg-event-fx :queue/change-pos [(rf/inject-cofx :store)] - (fn [{:keys [db store]} [_ i]] + (fn [{:keys [db]} [_ i]] (let [idx (if (< i (count (:queue db))) i (when (= (:loop-playback db) :playlist) 0)) diff --git a/src/frontend/tubo/queue/views.cljs b/src/frontend/tubo/queue/views.cljs index c6326fe..eae40b2 100644 --- a/src/frontend/tubo/queue/views.cljs +++ b/src/frontend/tubo/queue/views.cljs @@ -4,7 +4,6 @@ [re-frame.core :as rf] [reitit.frontend.easy :as rfe] [tubo.bookmarks.modals :as modals] - [tubo.components.items :as items] [tubo.components.layout :as layout] [tubo.components.player :as player] [tubo.utils :as utils])) @@ -29,11 +28,16 @@ (defn popover [{:keys [url service-id uploader-url] :as item} i menu-active? bookmarks] - (let [liked? (some #(= (:url %) url) (-> bookmarks first :items))] + (let [liked? (some #(= (:url %) url) + (-> bookmarks + first + :items))] [:div.absolute.right-0.top-0.min-h-full.flex.items-center [layout/popover-menu menu-active? [{:label (if liked? "Remove favorite" "Favorite") - :icon [:i.fa-solid.fa-heart (when liked? {:style {:color (utils/get-service-color service-id)}})] + :icon [:i.fa-solid.fa-heart + (when liked? + {:style {:color (utils/get-service-color service-id)}})] :on-click #(rf/dispatch [(if liked? :likes/remove :likes/add) item])} {:label "Play radio" :icon [:i.fa-solid.fa-tower-cell] @@ -54,12 +58,13 @@ :extra-classes [:px-7 :py-2]]])) (defn queue-item - [item queue queue-pos i bookmarks] - (let [!menu-active? (r/atom false) + [_item _queue _queue-pos _i _bookmarks] + (let [!menu-active? (r/atom false) show-main-player? @(rf/subscribe [:main-player/show])] (fn [item queue queue-pos i bookmarks] [:div.relative.w-full - {:ref #(when (and queue (= queue-pos i) (not show-main-player?)) (rf/dispatch [:scroll-into-view %]))} + {:ref #(when (and queue (= queue-pos i) (not show-main-player?)) + (rf/dispatch [:scroll-into-view %]))} [item-metadata item queue-pos i] [popover item i !menu-active? bookmarks]]))) @@ -76,24 +81,26 @@ uploader-name]]) (defn main-controls - [{:keys [service-id]} queue queue-pos color] - (let [loop-playback @(rf/subscribe [:loop-playback]) - !player @(rf/subscribe [:player]) - !main-player @(rf/subscribe [:main-player]) - loading? @(rf/subscribe [:background-player/loading]) - bg-player-ready? @(rf/subscribe [:background-player/ready]) - main-player-ready? @(rf/subscribe [:main-player/ready]) - paused? @(rf/subscribe [:paused]) - !elapsed-time @(rf/subscribe [:elapsed-time]) - queue @(rf/subscribe [:queue]) - queue-pos @(rf/subscribe [:queue-pos])] + [color] + (let [loop-playback @(rf/subscribe [:loop-playback]) + !player @(rf/subscribe [:player]) + loading? @(rf/subscribe [:bg-player/loading]) + bg-player-ready? @(rf/subscribe [:bg-player/ready]) + paused? @(rf/subscribe [:paused]) + !elapsed-time @(rf/subscribe [:elapsed-time]) + queue @(rf/subscribe [:queue]) + queue-pos @(rf/subscribe [:queue-pos])] [:<> [:div.flex.flex-auto.py-2.w-full.items-center.text-sm [:span.mr-4.whitespace-nowrap - (if (and bg-player-ready? @!player @!elapsed-time) (utils/format-duration @!elapsed-time) "--:--")] + (if (and bg-player-ready? @!player @!elapsed-time) + (utils/format-duration @!elapsed-time) + "--:--")] [player/time-slider !player !elapsed-time color] [:span.ml-4.whitespace-nowrap - (if (and bg-player-ready? @!player) (utils/format-duration (.-duration @!player)) "--:--")]] + (if (and bg-player-ready? @!player) + (utils/format-duration (.-duration @!player)) + "--:--")]] [:div.flex.justify-center.items-center [player/loop-button loop-playback color true] [player/button @@ -104,21 +111,23 @@ :show-on-mobile? true] [player/button :icon [:i.fa-solid.fa-backward] - :on-click #(rf/dispatch [:background-player/seek (- @!elapsed-time 5)]) + :on-click #(rf/dispatch [:bg-player/seek (- @!elapsed-time 5)]) :extra-classes [:text-xl] :show-on-mobile? true] [player/button - :icon (if (and (not loading?) @!player) - (if paused? - [:i.fa-solid.fa-play] - [:i.fa-solid.fa-pause]) - [layout/loading-icon color :text-3xl]) - :on-click #(rf/dispatch [:background-player/pause (not (.-paused @!player))]) + :icon + (if (and (not loading?) @!player) + (if paused? + [:i.fa-solid.fa-play] + [:i.fa-solid.fa-pause]) + [layout/loading-icon color :text-3xl]) + :on-click + #(rf/dispatch [:bg-player/pause (not (.-paused @!player))]) :show-on-mobile? true :extra-classes [:text-3xl]] [player/button :icon [:i.fa-solid.fa-forward] - :on-click #(rf/dispatch [:background-player/seek (+ @!elapsed-time 5)]) + :on-click #(rf/dispatch [:bg-player/seek (+ @!elapsed-time 5)]) :extra-classes [:text-xl] :show-on-mobile? true] [player/button @@ -140,7 +149,9 @@ bookmarks @(rf/subscribe [:bookmarks]) queue-pos @(rf/subscribe [:queue-pos]) queue @(rf/subscribe [:queue]) - color (-> stream :service-id utils/get-service-color)] + color (-> stream + :service-id + utils/get-service-color)] [:div.fixed.flex.flex-col.items-center.min-w-full.w-full.z-10.backdrop-blur {:class ["dark:bg-neutral-900/90" "bg-neutral-100/90" "min-h-[calc(100dvh-56px)]" "h-[calc(100dvh-56px)]" @@ -154,4 +165,4 @@ ^{:key i} [queue-item item queue queue-pos i bookmarks])] [:div.flex.flex-col.py-4.shrink-0.px-5 [queue-metadata stream] - [main-controls stream queue queue-pos color]]]])) + [main-controls color]]]])) diff --git a/src/frontend/tubo/routes.cljs b/src/frontend/tubo/routes.cljs index d721429..67de20e 100644 --- a/src/frontend/tubo/routes.cljs +++ b/src/frontend/tubo/routes.cljs @@ -13,46 +13,57 @@ (def router (ref/router - [["/" {:view kiosk/kiosk - :name :homepage - :controllers [{:start #(rf/dispatch [:fetch-homepage])}]}] - ["/search" {:view search/search - :name :search-page - :controllers [{:parameters {:query [:q :serviceId]} - :start (fn [{{:keys [serviceId q]} :query}] - (rf/dispatch [:search/fetch-page serviceId q])) - :stop #(rf/dispatch [:search/show-form false])}]}] - ["/stream" {:view stream/stream - :name :stream-page - :controllers [{:parameters {:query [:url]} - :start (fn [{{:keys [url]} :query}] - (rf/dispatch [:stream/fetch-page url]))}]}] - ["/channel" {:view channel/channel - :name :channel-page - :controllers [{:parameters {:query [:url]} - :start (fn [{{:keys [url]} :query}] - (rf/dispatch [:channel/fetch-page url]))}]}] - ["/playlist" {:view playlist/playlist - :name :playlist-page - :controllers [{:parameters {:query [:url]} - :start (fn [{{:keys [url]} :query}] - (rf/dispatch [:playlist/fetch-page url]))}]}] - ["/kiosk" {:view kiosk/kiosk - :name :kiosk-page - :controllers [{:parameters {:query [:kioskId :serviceId]} - :start (fn [{{:keys [serviceId kioskId]} :query}] - (rf/dispatch [:kiosks/fetch-page serviceId kioskId]))}]}] - ["/settings" {:view settings/settings - :name :settings-page - :controllers [{:start #(rf/dispatch [:settings/fetch-page])}]}] - ["/bookmark" {:view bookmarks/bookmark - :name :bookmark-page - :controllers [{:parameters {:query [:id]} - :start (fn [{{:keys [id]} :query}] - (rf/dispatch [:bookmark/fetch-page id]))}]}] - ["/bookmarks" {:view bookmarks/bookmarks - :name :bookmarks-page - :controllers [{:start #(rf/dispatch [:bookmarks/fetch-page])}]}]])) + [["/" + {:view kiosk/kiosk + :name :homepage + :controllers [{:start #(rf/dispatch [:fetch-homepage])}]}] + ["/search" + {:view search/search + :name :search-page + :controllers [{:parameters {:query [:q :serviceId]} + :start (fn [{{:keys [serviceId q]} :query}] + (rf/dispatch [:search/fetch-page serviceId + q])) + :stop #(rf/dispatch [:search/show-form false])}]}] + ["/stream" + {:view stream/stream + :name :stream-page + :controllers [{:parameters {:query [:url]} + :start (fn [{{:keys [url]} :query}] + (rf/dispatch [:stream/fetch-page url]))}]}] + ["/channel" + {:view channel/channel + :name :channel-page + :controllers [{:parameters {:query [:url]} + :start (fn [{{:keys [url]} :query}] + (rf/dispatch [:channel/fetch-page url]))}]}] + ["/playlist" + {:view playlist/playlist + :name :playlist-page + :controllers [{:parameters {:query [:url]} + :start (fn [{{:keys [url]} :query}] + (rf/dispatch [:playlist/fetch-page url]))}]}] + ["/kiosk" + {:view kiosk/kiosk + :name :kiosk-page + :controllers [{:parameters {:query [:kioskId :serviceId]} + :start (fn [{{:keys [serviceId kioskId]} :query}] + (rf/dispatch [:kiosks/fetch-page serviceId + kioskId]))}]}] + ["/settings" + {:view settings/settings + :name :settings-page + :controllers [{:start #(rf/dispatch [:settings/fetch-page])}]}] + ["/bookmark" + {:view bookmarks/bookmark + :name :bookmark-page + :controllers [{:parameters {:query [:id]} + :start (fn [{{:keys [id]} :query}] + (rf/dispatch [:bookmark/fetch-page id]))}]}] + ["/bookmarks" + {:view bookmarks/bookmarks + :name :bookmarks-page + :controllers [{:start #(rf/dispatch [:bookmarks/fetch-page])}]}]])) (defn on-navigate [new-match] diff --git a/src/frontend/tubo/search/events.cljs b/src/frontend/tubo/search/events.cljs index cfa3fe5..320e5ec 100644 --- a/src/frontend/tubo/search/events.cljs +++ b/src/frontend/tubo/search/events.cljs @@ -6,22 +6,27 @@ (rf/reg-event-fx :search/fetch - (fn [{:keys [db]} [_ service-id on-success on-error params]] + (fn [_ [_ service-id on-success on-error params]] (api/get-request (str "/services/" service-id "/search") - on-success on-error params))) + on-success + on-error + params))) (rf/reg-event-fx :search/load-page (fn [{:keys [db]} [_ res]] (let [search-res (js->clj res :keywordize-keys true)] - {:db (assoc db :search-results search-res + {:db (assoc db + :search-results search-res :show-page-loading false) :fx [[:dispatch [:services/fetch search-res]]]}))) (rf/reg-event-fx :search/bad-page-response (fn [{:keys [db]} [_ service-id query res]] - {:fx [[:dispatch [:change-view #(layout/error res [:search/fetch-page service-id query])]]] + {:fx [[:dispatch + [:change-view + #(layout/error res [:search/fetch-page service-id query])]]] :db (assoc db :show-page-loading false)})) (rf/reg-event-fx @@ -29,10 +34,12 @@ (fn [{:keys [db]} [_ service-id query]] {:db (assoc db :show-page-loading true - :show-search-form true - :search-results nil) - :fx [[:dispatch [:search/fetch service-id - [:search/load-page] [:search/bad-page-response service-id query] {:q query}]] + :show-search-form true + :search-results nil) + :fx [[:dispatch + [:search/fetch service-id + [:search/load-page] [:search/bad-page-response service-id query] + {:q query}]] [:document-title (str "Search for \"" query "\"")]]})) (rf/reg-event-db @@ -44,7 +51,8 @@ (assoc-in [:search-results :next-page] nil) (assoc :show-pagination-loading false)) (-> db - (update-in [:search-results :items] #(apply conj %1 %2) + (update-in [:search-results :items] + #(apply conj %1 %2) (:items search-res)) (assoc-in [:search-results :next-page] (:next-page search-res)) (assoc :show-pagination-loading false)))))) @@ -54,16 +62,20 @@ (fn [{:keys [db]} [_ query id next-page-url]] (if (empty? next-page-url) {:db (assoc db :show-pagination-loading false)} - {:fx [[:dispatch [:search/fetch id - [:search/load-paginated] [:bad-response] - {:q query - :nextPage (js/encodeURIComponent next-page-url)}]]] + {:fx [[:dispatch + [:search/fetch id + [:search/load-paginated] [:bad-response] + {:q query + :nextPage (js/encodeURIComponent next-page-url)}]]] :db (assoc db :show-pagination-loading true)}))) (rf/reg-event-db :search/show-form (fn [db [_ show?]] - (when-not (= (-> db :current-match :path) "search") + (when-not (= (-> db + :current-match + :path) + "search") (assoc db :show-search-form show?)))) (rf/reg-event-db diff --git a/src/frontend/tubo/search/views.cljs b/src/frontend/tubo/search/views.cljs index 58764f9..a121568 100644 --- a/src/frontend/tubo/search/views.cljs +++ b/src/frontend/tubo/search/views.cljs @@ -2,11 +2,11 @@ (:require [re-frame.core :as rf] [reagent.core :as r] - [reitit.frontend.easy :as rfe] [tubo.components.items :as items] [tubo.components.layout :as layout])) -(defn search-form [] +(defn search-form + [] (let [!query (r/atom "") !input (r/atom nil)] (fn [] @@ -14,7 +14,7 @@ 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") + {:class (when-not show-search-form? "hidden") :on-submit #(do (.preventDefault %) (when-not (empty? @!query) (rf/dispatch [:navigate @@ -49,12 +49,10 @@ (defn search [{{:keys [q serviceId]} :query-params}] - (let [{:keys [items next-page] - :as search-results} @(rf/subscribe [:search-results]) - next-page-url (:url next-page) - services @(rf/subscribe [:services]) - service-id (or @(rf/subscribe [:service-id]) serviceId) - scrolled-to-bottom? @(rf/subscribe [:scrolled-to-bottom])] + (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 diff --git a/src/frontend/tubo/services/events.cljs b/src/frontend/tubo/services/events.cljs index 7fb1789..922dd28 100644 --- a/src/frontend/tubo/services/events.cljs +++ b/src/frontend/tubo/services/events.cljs @@ -8,8 +8,9 @@ (fn [{:keys [db]} [_ {:keys [service-id]}]] {:db db :fx [[:dispatch [:services/change-id service-id]] - [:dispatch [:kiosks/fetch-all service-id - [:kiosks/load] [:bad-response]]]]})) + [:dispatch + [:kiosks/fetch-all service-id + [:kiosks/load] [:bad-response]]]]})) (rf/reg-event-fx :services/change-id @@ -20,7 +21,7 @@ (rf/reg-event-fx :services/fetch-all - (fn [{:keys [db]} [_ on-success on-error]] + (fn [_ [_ on-success on-error]] (api/get-request "/services" on-success on-error))) (rf/reg-event-db diff --git a/src/frontend/tubo/services/views.cljs b/src/frontend/tubo/services/views.cljs index b27dca0..ceac017 100644 --- a/src/frontend/tubo/services/views.cljs +++ b/src/frontend/tubo/services/views.cljs @@ -8,13 +8,17 @@ {:style {:background service-color}} [:div.w-full.box-border.z-10.lg:z-0 [:select.border-none.focus:ring-transparent.bg-blend-color-dodge.font-bold.w-full - {:on-change #(rf/dispatch [:kiosks/change-page (js/parseInt (.. % -target -value))]) + {:on-change #(rf/dispatch [:kiosks/change-page + (js/parseInt (.. % -target -value))]) :value service-id :style {:background :transparent}} (when services (for [[i service] (map-indexed vector services)] - ^{:key i} [:option.text-white.bg-neutral-900.border-none - {:value (:id service)} - (-> service :info :name)]))]] + ^{:key i} + [:option.text-white.bg-neutral-900.border-none + {:value (:id service)} + (-> service + :info + :name)]))]] [:div.flex.items-center.justify-end.absolute.min-h-full.top-0.right-4.lg:right-0.z-0 [:i.fa-solid.fa-caret-down]]]) diff --git a/src/frontend/tubo/settings/events.cljs b/src/frontend/tubo/settings/events.cljs index e2a5643..57e173f 100644 --- a/src/frontend/tubo/settings/events.cljs +++ b/src/frontend/tubo/settings/events.cljs @@ -15,27 +15,38 @@ [(rf/inject-cofx :store)] (fn [{:keys [db store]} [_ service-name service-id res]] (let [kiosks-res (js->clj res :keywordize-keys true) - default-service-kiosk (-> db :settings :default-service :default-kiosk) + default-service-kiosk (-> db + :settings + :default-service + :default-kiosk) default-kiosk (if (some #(= % default-service-kiosk) (:available-kiosks kiosks-res)) default-service-kiosk (:default-kiosk kiosks-res))] - {:db (update-in db [:settings :default-service] assoc - :id service-name - :service-id service-id + {:db (update-in db + [:settings :default-service] + assoc + :id service-name + :service-id service-id :available-kiosks (:available-kiosks kiosks-res) - :default-kiosk default-kiosk) - :store (update-in store [:default-service] assoc - :id service-name - :service-id service-id + :default-kiosk default-kiosk) + :store (update-in store + [:default-service] + assoc + :id service-name + :service-id service-id :available-kiosks (:available-kiosks kiosks-res) - :default-kiosk default-kiosk)}))) + :default-kiosk default-kiosk)}))) (rf/reg-event-fx :settings/change-service [(rf/inject-cofx :store)] - (fn [{:keys [db store]} [_ val]] - (let [service-id (-> (filter #(= val (-> % :info :name)) (:services db)) + (fn [{:keys [db]} [_ val]] + (let [service-id (-> (filter #(= val + (-> % + :info + :name)) + (:services db)) first :id)] (api/get-request (str "/services/" service-id "/kiosks") @@ -52,9 +63,17 @@ (rf/reg-event-fx :settings/fetch-page (fn [{:keys [db]} _] - (let [id (-> db :settings :default-service :id) - service-id (-> db :settings :default-service :service-id)] + (let [id (-> db + :settings + :default-service + :id) + service-id (-> db + :settings + :default-service + :service-id)] (assoc (api/get-request (str "/services/" service-id "/kiosks") - [:settings/load-kiosks id service-id] [:bad-response]) - :document-title "Settings")))) + [:settings/load-kiosks id service-id] + [:bad-response]) + :document-title + "Settings")))) diff --git a/src/frontend/tubo/settings/views.cljs b/src/frontend/tubo/settings/views.cljs index 207d125..0658bbb 100644 --- a/src/frontend/tubo/settings/views.cljs +++ b/src/frontend/tubo/settings/views.cljs @@ -16,20 +16,23 @@ (defn settings [] - (let [{:keys [theme themes show-comments show-related show-description - default-service]} @(rf/subscribe [:settings]) - service-color @(rf/subscribe [:service-color]) - services @(rf/subscribe [:services])] + (let [{:keys [theme show-comments show-related show-description + default-service]} + @(rf/subscribe [:settings]) + services @(rf/subscribe [:services])] [layout/content-container [layout/content-header "Settings"] [:form.flex.flex-wrap.py-4 [select-input "Theme" :theme theme #{:auto :light :dark}] [select-input "Default service" :default-service (:id default-service) - (map #(-> % :info :name) services) - #(rf/dispatch [:settings/change-service (.. % -target -value)])] + (map #(-> % + :info + :name) + services) + #(rf/dispatch [:settings/change-service (.. % -target -value)])] [select-input "Default kiosk" :default-service (:default-kiosk default-service) (:available-kiosks default-service) - #(rf/dispatch [:settings/change-kiosk (.. % -target -value)])] + #(rf/dispatch [:settings/change-kiosk (.. % -target -value)])] [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/stream/events.cljs b/src/frontend/tubo/stream/events.cljs index 6b4795a..7b55e7c 100644 --- a/src/frontend/tubo/stream/events.cljs +++ b/src/frontend/tubo/stream/events.cljs @@ -6,17 +6,21 @@ (rf/reg-event-fx :stream/fetch - (fn [{:keys [db]} [_ url on-success on-error]] + (fn [_ [_ url on-success on-error]] (api/get-request (str "/streams/" (js/encodeURIComponent url)) - on-success on-error))) + on-success + on-error))) (rf/reg-event-fx :stream/load-page (fn [{:keys [db]} [_ res]] (let [stream-res (js->clj res :keywordize-keys true)] - {:db (assoc db :stream stream-res + {:db (assoc db + :stream stream-res :show-page-loading false) - :fx [(when (and (-> db :settings :show-comments)) + :fx [(when (-> db + :settings + :show-comments) [:dispatch [:comments/fetch-page (:url stream-res)]]) [:dispatch [:services/fetch stream-res]] [:document-title (:name stream-res)]]}))) @@ -24,19 +28,25 @@ (rf/reg-event-fx :stream/bad-page-response (fn [{:keys [db]} [_ url res]] - {:fx [[:dispatch [:change-view #(layout/error res [:stream/fetch-page url])]]] + {:fx [[:dispatch + [:change-view #(layout/error res [:stream/fetch-page url])]]] :db (assoc db :show-page-loading false)})) (rf/reg-event-fx :stream/fetch-page (fn [{:keys [db]} [_ url]] - {:fx [[:dispatch [:stream/fetch url - [:stream/load-page] [:stream/bad-page-response url]]]] + {:fx [[:dispatch + [:stream/fetch url + [:stream/load-page] [:stream/bad-page-response url]]]] :db (assoc db :show-page-loading true - :stream nil)})) + :stream nil)})) (rf/reg-event-db :stream/toggle-layout (fn [db [_ layout]] - (assoc-in db [:stream layout] (not (-> db :stream layout))))) + (assoc-in db + [:stream layout] + (not (-> db + :stream + layout))))) diff --git a/src/frontend/tubo/stream/views.cljs b/src/frontend/tubo/stream/views.cljs index 5c855e4..568563b 100644 --- a/src/frontend/tubo/stream/views.cljs +++ b/src/frontend/tubo/stream/views.cljs @@ -15,7 +15,10 @@ (let [!menu-active? (r/atom nil)] (fn [{:keys [service-id url] :as stream}] (let [bookmarks @(rf/subscribe [:bookmarks]) - liked? (some #(= (:url %) url) (-> bookmarks first :items))] + liked? (some #(= (:url %) url) + (-> bookmarks + first + :items))] [layout/popover-menu !menu-active? [{:label "Add to queue" :icon [:i.fa-solid.fa-headphones] @@ -28,13 +31,15 @@ [:i.fa-solid.fa-heart {:style {:color (utils/get-service-color service-id)}}] [:i.fa-solid.fa-heart]) - :on-click #(rf/dispatch [(if liked? :likes/remove :likes/add) stream true])} + :on-click #(rf/dispatch [(if liked? :likes/remove :likes/add) stream + true])} {:label "Original" :link {:route url :external? true} :icon [:i.fa-solid.fa-external-link-alt]} {:label "Add to playlist" :icon [:i.fa-solid.fa-plus] - :on-click #(rf/dispatch [:modals/open [modals/add-to-bookmark stream]])}]])))) + :on-click #(rf/dispatch [:modals/open + [modals/add-to-bookmark stream]])}]])))) (defn metadata-uploader [{:keys [uploader-url uploader-name subscriber-count] :as stream}] @@ -84,7 +89,7 @@ (defn description [{:keys [description show-description]}] (let [show? (:show-description @(rf/subscribe [:settings]))] - (when (and show? (not (empty? description))) + (when (and show? (seq description)) [layout/show-more-container show-description description #(rf/dispatch [(if @(rf/subscribe [:main-player/show]) :main-player/toggle-layout @@ -95,7 +100,7 @@ [{:keys [comments-page show-comments show-comments-loading url] :as stream}] (let [show? (:show-comments @(rf/subscribe [:settings])) service-color @(rf/subscribe [:service-color])] - (when (and comments-page (not (empty? (:comments comments-page))) show?) + (when (and comments-page (seq (:comments comments-page)) show?) [layout/accordeon {:label "Comments" :on-open #(if show-comments @@ -115,27 +120,31 @@ (let [!menu-active? (r/atom nil)] (fn [{:keys [related-streams show-related]}] (let [show? (:show-related @(rf/subscribe [:settings]))] - (when (and show? (not (empty? related-streams))) + (when (and show? (seq related-streams)) [layout/accordeon {:label "Suggested" :on-open #(rf/dispatch [(if @(rf/subscribe [:main-player/show]) :main-player/toggle-layout - :stream/toggle-layout) :show-related]) + :stream/toggle-layout) + :show-related]) :open? (not show-related) :left-icon "fa-solid fa-list" :right-button [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])} + :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]])}]]} + :on-click #(rf/dispatch [:modals/open + [modals/add-to-bookmark + related-streams]])}]]} [items/related-streams related-streams nil]]))))) (defn stream [] - (let [{:keys [audio-streams video-streams name thumbnail-url] :as stream} @(rf/subscribe [:stream]) - !player @(rf/subscribe [:main-player]) + (let [stream @(rf/subscribe [:stream]) + !player @(rf/subscribe [:main-player]) page-loading? @(rf/subscribe [:show-page-loading])] [:<> (when-not page-loading? diff --git a/src/frontend/tubo/subs.cljs b/src/frontend/tubo/subs.cljs index 2dea707..a789ccb 100644 --- a/src/frontend/tubo/subs.cljs +++ b/src/frontend/tubo/subs.cljs @@ -2,7 +2,6 @@ (:require [reagent.core :as r] [re-frame.core :as rf] - [tubo.utils :as utils] [tubo.bookmarks.subs] [tubo.channel.subs] [tubo.kiosks.subs] @@ -42,7 +41,6 @@ #(reset! theme (if (.-matches %) "dark" "light"))) theme)) - (rf/reg-sub :is-window-visible (fn [_ _] diff --git a/src/frontend/tubo/utils.cljs b/src/frontend/tubo/utils.cljs index 4b4a45a..6d68219 100644 --- a/src/frontend/tubo/utils.cljs +++ b/src/frontend/tubo/utils.cljs @@ -31,7 +31,9 @@ (defn format-date-ago [date] - (if (-> date js/Date.parse js/isNaN) + (if (-> date + js/Date.parse + js/isNaN) date (timeago/format date))) @@ -39,11 +41,17 @@ [num] (.format (js/Intl.NumberFormat - "en-US" #js {"notation" "compact" "maximumFractionDigits" 1}) + "en-US" + #js {"notation" "compact" "maximumFractionDigits" 1}) num)) (defn format-duration [num] (let [duration (and (not (js/isNaN num)) (js/Date. (* num 1000))) - slice (and duration #(.slice % (if (>= (.getUTCHours duration) 1) 11 14) 19))] - (if slice (-> duration (.toISOString) slice) "--:--"))) + slice (and duration + #(.slice % (if (>= (.getUTCHours duration) 1) 11 14) 19))] + (if slice + (-> duration + (.toISOString) + slice) + "--:--"))) diff --git a/src/frontend/tubo/views.cljs b/src/frontend/tubo/views.cljs index 124f3ce..3a68b65 100644 --- a/src/frontend/tubo/views.cljs +++ b/src/frontend/tubo/views.cljs @@ -15,7 +15,9 @@ [navigation/navbar current-match] [notifications/notifications-panel] [:div.flex.flex-col.flex-auto.justify-between.relative - (when-let [view (-> current-match :data :view)] + (when-let [view (-> current-match + :data + :view)] [view current-match]) [queue/queue] [player/main-player] |