Browse Source

feat: add support for filters in search

Miguel Ángel Moreno 3 months ago
parent
commit
f11060a571

+ 8 - 5
src/backend/tubo/api.clj

@@ -249,25 +249,25 @@
       :disabled? false})))
 
 (defn get-search
-  ([service-id query content-filters sort-filter]
+  ([service-id query {:keys [filter sort]}]
    (let [service (NewPipe/getService service-id)
          query-handler
          (.. service
              (getSearchQHFactory)
-             (fromQuery query (or content-filters '()) (or sort-filter "")))
+             (fromQuery query (or filter '()) (or sort "")))
          info (SearchInfo/getInfo service query-handler)]
      {:items             (get-items (.getRelatedItems info))
       :next-page         (j/from-java (.getNextPage info))
       :service-id        service-id
       :search-suggestion (.getSearchSuggestion info)
       :corrected-search? (.isCorrectedSearch info)}))
-  ([service-id query content-filters sort-filter page-url]
+  ([service-id query {:keys [filter sort]} page-url]
    (let [service (NewPipe/getService service-id)
          url (url-decode page-url)
          query-handler
          (.. service
              (getSearchQHFactory)
-             (fromQuery query (or content-filters '()) (or sort-filter "")))
+             (fromQuery query (or filter '()) (or sort "")))
          info (SearchInfo/getMoreItems service query-handler (Page. url))]
      {:items     (get-items (.getItems info))
       :next-page (j/from-java (.getNextPage info))})))
@@ -343,7 +343,10 @@
                                {:name (.getDisplayCountry
                                        (Locale. "" (.toString country)))
                                 :code (.toString country)})
-                             (.getSupportedCountries service))})
+                             (.getSupportedCountries service))
+   :content-filters     (j/from-java (.. service
+                                         (getSearchQHFactory)
+                                         (getAvailableContentFilter)))})
 
 (defn get-services
   []

+ 3 - 3
src/backend/tubo/handler.clj

@@ -11,12 +11,12 @@
 
 (defn search
   [{{{:keys [service-id]} :path {:keys [q]} :query} :parameters
-    {:strs [contentFilters sortFilter nextPage]}    :query-params}]
+    {:strs [filter sort nextPage]}                  :query-params}]
   (response (apply api/get-search
                    service-id
                    q
-                   (and contentFilters (str/split contentFilters #","))
-                   sortFilter
+                   {:filter (and (seq filter) (str/split filter #","))
+                    :sort   sort}
                    (if nextPage [nextPage] []))))
 
 (defn channel

+ 12 - 9
src/frontend/tubo/layout/views.cljs

@@ -131,15 +131,18 @@
      :on-change on-change}]])
 
 (defn select-input
-  [label 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])]])
+  [value options on-change]
+  [: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])])
+
+(defn select-field
+  [label & args]
+  [generic-input label (apply select-input args)])
 
 (defn tooltip-item
   [{:keys [label icon on-click link] :as item}]

+ 2 - 44
src/frontend/tubo/navigation/views.cljs

@@ -1,58 +1,16 @@
 (ns tubo.navigation.views
   (:require
    [re-frame.core :as rf]
-   [reagent.core :as r]
    [reitit.frontend.easy :as rfe]
    [tubo.channel.views :as channel]
    [tubo.kiosks.views :as kiosks]
    [tubo.layout.views :as layout]
    [tubo.services.views :as services]
+   [tubo.search.views :as search]
    [tubo.stream.views :as stream]
    [tubo.playlist.views :as playlist]
    [tubo.bg-player.views :as bg-player]))
 
-(defn search-form
-  []
-  (let [!query (r/atom "")
-        !input (r/atom nil)]
-    (fn []
-      (let [search-query      @(rf/subscribe [:search/query])
-            show-search-form? @(rf/subscribe [:search/show-form])
-            service-id        @(rf/subscribe [:service-id])]
-        [:form.relative.text-white.flex.items-center.justify-center.flex-auto.lg:flex-1
-         {:class     (when-not show-search-form? "hidden")
-          :on-submit #(do (.preventDefault %)
-                          (when-not (empty? @!query)
-                            (rf/dispatch [:navigation/navigate
-                                          {:name   :search-page
-                                           :params {}
-                                           :query  {:q         search-query
-                                                    :serviceId service-id}}])))}
-         [:div.flex.items-center.relative.flex-auto.lg:flex-none
-          [:button.p-2
-           {:type "button" :on-click #(rf/dispatch [:search/show-form false])}
-           [:i.fa-solid.fa-arrow-left]]
-          [:input.w-full.lg:w-96.bg-transparent.pl-0.pr-6.m-2.border-none.focus:ring-transparent.placeholder-white
-           {:type          "text"
-            :ref           #(do (reset! !input %)
-                                (when %
-                                  (.focus %)))
-            :default-value @!query
-            :on-change     #(let [input (.. % -target -value)]
-                              (when-not (empty? input)
-                                (rf/dispatch [:search/change-query input]))
-                              (reset! !query input))
-            :placeholder   "Search"}]
-          [:button.p-3 {:type "submit"} [:i.fa-solid.fa-search]]
-          [:button.p-4.absolute.right-8
-           {:on-click #(when @!input
-                         (set! (.-value @!input) "")
-                         (reset! !query "")
-                         (.focus @!input))
-            :type     "button"
-            :class    (when (empty? @!query) :invisible)}
-           [:i.fa-solid.fa-xmark]]]]))))
-
 (defn nav-left-content
   [title]
   (let [show-search-form? @(rf/subscribe [:search/show-form])
@@ -155,7 +113,7 @@
          :stream-page   (:name @(rf/subscribe [:stream]))
          :playlist-page (:name @(rf/subscribe [:playlist]))
          nil)]
-      [search-form]
+      [search/search-form]
       [nav-right-content match]]]))
 
 (defn mobile-menu-item

+ 7 - 4
src/frontend/tubo/router.cljs

@@ -23,15 +23,18 @@
                       :controllers [{:start #(rf/dispatch [:fetch-homepage])}]}
       :web/search    {:view        search/search
                       :name        :search-page
-                      :controllers [{:parameters {:query [:q :serviceId]}
-                                     :start      (fn [{{:keys [serviceId q]}
+                      :controllers [{:parameters {:query [:q :serviceId
+                                                          :filter]}
+                                     :start      (fn [{{:keys [serviceId q
+                                                               filter]}
                                                        :query}]
                                                    (rf/dispatch
                                                     [:search/fetch-page
                                                      serviceId
-                                                     q]))
+                                                     q
+                                                     filter]))
                                      :stop       #(rf/dispatch
-                                                   [:search/show-form false])}]}
+                                                   [:search/leave-page])}]}
       :web/stream    {:view        stream/stream
                       :name        :stream-page
                       :controllers [{:parameters {:query [:url]}

+ 102 - 16
src/frontend/tubo/search/events.cljs

@@ -4,6 +4,25 @@
    [tubo.api :as api]
    [tubo.layout.views :as layout]))
 
+(defonce !timeouts (atom {}))
+
+(rf/reg-fx
+ :debounce
+ (fn [{:keys [id event time]}]
+   (js/clearTimeout (get @!timeouts id))
+   (swap! !timeouts assoc
+     id
+     (js/setTimeout (fn []
+                      (rf/dispatch event)
+                      (swap! !timeouts dissoc id))
+                    time))))
+
+(rf/reg-fx
+ :stop-debounce
+ (fn [id]
+   (js/clearTimeout (get @!timeouts id))
+   (swap! !timeouts dissoc id)))
+
 (rf/reg-event-fx
  :search/fetch
  (fn [_ [_ service-id on-success on-error params]]
@@ -14,10 +33,12 @@
 
 (rf/reg-event-fx
  :search/load-page
- (fn [{:keys [db]} [_ res]]
+ (fn [{:keys [db]} [_ {:keys [query filter]} res]]
    (let [search-res (js->clj res :keywordize-keys true)]
      {:db (assoc db
                  :search/results    search-res
+                 :search/query      query
+                 :search/filter     filter
                  :show-page-loading false)
       :fx [[:dispatch [:services/fetch search-res]]]})))
 
@@ -31,16 +52,23 @@
 
 (rf/reg-event-fx
  :search/fetch-page
- (fn [{:keys [db]} [_ service-id query]]
-   {:db (assoc db
-               :show-page-loading true
-               :search/show-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 "\"")]]}))
+ (fn [{:keys [db]} [_ service-id query filter]]
+   (let [default-filter (-> db
+                            :settings
+                            :default-filter
+                            (get (js/parseInt service-id)))]
+     {:db (assoc db
+                 :show-page-loading true
+                 :search/show-form  true
+                 :search/results    nil)
+      :fx [[:dispatch
+            [:search/fetch service-id
+             [:search/load-page {:query query :filter filter}]
+             [:search/bad-page-response service-id query]
+             (into {:q query}
+                   (when (or (seq filter) default-filter)
+                     {:filter (if (seq filter) filter default-filter)}))]]
+           [:document-title (str "Search for \"" query "\"")]]})))
 
 (rf/reg-event-db
  :search/load-paginated
@@ -59,22 +87,80 @@
 
 (rf/reg-event-fx
  :search/fetch-paginated
- (fn [{:keys [db]} [_ query id next-page-url]]
+ (fn [{:keys [db]} [_ {:keys [query id next-page-url filter]}]]
    (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)}]]]
+             (into
+              {:q        query
+               :nextPage (js/encodeURIComponent next-page-url)}
+              (when filter
+                {:filter filter}))]]]
       :db (assoc db :show-pagination-loading true)})))
 
+(rf/reg-event-fx
+ :search/leave-page
+ (fn []
+   {:fx [[:dispatch [:search/show-form false]]
+         [:dispatch [:search/change-filter nil]]]}))
+
 (rf/reg-event-db
  :search/show-form
  (fn [db [_ show?]]
    (assoc db :search/show-form show?)))
 
 (rf/reg-event-db
+ :search/set-query
+ (fn [db [_ query]]
+   (assoc db :search/query query)))
+
+(rf/reg-event-fx
  :search/change-query
- (fn [db [_ res]]
-   (assoc db :search/query res)))
+ (fn [_ [_ query]]
+   {:debounce {:id    :search/query
+               :event [:search/set-query query]
+               :time  150}}))
+
+(rf/reg-event-fx
+ :search/clear-query
+ (fn []
+   {:stop-debounce :search/query
+    :fx            [[:dispatch [:search/change-query nil]]]}))
+
+(rf/reg-event-fx
+ :search/cancel
+ (fn []
+   {:stop-debounce :search/query
+    :fx            [[:dispatch [:search/show-form false]]]}))
+
+(rf/reg-event-db
+ :search/change-filter
+ (fn [db [_ filter]]
+   (assoc db :search/filter filter)))
+
+(rf/reg-event-fx
+ :search/submit
+ (fn [{:keys [db]}]
+   (when (seq (:search/query db))
+     {:stop-debounce :search/query
+      :fx            [[:dispatch
+                       [:navigation/navigate
+                        {:name   :search-page
+                         :params {}
+                         :query  (into {:q         (:search/query db)
+                                        :serviceId (:service-id db)}
+                                       (when (seq (:search/filter db))
+                                         {:filter (:search/filter db)}))}]]]})))
+
+(rf/reg-event-fx
+ :search/set-filter
+ (fn [{:keys [db]} [_ filter]]
+   {:fx [[:dispatch
+          [:navigation/navigate
+           {:name   :search-page
+            :params {}
+            :query  {:serviceId (:service-id db)
+                     :q         (:search/query db)
+                     :filter    filter}}]]]}))

