(ns auto-ap.views.components.grid (:require [reagent.core :as r] [auto-ap.views.utils :refer [appearing]] [react :as react])) (defonce grid-context (react/createContext "default")) (def Provider (.-Provider grid-context)) (def Consumer (.-Consumer grid-context)) (defn toggle-sort-by [params sort-key sort-name] (let [[found? sort] (reduce (fn [[found? sort] sort-item] (if (= sort-key (:sort-key sort-item)) [true (conj sort (update sort-item :asc not))] [found? (conj sort sort-item)])) [false []] (:sort params)) sort (if found? sort (conj sort {:sort-key sort-key :sort-name sort-name :asc true}))] (-> params (assoc :sort sort)))) (defn sort-icon [which sort] (let [sort-item (first (filter #(= which (:sort-key %)) sort))] (cond (and sort-item (:asc sort-item)) [:span.icon [:i.fa.fa-sort-up]] (and sort-item (not (:asc sort-item))) [:span.icon [:i.fa.fa-sort-down]] :else [:span.icon [:i.fa.fa-sort]]))) (defn bound [x y z] (cond (< z x) x (< y x) x (> y z) z :else y)) (defn paginator [{:keys [start end count total on-change]}] (let [per-page 100 max-buttons 5 buttons-before (Math/floor (/ max-buttons 2)) total-pages (Math/ceil (/ total per-page)) current-page (Math/floor (/ start per-page)) first-page-button (bound 0 (- current-page buttons-before) (- total-pages max-buttons)) all-buttons (into [] (for [x (range total-pages)] ^{:key x} [:li [:a.pagination-link {:class (when (= current-page x) "is-current") :on-click (fn [e] (on-change {:start (* x per-page)}))} (inc x)]])) last-page-button (Math/min total-pages (+ max-buttons first-page-button)) extended-last-page-button (when (not= last-page-button total-pages) (list ^ {:key -1} [:li [:span.pagination-ellipsis "…"]] ^ {:key -2} (last all-buttons))) extended-first-page-button (when (not= first-page-button 0) (list ^{:key -1} (first all-buttons) ^{:key -2} [:li [:span.pagination-ellipsis "…"]]))] [:nav.pagination {:role "pagination"} [:ul.pagination-list extended-first-page-button (apply list (subvec all-buttons first-page-button last-page-button)) extended-last-page-button "Showing " (Math/min (inc start) total) "-" end "/" total]])) (defn sort-by-list [{:keys [sort on-change]}] [:div.field.is-grouped.is-grouped-multiline (for [{:keys [sort-key sort-name asc]} sort] ^{:key sort-key} [:div.control [:div.tags.has-addons [:div.tag.is-medium [:span.icon (if asc [:i.fa.fa-sort-up] [:i.fa.fa-sort-down])] [:span sort-name] ] [:a.tag.is-medium.is-delete {:on-click (fn [] (on-change {:sort (filter #(not= sort-key (:sort-key %)) sort)}))}]]])]) (defn controls [{:keys [start end count total] :as para}] (let [children (r/children (r/current-component))] [:> Consumer {} (fn [consume] (let [{:strs [on-params-change params] :as consume} (js->clj consume)] (r/as-element (into [:div {:style {:margin-bottom "1rem"}} [:div.level (into [:div.level-left [:div.level-item [paginator {:start start :end end :count count :total total :on-change on-params-change}]] [:div.level-item [sort-by-list {:sort (:sort params) :on-change on-params-change}]]] (mapv (fn [c] [:div.level-item c]) children))]]))))])) (defn table [{:keys [fullwidth]}] (into [:table.table.compact.grid {:class (if fullwidth ["is-fullwidth"])}] (r/children (r/current-component)))) (defn header [] (into [:thead] (r/children (r/current-component)))) (defn header-cell [{:keys [style class]}] (into [:th {:style style :class class}] (r/children (r/current-component)))) (defn row [{:keys [class]}] (apply r/create-element "tr" #js {:className class} (map r/as-element (r/children (r/current-component))))) (defn button-cell [params] (apply r/create-element "td" #js {"style" #js {"overflow" "visible"}} (map r/as-element (r/children (r/current-component))))) (defn cell [params] (apply r/create-element "td" #js {} (map r/as-element (r/children (r/current-component)))) ) (defn body [] (let [children (r/children (r/current-component))] [:> Consumer {} (fn [consume] (let [{:strs [column-count status]} (js->clj consume)] (r/as-element (cond (= :loading (:state status)) ^{:key "loading-body"} [:tbody.test (for [i (range 20)] ^{:key i} [:tr (for [x (range column-count)] ^{:key x} [:td #_{:col-span column-count} [:div [:div.ph-item [:div.ph-row [:div.ph-col-12.big]]]]])])] (= :error (:state status)) ^{:key "error-body"} [:tbody [:tr [:td.has-text-centered {:col-span column-count} "An unexpected error has occured. " (-> status :error first :message) " Please try refreshing the page."]]] :else (into ^{:key "main-body"} [:tbody] children)))))])) (defn sortable-header-cell [{:keys [style class sort-key sort-name asc]}] (let [children (r/children (r/current-component))] [:> Consumer {} (fn [consume] (let [{:strs [on-params-change params] :as consume} (js->clj consume)] (r/as-element (conj (into [:th {:on-click (fn [e] (on-params-change (toggle-sort-by {:sort (:sort params)} sort-key sort-name))) :style (assoc style :cursor "pointer") :class class}] children) (sort-icon sort-key (:sort params))))))])) (defn grid [{:keys [on-params-change params status column-count]}] (r/create-element Provider #js {:value #js {:on-params-change on-params-change :params params :status status :column-count column-count}} (r/as-element (into [:<> ] (r/children (r/current-component)))))) (defn virtual-paginate [start xs ] (take 100 (drop (or start 0) xs))) (defn virtual-paginate-controls [start xs] {:start (or start 0) :end (min (+ (or start 0) 100) (count xs)) :total (count xs)})