From 758c276bf65f1be6d60736d1694f00c7e59d6cae Mon Sep 17 00:00:00 2001 From: Miguel Ángel Moreno Date: Fri, 19 Apr 2024 16:20:50 +0200 Subject: feat: add notification support --- src/frontend/tubo/components/notification.cljs | 28 +++++++++++ src/frontend/tubo/events.cljs | 70 +++++++++++++++++++++----- src/frontend/tubo/subs.cljs | 5 ++ src/frontend/tubo/views.cljs | 2 + 4 files changed, 92 insertions(+), 13 deletions(-) create mode 100644 src/frontend/tubo/components/notification.cljs (limited to 'src/frontend') diff --git a/src/frontend/tubo/components/notification.cljs b/src/frontend/tubo/components/notification.cljs new file mode 100644 index 0000000..61c10ad --- /dev/null +++ b/src/frontend/tubo/components/notification.cljs @@ -0,0 +1,28 @@ +(ns tubo.components.notification + (:require + [re-frame.core :as rf] + [tubo.events :as events])) + +(defn notification-content + [{:keys [failure parse-error status status-text] :as notification} key] + (when notification + [:div.py-4.pl-4.pr-8.rounded.backdrop-blur.flex.flex-col.justify-center.shadow.shadow-neutral-700 + {:class (clojure.string/join + "" (case failure + :success ["bg-green-600/90 text-white"] + :error ["bg-red-600/90 text-white"] + ["bg-neutral-300"]))} + [:button.text-lg.absolute.top-1.right-2 + {:on-click #(rf/dispatch [::events/remove-notification key])} + [:i.fa-solid.fa-close]] + [:span.font-bold (str (when status (str status ": ")) status-text)] + (when parse-error + [:span.line-clamp-1 (:status-text parse-error)])])) + +(defn notifications-panel + [] + (fn [] + (let [notifications @(rf/subscribe [:notifications])] + [:div.fixed.flex.flex-col.items-end.gap-2.top-16.z-20.w-full.py-1.px-2 + (for [[i notification] (map-indexed vector notifications)] + ^{:key i} [notification-content notification i])]))) diff --git a/src/frontend/tubo/events.cljs b/src/frontend/tubo/events.cljs index 678a0db..5aecfec 100644 --- a/src/frontend/tubo/events.cljs +++ b/src/frontend/tubo/events.cljs @@ -4,6 +4,7 @@ [day8.re-frame.http-fx] [goog.object :as gobj] [nano-id.core :refer [nano-id]] + [reagent.core :as r] [re-frame.core :as rf] [reitit.frontend.easy :as rfe] [reitit.frontend.controllers :as rfc] @@ -257,11 +258,42 @@ (fn [{:keys [name params query]}] (rfe/push-state name params query))) +(defonce timeouts! (r/atom {})) + +(rf/reg-fx + ::timeout! + (fn [{:keys [id event time]}] + (when-some [existing (get @timeouts! id)] + (js/clearTimeout existing) + (swap! timeouts! dissoc id)) + (when (some? event) + (swap! timeouts! assoc id + (js/setTimeout #(rf/dispatch event) time))))) + +(rf/reg-event-fx + ::add-notification + (fn [{:keys [db]} [_ data time]] + (let [updated-db (update db :notifications #(into [] (conj %1 %2)) data)] + {:db updated-db + :fx (when time + [[::timeout! {:id (-> updated-db :notifications count dec) + :event [::pop-notification] + :time time}]])}))) + +(rf/reg-event-fx + ::pop-notification + (fn [{:keys [db]} _] + {:fx [[:dispatch [::remove-notification (dec (count (:notifications db)))]]]})) + (rf/reg-event-db + ::remove-notification + (fn [db [_ pos]] + (update db :notifications #(into (subvec % 0 pos) (subvec % (inc pos)))))) + +(rf/reg-event-fx ::bad-response - (fn [db [_ res]] - (.log js/console (clj->js res)) - (assoc db :http-response (get-in res [:response :error])))) + (fn [{:keys [db]} [_ res]] + {:fx [[:dispatch [::add-notification res 2000]]]})) (rf/reg-event-db ::change-search-query @@ -466,7 +498,9 @@ (let [updated-db (update db :bookmarks conj (assoc bookmark :id (nano-id)))] {:db updated-db :store (assoc store :bookmarks (:bookmarks updated-db)) - :fx [[:dispatch [::close-modal]]]}))) + :fx [[:dispatch [::close-modal]] + [:dispatch [::add-notification {:status-text (str "Added playlist \"" (:name bookmark) "\"") + :failure :success} 2000]]]}))) (rf/reg-event-fx ::back-to-bookmark-list-modal @@ -483,9 +517,12 @@ ::remove-bookmark-list [(rf/inject-cofx :store)] (fn [{:keys [db store]} [_ id]] - (let [updated-db (update db :bookmarks #(into [] (remove (fn [bookmark] (= (:id bookmark) id)) %)))] + (let [bookmark (first (filter #(= (:id %) id) (:bookmarks db))) + updated-db (update db :bookmarks #(into [] (remove (fn [bookmark] (= (:id bookmark) id)) %)))] {:db updated-db - :store (assoc store :bookmarks (:bookmarks updated-db))}))) + :store (assoc store :bookmarks (:bookmarks updated-db)) + :fx [[:dispatch [::add-notification {:status-text (str "Removed playlist \"" (:name bookmark) "\"") + :failure :success} 2000]]]}))) (rf/reg-event-fx ::add-to-likes @@ -495,7 +532,8 @@ (let [updated-db (update-in db [:bookmarks 0 :items] #(into [] (conj (into [] %1) %2)) (assoc bookmark :bookmark-id (-> db :bookmarks first :id)))] {:db updated-db - :store (assoc store :bookmarks (:bookmarks updated-db))})))) + :store (assoc store :bookmarks (:bookmarks updated-db)) + :fx [[:dispatch [::add-notification {:status-text "Added to favorites" :failure :success} 2000]]]})))) (rf/reg-event-fx ::remove-from-likes @@ -503,7 +541,8 @@ (fn [{:keys [db store]} [_ bookmark]] (let [updated-db (update-in db [:bookmarks 0 :items] #(remove (fn [item] (= (:url item) (:url bookmark))) %))] {:db updated-db - :store (assoc store :bookmarks (:bookmarks updated-db))}))) + :store (assoc store :bookmarks (:bookmarks updated-db)) + :fx [[:dispatch [::add-notification {:status-text "Removed from favorites" :failure :success} 2000]]]}))) (rf/reg-event-fx ::add-to-bookmark-list @@ -517,16 +556,21 @@ (assoc item :bookmark-id (:id bookmark))))] {:db updated-db :store (assoc store :bookmarks (:bookmarks updated-db)) - :fx [[:dispatch [::close-modal]]]}))) + :fx [[:dispatch [::close-modal]] + [:dispatch [::add-notification {:status-text (str "Added to playlist \"" (:name bookmark-list) "\"") + :failure :success} 2000]]]}))) (rf/reg-event-fx ::remove-from-bookmark-list [(rf/inject-cofx :store)] (fn [{:keys [db store]} [_ bookmark]] - (let [bookmark-list (.indexOf (:bookmarks db) (first (filter #(= (:id %) (:bookmark-id bookmark)) (:bookmarks db)))) - updated-db (update-in db [:bookmarks bookmark-list :items] #(remove (fn [item] (= (:url item) (:url bookmark))) %))] - {:db updated-db - :store (assoc store :bookmarks (:bookmarks updated-db))}))) + (let [bookmark-list (first (filter #(= (:id %) (:bookmark-id bookmark)) (:bookmarks db))) + pos (.indexOf (:bookmarks db) bookmark-list) + 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 [::add-notification {:status-text (str "Removed from playlist \"" (:name bookmark-list) "\"") + :failure :success} 2000]]]}))) (rf/reg-event-db ::load-services diff --git a/src/frontend/tubo/subs.cljs b/src/frontend/tubo/subs.cljs index 9a82d6a..eec5ec8 100644 --- a/src/frontend/tubo/subs.cljs +++ b/src/frontend/tubo/subs.cljs @@ -71,6 +71,11 @@ (fn [db _] (:search-results db))) +(rf/reg-sub + :notifications + (fn [db _] + (:notifications db))) + (rf/reg-sub :modal (fn [db _] diff --git a/src/frontend/tubo/views.cljs b/src/frontend/tubo/views.cljs index 0618b51..3abc4e2 100644 --- a/src/frontend/tubo/views.cljs +++ b/src/frontend/tubo/views.cljs @@ -3,6 +3,7 @@ [re-frame.core :as rf] [tubo.components.audio-player :as player] [tubo.components.navigation :as navigation] + [tubo.components.notification :as notification] [tubo.components.play-queue :as queue] [tubo.events :as events])) @@ -14,6 +15,7 @@ [:div.min-h-screen.flex.flex-col.h-full.dark:text-white.dark:bg-neutral-900.relative [navigation/navbar current-match] [:div.flex.flex-col.flex-auto.justify-between.relative.font-nunito + [notification/notifications-panel] (when-let [view (-> current-match :data :view)] [view current-match]) [queue/queue] -- cgit v1.2.3