+ 5 - 0
src/frontend/tubo/search/subs.cljs

@@ -16,3 +16,8 @@
  :search/show-form
  (fn [db]
    (:search/show-form db)))
+
+(rf/reg-sub
+ :search/filter
+ (fn [db]
+   (:search/filter db)))

+ 47 - 4
src/frontend/tubo/search/views.cljs

@@ -5,6 +5,36 @@
    [tubo.items.views :as items]
    [tubo.layout.views :as layout]))
 
+(defn search-form
+  []
+  (let [!input (atom nil)]
+    (fn []
+      (let [query             @(rf/subscribe [:search/query])
+            show-search-form? @(rf/subscribe [:search/show-form])]
+        [:form.relative.text-white.flex.items-center.justify-center.flex-auto.lg:flex-1
+         {:class     (when-not show-search-form? "hidden")
+          :on-submit #(do (.preventDefault %) (rf/dispatch [:search/submit]))}
+         [:div.flex.items-center.relative.flex-auto.lg:flex-none
+          [:button.p-2
+           {:type "button" :on-click #(rf/dispatch [:search/cancel])}
+           [:i.fa-solid.fa-arrow-left]]
+          [:input.w-full.lg:w-96.bg-transparent.pl-0.pr-6.m-2.border-none.focus:ring-transparent.placeholder-white
+           {:type          "text"
+            :ref           #(reset! !input %)
+            :default-value query
+            :on-change     #(rf/dispatch [:search/change-query
+                                          (.. % -target -value)])
+            :placeholder   "Search"}]
+          [:button.p-3 {:type "submit"} [:i.fa-solid.fa-search]]
+          [:button.p-4.absolute.right-8
+           {:on-click #(when @!input
+                         (set! (.-value @!input) "")
+                         (rf/dispatch [:search/clear-query])
+                         (.focus @!input))
+            :type     "button"
+            :class    (when (empty? query) :invisible)}
+           [:i.fa-solid.fa-xmark]]]]))))
+
 (defn search
   []
   (let [!layout (r/atom (:items-layout @(rf/subscribe [:settings])))]
@@ -12,9 +42,22 @@
       (let [{:keys [items next-page]} @(rf/subscribe [:search/results])
             next-page-url             (:url next-page)
             service-id                (or @(rf/subscribe [:service-id])
-                                          serviceId)]
+                                          serviceId)
+            filter                    @(rf/subscribe [:search/filter])
+            service                   @(rf/subscribe [:services/current])
+            settings                  @(rf/subscribe [:settings])]
         [layout/content-container
-         [items/layout-switcher !layout]
+         [:div.flex.w-full.justify-between
+          [layout/select-input
+           (or filter (get (:default-filter settings) service-id))
+           (:content-filters service)
+           #(rf/dispatch [:search/set-filter (.. % -target -value)])]
+          [items/layout-switcher !layout]]
          [items/related-streams items next-page-url !layout
-          #(rf/dispatch [:search/fetch-paginated q service-id
-                         next-page-url])]]))))
+          #(rf/dispatch
+            [:search/fetch-paginated
+             {:query         q
+              :id            service-id
+              :filter        (or filter
+                                 (get (:default-filter settings) service-id))
+              :next-page-url next-page-url}])]]))))

+ 9 - 1
src/frontend/tubo/services/subs.cljs

@@ -1,7 +1,8 @@
 (ns tubo.services.subs
   (:require
    [re-frame.core :as rf]
-   [tubo.utils :as utils]))
+   [tubo.utils :as utils]
+   [tubo.services.views :as services]))
 
 (rf/reg-sub
  :service-id
@@ -26,3 +27,10 @@
  :services
  (fn [db]
    (:services db)))
+
+(rf/reg-sub
+ :services/current
+ (fn []
+   [(rf/subscribe [:services]) (rf/subscribe [:service-id])])
+ (fn [[services id]]
+   (get services id)))

+ 8 - 7
src/frontend/tubo/settings/views.cljs

@@ -11,7 +11,7 @@
 
 (defn select-input
   [label keys value options on-change]
-  [layout/select-input label value options
+  [layout/select-field label value options
    (or on-change
        #(rf/dispatch [:settings/change keys (.. % -target -value)]))])
 
@@ -22,15 +22,13 @@
    [select-input "List view mode" [:items-layout] items-layout #{:grid :list}]])
 
 (defn content-settings
-  [{:keys [default-service default-country default-kiosk show-description
-           show-comments show-related]}]
+  [{:keys [default-service default-country default-kiosk default-filter
+           show-description show-comments show-related]}]
   (let [services   @(rf/subscribe [:services])
+        service    @(rf/subscribe [:services/current])
         kiosks     @(rf/subscribe [:kiosks])
         service-id @(rf/subscribe [:service-id])
-        countries  (->> services
-                        (filter #(= (:id %) service-id))
-                        first
-                        :supported-countries)]
+        countries  (:supported-countries service)]
     [:<>
      [select-input "Default country" nil
       (:name (get default-country service-id)) (map :name countries)
@@ -60,6 +58,9 @@
      [select-input "Default kiosk" [:default-kiosk service-id]
       (or (get default-kiosk service-id) (:default-kiosk kiosks))
       (:available-kiosks kiosks)]
+     [select-input "Default filter" [:default-filter service-id]
+      (or (get default-filter service-id) (first (:content-filters service)))
+      (:content-filters service)]
      [boolean-input "Show comments" [:show-comments] show-comments]
      [boolean-input "Show description" [:show-description] show-description]
      [boolean-input "Show 'Next' and 'Similar' videos" [:show-related]