aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiguel Ángel Moreno <mail@migalmoreno.com>2024-04-29 01:32:31 +0200
committerMiguel Ángel Moreno <mail@migalmoreno.com>2024-05-29 11:16:14 +0200
commitee6eda82b113a7f1759b2926aa56216411fc0461 (patch)
treec5b772dbadd7b7398921425c518bed568418440a
parent8aacdfddb3d75c99a8b7c5784e3db32be2453d04 (diff)
feat: add import and export of bookmark lists
-rw-r--r--deps.edn4
-rw-r--r--src/frontend/tubo/events.cljs93
-rw-r--r--src/frontend/tubo/views/bookmarks.cljs49
3 files changed, 130 insertions, 16 deletions
diff --git a/deps.edn b/deps.edn
index 461ad9f..d0cb856 100644
--- a/deps.edn
+++ b/deps.edn
@@ -27,6 +27,8 @@
akiroz.re-frame/storage {:mvn/version "0.1.4"}
re-frame-utils/re-frame-utils {:mvn/version "0.1.0"}
nano-id/nano-id {:mvn/version "1.1.0"}
- com.github.scopews/svgreq {:mvn/version "1.1.0"}}
+ com.github.scopews/svgreq {:mvn/version "1.1.0"}
+ funcool/promesa {:mvn/version "11.0.678"}
+ re-promise/re-promise {:mvn/version "0.1.1"}}
:main-opts ["-m" "shadow.cljs.devtools.cli"]}
:run {:main-opts ["-m" "tubo.core"]}}}
diff --git a/src/frontend/tubo/events.cljs b/src/frontend/tubo/events.cljs
index c431daf..5a8bd9a 100644
--- a/src/frontend/tubo/events.cljs
+++ b/src/frontend/tubo/events.cljs
@@ -4,8 +4,10 @@
[day8.re-frame.http-fx]
[goog.object :as gobj]
[nano-id.core :refer [nano-id]]
+ [promesa.core :as p]
[reagent.core :as r]
[re-frame.core :as rf]
+ [re-promise.core]
[reitit.frontend.easy :as rfe]
[reitit.frontend.controllers :as rfc]
[tubo.api :as api]
@@ -506,6 +508,86 @@
(fn [_ [_ child]]
{:fx [[:dispatch [::open-modal child]]]}))
+(rf/reg-fx
+ ::download-file!
+ (fn [{:keys [data name 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)
+ (.click !link)
+ (.remove !link))))
+
+(rf/reg-event-fx
+ ::add-imported-bookmark-list
+ (fn [{:keys [db]} [_ index bookmark]]
+ {:fx (if (= index 0)
+ (map (fn [s] [:dispatch [::add-to-likes s]])
+ (:items bookmark))
+ [[:dispatch [::add-bookmark-list bookmark]]])}))
+
+(rf/reg-event-fx
+ ::add-streams-to-imported-bookmark-lists
+ (fn [{:keys [db]} [_ bookmarks]]
+ {:fx (conj (map-indexed (fn [i b] [:dispatch [::add-imported-bookmark-list i b]]) bookmarks)
+ [:dispatch [::add-notification
+ {:status-text "Imported playlists successfully"
+ :failure :success}]])}))
+
+(defn fetch-imported-playlists-streams
+ [bookmarks]
+ (-> #(-> (p/all (map (fn [stream]
+ (p/then (js/fetch
+ (str "/api/v1/streams/" (js/encodeURIComponent stream)))
+ (fn [res] (.json res))))
+ (:items %)))
+ (p/then (fn [results]
+ (assoc % :items results))))
+ (map bookmarks)
+ p/all))
+
+(rf/reg-event-fx
+ ::add-imported-bookmark-lists
+ (fn [{:keys [db]} [_ bookmarks]]
+ {:promise {:call #(-> (fetch-imported-playlists-streams bookmarks)
+ (p/then (fn [res] (js->clj res :keywordize-keys true))))
+ :on-success-n [[::clear-notifications]
+ [::add-streams-to-imported-bookmark-lists]]}
+ :fx [[:dispatch [::add-notification {:status-text "Importing playlists..." :failure :success} false]]]}))
+
+(rf/reg-fx
+ ::import-bookmark-list
+ (fn [file]
+ (-> (.text file)
+ (p/then
+ #(let [res (js->clj (.parse js/JSON %) :keywordize-keys true)]
+ (if (= (:format res) "Tubo")
+ (rf/dispatch [::add-imported-bookmark-lists (:playlists res)])
+ (throw (js/Error. "Format not supported")))))
+ (p/catch js/Error
+ (fn [error]
+ (rf/dispatch [::add-notification {:status-text (.-message error) :failure :error}]))))))
+
+(rf/reg-event-fx
+ ::import-bookmark-lists
+ (fn [{:keys [db]} [_ files]]
+ {:fx (map (fn [file] [::import-bookmark-list file]) files)}))
+
+(rf/reg-event-fx
+ ::export-bookmark-lists
+ (fn [{:keys [db]} [_]]
+ {::download-file!
+ {:name "playlists.json"
+ :mime-type "application/json"
+ :data (.stringify js/JSON (clj->js {:format "Tubo"
+ :version 1
+ :playlists
+ (map (fn [bookmark]
+ {:name (:name bookmark)
+ :items (map :url (:items bookmark))})
+ (:bookmarks db))}))}
+ :fx [[:dispatch [::add-notification {:status-text "Exported playlists" :failure :success}]]]}))
+
(rf/reg-event-fx
::add-bookmark-list
[(rf/inject-cofx :store)]
@@ -545,6 +627,17 @@
[])})))
(rf/reg-event-fx
+ ::clear-bookmark-lists
+ (fn [{:keys [db]} _]
+ {:fx (apply merge
+ (map (fn [b] [:dispatch [::remove-bookmark-list (:id b)]]) (rest (:bookmarks db)))
+ (conj
+ (map (fn [s] [:dispatch [::remove-from-likes s]]) (:items (first (:bookmarks db))))
+ [:dispatch [::add-notification
+ {:status-text "Cleared all playlists"
+ :failure :success}]]))}))
+
+(rf/reg-event-fx
::add-to-likes
[(rf/inject-cofx :store)]
(fn [{:keys [db store]} [_ bookmark notify?]]
diff --git a/src/frontend/tubo/views/bookmarks.cljs b/src/frontend/tubo/views/bookmarks.cljs
index e1a0ed8..2e14a3a 100644
--- a/src/frontend/tubo/views/bookmarks.cljs
+++ b/src/frontend/tubo/views/bookmarks.cljs
@@ -6,6 +6,7 @@
[tubo.components.items :as items]
[tubo.components.layout :as layout]
[tubo.components.modal :as modal]
+ [tubo.components.modals.bookmarks :as bookmarks]
[tubo.events :as events]))
(defn add-bookmark-modal
@@ -18,25 +19,43 @@
[layout/secondary-button "Cancel"
#(rf/dispatch [::events/close-modal])]
[layout/primary-button "Create Playlist"
- #(rf/dispatch [::events/add-bookmark-list {:name @!bookmark-name}])]])))
+ #(rf/dispatch [::events/add-bookmark-list {:name @!bookmark-name} true])]])))
(defn bookmarks-page
[]
(let [!menu-active? (r/atom nil)]
- (let [service-color @(rf/subscribe [:service-color])
- bookmarks @(rf/subscribe [:bookmarks])
- items (map #(assoc %
- :url (rfe/href :tubo.routes/bookmark nil {:id (:id %)})
- :thumbnail-url (-> % :items first :thumbnail-url)
- :stream-count (count (:items %))
- :bookmark-id (:id %)) bookmarks)]
- [layout/content-container
- [layout/content-header "Bookmarks"
- [layout/popover-menu !menu-active?
- [{:label "Create playlist"
- :icon [:i.fa-solid.fa-plus]
- :on-click #(rf/dispatch [::events/open-modal [add-bookmark-modal]])}]]]
- [items/related-streams items]])))
+ (fn []
+ (let [service-color @(rf/subscribe [:service-color])
+ bookmarks @(rf/subscribe [:bookmarks])
+ items (map #(assoc %
+ :url (rfe/href :tubo.routes/bookmark nil {:id (:id %)})
+ :thumbnail-url (-> % :items first :thumbnail-url)
+ :stream-count (count (:items %))
+ :bookmark-id (:id %)) bookmarks)]
+ [layout/content-container
+ [layout/content-header "Bookmarks"
+ [layout/popover-menu !menu-active?
+ [{:label "Add New"
+ :icon [:i.fa-solid.fa-plus]
+ :on-click #(rf/dispatch [::events/open-modal [add-bookmark-modal]])}
+ [:<>
+ [:input.hidden
+ {:id "file-selector"
+ :type "file"
+ :multiple "multiple"
+ :on-click #(reset! !menu-active? true)
+ :on-change #(rf/dispatch [::events/import-bookmark-lists (.. % -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 "Import"]]
+ {:label "Export"
+ :icon [:i.fa-solid.fa-file-export]
+ :on-click #(rf/dispatch [::events/export-bookmark-lists])}
+ {:label "Clear All"
+ :icon [:i.fa-solid.fa-trash]
+ :on-click #(rf/dispatch [::events/clear-bookmark-lists])}]]]
+ [items/related-streams items]]))))
(defn bookmark-page
[]