mirror of
https://github.com/edufeed-org/edufeed-web.git
synced 2025-12-09 16:24:34 +00:00
Add support for Multi Filter Search
This commit is contained in:
parent
f1ba7eeab0
commit
903291146c
7 changed files with 374 additions and 179 deletions
|
|
@ -125,3 +125,15 @@
|
|||
:d
|
||||
"M9.965 11.026a5 5 0 1 1 1.06-1.06l2.755 2.754a.75.75 0 1 1-1.06 1.06l-2.755-2.754ZM10.5 7a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0Z",
|
||||
:clip-rule "evenodd"}]])
|
||||
|
||||
(defn filter-icon []
|
||||
[:svg
|
||||
{:xmlns "http://www.w3.org/2000/svg",
|
||||
:width "16",
|
||||
:height "16",
|
||||
:fill "currentColor",
|
||||
:class "bi bi-filter",
|
||||
:viewBox "0 0 16 16"}
|
||||
[:path
|
||||
{:d
|
||||
"M6 10.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5m-2-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5m-2-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5"}]])
|
||||
|
|
|
|||
|
|
@ -20,13 +20,13 @@
|
|||
;; values is an array of events that referenced the concept
|
||||
(let [_ (println "values " values)
|
||||
pubkeys (->> values
|
||||
(filter #(= "de" (:label-language %)))
|
||||
(map :pubkey))
|
||||
(filter #(= "de" (:label-language %)))
|
||||
(map :pubkey))
|
||||
profiles (re-frame/subscribe [::subs/profiles pubkeys])
|
||||
user-language (re-frame/subscribe [::subs/user-language])
|
||||
_ (js/console.log "group key and values" (clj->js values))
|
||||
_ (js/console.log (clj->js @profiles))]
|
||||
[:div {:on-click #(re-frame/dispatch [::events/handle-filter-search ["about.id" group-key]])
|
||||
user-language (re-frame/subscribe [::subs/user-language])]
|
||||
[:div {:on-click #(do
|
||||
(re-frame/dispatch [::events/navigate [:search-view]])
|
||||
(re-frame/dispatch [::events/handle-filter-search ["about.id" group-key]]))
|
||||
:data-tip (str
|
||||
(str/join
|
||||
", "
|
||||
|
|
|
|||
141
src/ied/components/skos_multiselect.cljs
Normal file
141
src/ied/components/skos_multiselect.cljs
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
(ns ied.components.skos-multiselect
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[reagent.core :as reagent]
|
||||
[clojure.string :as str]
|
||||
[ied.components.icons :as icons]
|
||||
[ied.subs :as subs]
|
||||
[ied.events :as events]))
|
||||
|
||||
(defn concept-checkbox [concept field toggled disable-on-change]
|
||||
[:input {:type "checkbox"
|
||||
:checked (or toggled false)
|
||||
:class "checkbox checkbox-warning"
|
||||
:on-change (fn [] (when-not disable-on-change (re-frame/dispatch [::events/toggle-concept [concept field]])))}])
|
||||
|
||||
|
||||
(defn highlight-match
|
||||
"Wraps the matching part of the text in bold markers, preserving the original capitalization.
|
||||
Arguments:
|
||||
- `text`: The full text (string).
|
||||
- `query`: The search term (string).
|
||||
Returns:
|
||||
- A Hiccup structure with the matching part bolded, or the original text if no match."
|
||||
[text query]
|
||||
(if (and text query)
|
||||
(let [lower-text (str/lower-case text)
|
||||
lower-query (str/lower-case query)
|
||||
index (str/index-of lower-text lower-query)]
|
||||
(if index
|
||||
[:span
|
||||
(subs text 0 index)
|
||||
[:span {:class "font-bold"} (subs text index (+ index (count query)))]
|
||||
(subs text (+ index (count query)))]
|
||||
text))
|
||||
text))
|
||||
|
||||
(defn concept-label-component
|
||||
[[concept field search-input]]
|
||||
(fn []
|
||||
(let [toggled-concepts @(re-frame/subscribe [::subs/toggled-concepts])
|
||||
toggled (some #(= (:id %) (:id concept)) toggled-concepts) ;; TODO could also be a subscription?
|
||||
prefLabel (highlight-match (-> concept :prefLabel :de) search-input)]
|
||||
[:li
|
||||
(if-let [narrower (:narrower concept)]
|
||||
[:details {:open false}
|
||||
[:summary {:class (when toggled "bg-orange-400 text-black")}
|
||||
[concept-checkbox concept field toggled]
|
||||
prefLabel]
|
||||
[:ul {:tabIndex "0"}
|
||||
(for [child narrower]
|
||||
^{:key (:id child)} [concept-label-component [child field]])]]
|
||||
[:a {:class (when toggled "bg-orange-400 text-black")
|
||||
:on-click (fn [] (re-frame/dispatch [::events/toggle-concept [concept field]]))}
|
||||
[concept-checkbox concept field toggled true]
|
||||
[:p
|
||||
prefLabel]])])))
|
||||
|
||||
(defn get-nested
|
||||
"Retrieve all values from nested paths in a node.
|
||||
Arguments:
|
||||
- `node`: The map representing a node.
|
||||
- `paths`: A sequence of keyword paths to retrieve values from the node."
|
||||
[node paths]
|
||||
(map #(get-in node %) paths))
|
||||
|
||||
(defn match-node?
|
||||
"Checks if the query matches any field value in the provided paths.
|
||||
Arguments:
|
||||
- `node`: The map representing a node.
|
||||
- `query`: The search term (string).
|
||||
- `paths`: A sequence of keyword paths to check in the node."
|
||||
[node query paths]
|
||||
(some #(str/includes? (str/lower-case (str %)) (str/lower-case query)) (get-nested node paths)))
|
||||
|
||||
(defn search-concepts
|
||||
"Recursively search for matches in `hasTopConcept` and `narrower`.
|
||||
Arguments:
|
||||
- `cs`: The Concept Scheme
|
||||
- `query`: The search term (string).
|
||||
- `paths`: A sequence of keyword paths to check in each concept node.
|
||||
Returns:
|
||||
- A map with the same structure as `cs`, containing only matching concepts
|
||||
and their parents."
|
||||
[cs query paths]
|
||||
(letfn [(traverse [concept]
|
||||
(let [children (:narrower concept)
|
||||
matched-children (keep traverse children)
|
||||
node-matches (match-node? concept query paths)]
|
||||
(when (or node-matches (seq matched-children))
|
||||
(-> concept
|
||||
(assoc :narrower (when (seq matched-children) matched-children))))))]
|
||||
(if-let [filtered-concepts (keep traverse (:hasTopConcept cs))]
|
||||
(assoc cs :hasTopConcept filtered-concepts)
|
||||
nil)))
|
||||
|
||||
(comment
|
||||
(def data {:id "http://w3id.org/openeduhub/vocabs/locationType/"
|
||||
:type "ConceptScheme"
|
||||
:title {:de "Typ eines Ortes, einer Einrichtung oder Organisation"
|
||||
:en "Type of place, institution or organisation"}
|
||||
:hasTopConcept [{:id "http://w3id.org/openeduhub/vocabs/locationType/bdc2d9f1-bef6-4bf4-844d-4efc1c99ddbe"
|
||||
:prefLabel {:de "Lernort"}
|
||||
:narrower [{:id "http://w3id.org/openeduhub/vocabs/locationType/0d08a1e9-09d4-4024-9b5c-7e4028e28ce5"
|
||||
:prefLabel {:de "Kindergarten/-betreuung"}}
|
||||
{:id "http://w3id.org/openeduhub/vocabs/locationType/1d0965c1-4a3e-4228-a600-bfa1d267e4d9"
|
||||
:prefLabel {:de "Schule"}}
|
||||
{:id "http://w3id.org/openeduhub/vocabs/locationType/729b6724-1dd6-476e-b871-44383ac11ef3"
|
||||
:prefLabel {:de "berufsbildende Schule"}}]}
|
||||
{:id "http://test.com/"
|
||||
:prefLabel {:de "Test"}}]})
|
||||
|
||||
(def fields [[:prefLabel :de] [:prefLabel :en]])
|
||||
(def query "Test")
|
||||
(println (pr-str (search-concepts data query fields))))
|
||||
|
||||
(defn skos-multiselect-component
|
||||
[[cs field field-title]]
|
||||
(let [search-input (reagent/atom "")]
|
||||
(fn []
|
||||
(let [filtered-cs (search-concepts cs @search-input [[:prefLabel :de]])] ;; TODO needs to be otherwise configured for multi language stuff
|
||||
[:div
|
||||
{:class "dropdown w-full"
|
||||
:key (:id cs)}
|
||||
[:div
|
||||
{:tabIndex "0"
|
||||
:role "button"
|
||||
:class "btn m-1 grow w-full"}
|
||||
field-title]
|
||||
[:div {:class "dropdown-content z-[1]"}
|
||||
[:ul {:class "menu bg-base-200 rounded-box w-96 "
|
||||
:tabIndex "0"}
|
||||
[:label {:class "input flex items-center gap-2"}
|
||||
[:input {:class "grow"
|
||||
:placeholder "Suche..."
|
||||
:type "text"
|
||||
:on-change (fn [e]
|
||||
(reset! search-input (-> e .-target .-value)))}]
|
||||
[icons/looking-glass]]
|
||||
(doall
|
||||
(for [concept (:hasTopConcept filtered-cs)]
|
||||
^{:key (str @search-input (:id concept))} [concept-label-component [concept field @search-input]]))]]]))))
|
||||
|
||||
|
|
@ -375,7 +375,7 @@
|
|||
_ (.log js/console (clj->js dispatch-events))]
|
||||
{:fx [[:dispatch-n dispatch-events]]})))
|
||||
|
||||
(defn sanitize-subscription-id
|
||||
(defn sanitize-subscription-id
|
||||
"cuts the subscription string at 64 chars"
|
||||
[s]
|
||||
(str/join "" (take 64 s)))
|
||||
|
|
@ -461,7 +461,6 @@
|
|||
{:#d d-tags}]]
|
||||
{::request-from-relay [sockets query]})))
|
||||
|
||||
|
||||
(defn cleanup-list-name [s]
|
||||
(-> s
|
||||
(str/replace #"\s" "-")
|
||||
|
|
@ -732,13 +731,26 @@
|
|||
:on-success [::save-concept-scheme]
|
||||
:on-failure [::failure]}}))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::fetch-missing-concept-schemes
|
||||
(fn [{:keys [db]} [_ missing-uris]]
|
||||
{:db db
|
||||
:http-xhrio (map (fn [uri]
|
||||
{:method :get
|
||||
:uri (jsonize-uri uri)
|
||||
:timeout 5000
|
||||
:response-format (ajax/json-response-format {:keywords? true})
|
||||
:on-success [::save-concept-scheme]
|
||||
:on-failure [::failure]})
|
||||
missing-uris)}))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::toggle-concept
|
||||
(fn [db [_ [concept field]]]
|
||||
(update-in db [:md-form-resource field] (fn [coll]
|
||||
(if (some #(= (:id concept) (:id %)) coll)
|
||||
(filter (fn [e] (not= (:id e) (:id concept))) coll)
|
||||
(conj coll (select-keys concept [:id :notation :prefLabel] )))))))
|
||||
(conj coll (select-keys concept [:id :notation :prefLabel])))))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::handle-md-form-input
|
||||
|
|
@ -783,17 +795,72 @@
|
|||
(defn sanitize-filter-term [term]
|
||||
(str/replace term #"[ ()]" " "))
|
||||
|
||||
(defn build-url-for-multi-filter-search
|
||||
"Builds a Typesense search URL with the given base URI, filters, and search term.
|
||||
|
||||
Arguments:
|
||||
- `base-uri` (string): The base URL of the Typesense API (e.g., http://localhost:8108).
|
||||
- `filters` (map): A map of filters where keys are filter attributes (keywords)
|
||||
and values are collections of maps, each containing an `:id` key.
|
||||
Example:
|
||||
{:about [{:id \"https://example.org/1\"} {:id \"https://example.org/2\"}]
|
||||
:type [{:id \"https://example.org/3\"}]}
|
||||
- `search-term` (string): The term to search for, defaults to `*` for wildcard searches.
|
||||
|
||||
Returns:
|
||||
- (string): A fully constructed URL for querying the Typesense API."
|
||||
[base-uri filters search-term]
|
||||
(let [extract-ids (fn [filter-key]
|
||||
(->> (get filters filter-key)
|
||||
(map :id)
|
||||
(str/join ",")))
|
||||
filter-by (->> filters
|
||||
(filter (fn [[_ v]] (seq v))) ; Exclude empty values
|
||||
(map (fn [[filter-key _]]
|
||||
(let [ids (extract-ids filter-key)]
|
||||
(str (name filter-key) ".id" ":=[" ids "]"))))
|
||||
(str/join "&&"))]
|
||||
(str base-uri
|
||||
"?q=" (or search-term "*")
|
||||
"&query_by=name,about,description,creator"
|
||||
(when (seq filter-by)
|
||||
(str "&filter_by=" filter-by)))))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::handle-multi-filter-search
|
||||
(fn [cofx [_ [filters search-term]]]
|
||||
(let [_ (.log js/console (clj->js filters ) )
|
||||
uri (build-url-for-multi-filter-search (str config/typesense-uri "search")
|
||||
filters
|
||||
search-term)
|
||||
_ (.log js/console "uri" uri)]
|
||||
{:http-xhrio {:method :get
|
||||
:uri uri
|
||||
:headers {"x-typesense-api-key" "xyz"}
|
||||
:timeout 5000
|
||||
:response-format (ajax/json-response-format {:keywords? true})
|
||||
:on-success [::save-search-results]
|
||||
:on-failure [::failure]}})))
|
||||
|
||||
(comment
|
||||
"http://localhost:8108/collections/amb/documents//collections/amb/documents/search?q=chemie&query_by=name,about,description,creator"
|
||||
"http://localhost:8108/collections/amb/documents/search?q=biologie&query_by=name,about,description,creator&filter_by=about.id:=[https://w3id.org/kim/hochschulfaechersystematik/n42]&&learningResourceType.id:=[]"
|
||||
)
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::handle-filter-search
|
||||
(fn [cofx [_ [filter-attribute filter-term]]]
|
||||
(fn [cofx [_ [filter-attribute filter-term search-term]]]
|
||||
(let [uri (str config/typesense-uri
|
||||
"search?q=*"
|
||||
"search?q="
|
||||
(or search-term "*")
|
||||
"&query_by="
|
||||
"name"
|
||||
"&filter_by="
|
||||
filter-attribute
|
||||
":="
|
||||
(sanitize-filter-term filter-term ))] ;; parantetheses seem to cause error when filtering
|
||||
"name,about,description,creator"
|
||||
(when filter-attribute
|
||||
(str
|
||||
"&filter_by="
|
||||
filter-attribute
|
||||
":="
|
||||
(sanitize-filter-term filter-term))))] ;; parantetheses seem to cause error when filtering
|
||||
{:http-xhrio {:method :get
|
||||
:uri uri
|
||||
:headers {"x-typesense-api-key" "xyz"}
|
||||
|
|
|
|||
|
|
@ -259,16 +259,37 @@
|
|||
(filter (fn [e] (some #(= (:pubkey e) %) following-pks)))
|
||||
(filter #(= 1 (:kind %))))))
|
||||
|
||||
; "Args:
|
||||
; - `schemes`: Array of strings identifying the concept schemes "
|
||||
; (re-frame/reg-sub
|
||||
; ::concept-schemes
|
||||
; (fn [db [_ schemes]]
|
||||
; (into {} (map (fn [cs]
|
||||
; [cs (get-in db [:concept-schemes cs]) nil])
|
||||
; schemes))))
|
||||
|
||||
(defn concept-schemes-sub
|
||||
"Args:
|
||||
- `schemes`: Array of strings identifying the concept schemes.
|
||||
|
||||
Returns a map of concept scheme identifiers to their values in the app-db."
|
||||
[db [_ schemes]]
|
||||
(into {} (map (fn [cs]
|
||||
[cs (get-in db [:concept-schemes cs])])
|
||||
schemes)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::concept-schemes
|
||||
(fn [db [_ schemes]]
|
||||
(into {} (map (fn [cs]
|
||||
[cs (get-in db [:concept-schemes cs]) nil])
|
||||
schemes))))
|
||||
::concept-schemes
|
||||
concept-schemes-sub)
|
||||
|
||||
(comment
|
||||
(get-in {:concept-schemes {"1" :yes "2" :no}} [:concept-schemes "3"] nil))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::active-filters
|
||||
(fn [db]
|
||||
(:md-form-resource db)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::toggled-concepts
|
||||
(fn [db]
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
[ied.subs :as subs]
|
||||
[ied.events :as events]
|
||||
[ied.components.icons :as icons]
|
||||
[ied.routes :as routes]))
|
||||
[ied.routes :as routes]
|
||||
[ied.components.skos-multiselect :refer [skos-multiselect-component ]]))
|
||||
|
||||
(def md-scheme-map
|
||||
{:amblight {:name {:title "Name"
|
||||
|
|
@ -81,137 +82,7 @@
|
|||
:about {:type :skos
|
||||
:schemes ["https://w3id.org/kim/hochschulfaechersystematik/scheme"]}}})
|
||||
|
||||
(defn concept-checkbox [concept field toggled disable-on-change]
|
||||
[:input {:type "checkbox"
|
||||
:checked (or toggled false)
|
||||
:class "checkbox checkbox-warning"
|
||||
:on-change (fn [] (when-not disable-on-change (re-frame/dispatch [::events/toggle-concept [concept field]])))}])
|
||||
|
||||
(defn highlight-match
|
||||
"Wraps the matching part of the text in bold markers, preserving the original capitalization.
|
||||
Arguments:
|
||||
- `text`: The full text (string).
|
||||
- `query`: The search term (string).
|
||||
Returns:
|
||||
- A Hiccup structure with the matching part bolded, or the original text if no match."
|
||||
[text query]
|
||||
(if (and text query)
|
||||
(let [lower-text (str/lower-case text)
|
||||
lower-query (str/lower-case query)
|
||||
index (str/index-of lower-text lower-query)]
|
||||
(if index
|
||||
[:span
|
||||
(subs text 0 index)
|
||||
[:span {:class "font-bold"} (subs text index (+ index (count query)))]
|
||||
(subs text (+ index (count query)))]
|
||||
text))
|
||||
text))
|
||||
|
||||
(defn concept-label-component
|
||||
[[concept field search-input]]
|
||||
(fn []
|
||||
(let [toggled-concepts @(re-frame/subscribe [::subs/toggled-concepts])
|
||||
toggled (some #(= (:id %) (:id concept)) toggled-concepts) ;; TODO could also be a subscription?
|
||||
prefLabel (highlight-match (-> concept :prefLabel :de) search-input)]
|
||||
[:li
|
||||
(if-let [narrower (:narrower concept)]
|
||||
[:details {:open false}
|
||||
[:summary {:class (when toggled "bg-orange-400 text-black")}
|
||||
[concept-checkbox concept field toggled]
|
||||
prefLabel]
|
||||
[:ul {:tabIndex "0"}
|
||||
(for [child narrower]
|
||||
^{:key (:id child)} [concept-label-component [child field]])]]
|
||||
[:a {:class (when toggled "bg-orange-400 text-black")
|
||||
:on-click (fn [] (re-frame/dispatch [::events/toggle-concept [concept field]]))}
|
||||
[concept-checkbox concept field toggled true]
|
||||
[:p
|
||||
prefLabel]])])))
|
||||
|
||||
(defn get-nested
|
||||
"Retrieve all values from nested paths in a node.
|
||||
Arguments:
|
||||
- `node`: The map representing a node.
|
||||
- `paths`: A sequence of keyword paths to retrieve values from the node."
|
||||
[node paths]
|
||||
(map #(get-in node %) paths))
|
||||
|
||||
(defn match-node?
|
||||
"Checks if the query matches any field value in the provided paths.
|
||||
Arguments:
|
||||
- `node`: The map representing a node.
|
||||
- `query`: The search term (string).
|
||||
- `paths`: A sequence of keyword paths to check in the node."
|
||||
[node query paths]
|
||||
(some #(str/includes? (str/lower-case (str %)) (str/lower-case query)) (get-nested node paths)))
|
||||
|
||||
(defn search-concepts
|
||||
"Recursively search for matches in `hasTopConcept` and `narrower`.
|
||||
Arguments:
|
||||
- `cs`: The Concept Scheme
|
||||
- `query`: The search term (string).
|
||||
- `paths`: A sequence of keyword paths to check in each concept node.
|
||||
Returns:
|
||||
- A map with the same structure as `cs`, containing only matching concepts
|
||||
and their parents."
|
||||
[cs query paths]
|
||||
(letfn [(traverse [concept]
|
||||
(let [children (:narrower concept)
|
||||
matched-children (keep traverse children)
|
||||
node-matches (match-node? concept query paths)]
|
||||
(when (or node-matches (seq matched-children))
|
||||
(-> concept
|
||||
(assoc :narrower (when (seq matched-children) matched-children))))))]
|
||||
(if-let [filtered-concepts (keep traverse (:hasTopConcept cs))]
|
||||
(assoc cs :hasTopConcept filtered-concepts)
|
||||
nil)))
|
||||
|
||||
(comment
|
||||
(def data {:id "http://w3id.org/openeduhub/vocabs/locationType/"
|
||||
:type "ConceptScheme"
|
||||
:title {:de "Typ eines Ortes, einer Einrichtung oder Organisation"
|
||||
:en "Type of place, institution or organisation"}
|
||||
:hasTopConcept [{:id "http://w3id.org/openeduhub/vocabs/locationType/bdc2d9f1-bef6-4bf4-844d-4efc1c99ddbe"
|
||||
:prefLabel {:de "Lernort"}
|
||||
:narrower [{:id "http://w3id.org/openeduhub/vocabs/locationType/0d08a1e9-09d4-4024-9b5c-7e4028e28ce5"
|
||||
:prefLabel {:de "Kindergarten/-betreuung"}}
|
||||
{:id "http://w3id.org/openeduhub/vocabs/locationType/1d0965c1-4a3e-4228-a600-bfa1d267e4d9"
|
||||
:prefLabel {:de "Schule"}}
|
||||
{:id "http://w3id.org/openeduhub/vocabs/locationType/729b6724-1dd6-476e-b871-44383ac11ef3"
|
||||
:prefLabel {:de "berufsbildende Schule"}}]}
|
||||
{:id "http://test.com/"
|
||||
:prefLabel {:de "Test"}}]})
|
||||
|
||||
(def fields [[:prefLabel :de] [:prefLabel :en]])
|
||||
(def query "Test")
|
||||
(println (pr-str (search-concepts data query fields))))
|
||||
|
||||
(defn skos-concept-scheme-multiselect-component
|
||||
[[cs field field-title]]
|
||||
(let [search-input (reagent/atom "")]
|
||||
(fn []
|
||||
(let [filtered-cs (search-concepts cs @search-input [[:prefLabel :de]])] ;; TODO needs to be otherwise configured for multi language stuff
|
||||
[:div
|
||||
{:class "dropdown w-full"
|
||||
:key (:id cs)}
|
||||
[:div
|
||||
{:tabIndex "0"
|
||||
:role "button"
|
||||
:class "btn m-1 grow w-full"}
|
||||
field-title]
|
||||
[:div {:class "dropdown-content z-[1]"}
|
||||
[:ul {:class "menu bg-base-200 rounded-box w-96 "
|
||||
:tabIndex "0"}
|
||||
[:label {:class "input flex items-center gap-2"}
|
||||
[:input {:class "grow"
|
||||
:placeholder "Suche..."
|
||||
:type "text"
|
||||
:on-change (fn [e]
|
||||
(reset! search-input (-> e .-target .-value)))}]
|
||||
[icons/looking-glass]]
|
||||
(doall
|
||||
(for [concept (:hasTopConcept filtered-cs)]
|
||||
^{:key (str @search-input (:id concept))} [concept-label-component [concept field @search-input]]))]]]))))
|
||||
|
||||
(defn array-fields-component [selected-md-scheme field field-title]
|
||||
(let [array-items-type (get-in selected-md-scheme [field :items :type])
|
||||
|
|
@ -315,7 +186,7 @@
|
|||
|
||||
(doall
|
||||
(for [cs (keys concept-schemes)]
|
||||
^{:key cs} [skos-concept-scheme-multiselect-component [(get concept-schemes cs)
|
||||
^{:key cs} [skos-multiselect-component [(get concept-schemes cs)
|
||||
field
|
||||
field-title]])))
|
||||
(= :array field-type)
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@
|
|||
[reagent.core :as reagent]
|
||||
[clojure.string :as str]
|
||||
[ied.components.resource :as resource-components]
|
||||
[ied.components.skos-multiselect :refer [skos-multiselect-component]]
|
||||
[ied.components.icons :as icons]
|
||||
[ied.nostr :as nostr]
|
||||
[ied.components.resource :as resource-component]
|
||||
[cljs.core :as c]))
|
||||
|
||||
;; TODO refactor this to also work with raw event data
|
||||
|
|
@ -71,7 +72,7 @@
|
|||
[:div {:class "flex flex-wrap"}
|
||||
(doall
|
||||
(for [[k v] keywords]
|
||||
^{:key k} (resource-component/keywords-component k)))]
|
||||
^{:key k} (resource-components/keywords-component k)))]
|
||||
|
||||
[:p {:class "line-clamp-3"} description]
|
||||
|
||||
|
|
@ -79,7 +80,7 @@
|
|||
[:label {:class "btn "
|
||||
:on-click #(re-frame/dispatch [::events/navigate [:naddr-view :naddr naddr]])}
|
||||
"Details"]
|
||||
[resource-component/add-to-list latest-event]]]
|
||||
[resource-components/add-to-list latest-event]]]
|
||||
|
||||
[:div {:class "w-1/4"}
|
||||
[:figure
|
||||
|
|
@ -90,31 +91,113 @@
|
|||
(nostr/get-image-from-metadata-event latest-event)
|
||||
:alt ""}]]]])))
|
||||
|
||||
(defn search-view-panel []
|
||||
; (defn filter-bar []
|
||||
; (let [concept-schemes (re-frame/subscribe
|
||||
; [::subs/concept-schemes ["https://w3id.org/kim/hochschulfaechersystematik/scheme"]])
|
||||
; _ (doall (for [[cs-uri _] (filter (fn [[_ v]] (nil? v)) @concept-schemes)]
|
||||
; (re-frame/dispatch [::events/skos-concept-scheme-from-uri cs-uri])))]
|
||||
|
||||
; (fn []
|
||||
; [:div
|
||||
; (doall
|
||||
; (for [cs (keys @concept-schemes)]
|
||||
; ^{:key cs} [skos-multiselect-component [(get @concept-schemes "https://w3id.org/kim/hochschulfaechersystematik/scheme")
|
||||
; :about
|
||||
; "Fachsystematik"]]))]))) ;; TODO reuse skos components
|
||||
|
||||
;; TODO make filters configurable with a map
|
||||
|
||||
(def filters
|
||||
[{:scheme "https://w3id.org/kim/hochschulfaechersystematik/scheme"
|
||||
:field :about
|
||||
:title "Fachsystematik"}
|
||||
{:scheme "https://w3id.org/kim/hcrt/scheme"
|
||||
:field :learningResourceType
|
||||
:title "Typ"}
|
||||
])
|
||||
|
||||
(defn filter-bar []
|
||||
(let [concept-schemes (re-frame/subscribe
|
||||
[::subs/concept-schemes (map :scheme filters)])]
|
||||
|
||||
(re-frame/dispatch
|
||||
[::events/fetch-missing-concept-schemes
|
||||
(->> @concept-schemes
|
||||
(filter (fn [[_ v]] (nil? v)))
|
||||
(map first))])
|
||||
|
||||
;; TODO maybe put this in concept-scheme component?
|
||||
(fn []
|
||||
[:div
|
||||
(doall
|
||||
(for [filter filters]
|
||||
^{:key (get-in @concept-schemes [(:scheme filter) :id])}
|
||||
[skos-multiselect-component [(get @concept-schemes (:scheme filter))
|
||||
(:field filter)
|
||||
(:title filter)]]))
|
||||
#_(doall
|
||||
(for [cs (keys @concept-schemes)]
|
||||
^{:key (get-in @concept-schemes [cs :id])}
|
||||
[skos-multiselect-component [(get @concept-schemes "https://w3id.org/kim/hcrt/scheme")
|
||||
:learningResourceType
|
||||
"Typ"]]))])))
|
||||
|
||||
(defn has-any-values?
|
||||
"checks if a map has any values"
|
||||
[m]
|
||||
(some
|
||||
(fn [[_ v]]
|
||||
(cond
|
||||
(map? v) (has-any-values? v)
|
||||
(coll? v) (seq (filter (complement nil?) v))
|
||||
:else (not (nil? v))))
|
||||
m))
|
||||
|
||||
(defn active-filters []
|
||||
(let [active-filters (re-frame/subscribe [::subs/active-filters])]
|
||||
(when (has-any-values? @active-filters)
|
||||
[:div
|
||||
(doall
|
||||
(for [field (keys @active-filters)]
|
||||
[:div {:class "badge badge-secondary"} "hello"]))])))
|
||||
|
||||
(defn search-bar []
|
||||
(let [search-term (reagent/atom nil)
|
||||
grouped-results (re-frame/subscribe [::subs/grouped-search-results])
|
||||
_ (.log js/console (clj->js @grouped-results))]
|
||||
filters (re-frame/subscribe [::subs/active-filters])
|
||||
show-filter-bar (reagent/atom false)]
|
||||
(fn []
|
||||
[:div {:class "flex flex-col"}
|
||||
[:div {:class "flex"} ;;TODO fix layout
|
||||
[:form {:class "w-3/4"
|
||||
:on-submit (fn [e]
|
||||
(.preventDefault e)
|
||||
(re-frame/dispatch [::events/handle-multi-filter-search [@filters @search-term]]))}
|
||||
[:label
|
||||
{:class "input input-bordered flex items-center gap-2 w-1/2 mx-auto"}
|
||||
[:input {:type "search"
|
||||
:class "grow"
|
||||
:placeholder "Search"
|
||||
:on-change (fn [e] (reset! search-term (-> e .-target .-value)))}]
|
||||
[:svg
|
||||
{:xmlns "http://www.w3.org/2000/svg",
|
||||
:viewBox "0 0 16 16",
|
||||
:fill "currentColor",
|
||||
:class "h-4 w-4 opacity-70"}
|
||||
[:path
|
||||
{:fill-rule "evenodd",
|
||||
:d
|
||||
"M9.965 11.026a5 5 0 1 1 1.06-1.06l2.755 2.754a.75.75 0 1 1-1.06 1.06l-2.755-2.754ZM10.5 7a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0Z",
|
||||
:clip-rule "evenodd"}]]]]
|
||||
[:button {:on-click #(reset! show-filter-bar (not @show-filter-bar))
|
||||
:class "btn"} [icons/filter-icon] "Filter"]]
|
||||
[active-filters]
|
||||
(when @show-filter-bar
|
||||
[filter-bar])])))
|
||||
|
||||
(defn search-view-panel []
|
||||
(let [grouped-results (re-frame/subscribe [::subs/grouped-search-results])]
|
||||
[:div {:class ""}
|
||||
[:form {:on-submit (fn [e]
|
||||
(.preventDefault e)
|
||||
(.log js/console @search-term)
|
||||
(re-frame/dispatch [::events/handle-search @search-term]))}
|
||||
[:label
|
||||
{:class "input input-bordered flex items-center gap-2 w-1/2 mx-auto"}
|
||||
[:input {:type "search"
|
||||
:class "grow"
|
||||
:placeholder "Search"
|
||||
:on-change (fn [e] (reset! search-term (-> e .-target .-value)))}]
|
||||
[:svg
|
||||
{:xmlns "http://www.w3.org/2000/svg",
|
||||
:viewBox "0 0 16 16",
|
||||
:fill "currentColor",
|
||||
:class "h-4 w-4 opacity-70"}
|
||||
[:path
|
||||
{:fill-rule "evenodd",
|
||||
:d
|
||||
"M9.965 11.026a5 5 0 1 1 1.06-1.06l2.755 2.754a.75.75 0 1 1-1.06 1.06l-2.755-2.754ZM10.5 7a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0Z",
|
||||
:clip-rule "evenodd"}]]]]
|
||||
[search-bar]
|
||||
(when (not-empty @grouped-results)
|
||||
[:div {:class "divide-y divide-slate-700 flex flex-col gap-2"}
|
||||
(doall
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue