Files
integreat/src/cljs/auto_ap/views/utils.cljs
2022-07-26 07:01:18 -07:00

315 lines
10 KiB
Clojure

(ns auto-ap.views.utils
(:require
[cemerick.url]
[cljs-time.coerce :as c]
[cljs-time.core :as t]
[cljs-time.format :as format]
[cljs.tools.reader.edn :as edn]
[clojure.string :as str]
[goog.crypt.base64 :as base64]
[re-frame.core :as re-frame]
[react-transition-group :as react-transition-group]
#_{:clj-kondo/ignore [:unused-namespace]}
[react :as react]
[reagent.core :as r])
(:import
(goog.i18n NumberFormat)
(goog.i18n.NumberFormat Format)))
(def date-regex #"[2]{1}[0-9]{3}-[0-9]{1,2}-[0-9]{1,2}")
(def nff
(NumberFormat. Format/CURRENCY))
(defn nf
[num]
(.format nff (str num)))
(defn ->$ [x]
(nf x))
(defn- nf%
[num]
(.format (doto
(NumberFormat. Format/PERCENT)
(.setMaximumFractionDigits 1)
(.setMinimumFractionDigits 1))
(str num)))
(defn ->% [x]
(nf% x))
(defn ->short$ [x]
(cond
(nil? x)
nil
(int? x)
(str x)
(float? x)
(.toFixed x 2)
))
(defn active-when [active-page f & rest]
(when (apply f (into [active-page] rest)) " is-active"))
(def login-url
(let [client-id "264081895820-0nndcfo3pbtqf30sro82vgq5r27h8736.apps.googleusercontent.com"
redirect-uri (js/encodeURI (str (.-origin (.-location js/window)) "/api/oauth"))]
(str "https://accounts.google.com/o/oauth2/auth?access_type=online&client_id=" client-id "&redirect_uri=" redirect-uri "&response_type=code&max_auth_age=0&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile")))
(defn dispatch-value-change [event]
(fn [e]
(.preventDefault e)
(re-frame/dispatch (conj event (.. e -target -value)))))
(defn dispatch-event [event]
(fn [e]
(when (.-stopPropagation e)
(.stopPropagation e)
(.preventDefault e))
(re-frame/dispatch-sync event)))
(defn dispatch-event-with-propagation [event]
(fn [_]
(re-frame/dispatch-sync event)))
(def pretty-long (format/formatter "MM/dd/yyyy HH:mm:ss"))
(def pretty (format/formatter "MM/dd/yyyy"))
(def standard (format/formatter "yyyy-MM-dd"))
(defn date->str
([d] (date->str d pretty))
([d format]
(when d
(format/unparse format d))))
(defn str->date [d f]
(when d
(format/parse f d)))
(def css-transition-group
(r/adapt-react-class react-transition-group/CSSTransition))
(def transition
(r/adapt-react-class react-transition-group/Transition))
(def transition-group
(r/adapt-react-class react-transition-group/TransitionGroup))
(def switch-transition
(r/adapt-react-class react-transition-group/SwitchTransition))
(defn appearing [{:keys [visible? enter-class exit-class timeout]}]
(let [final-state (r/atom visible?)]
(fn [{:keys [visible?]} & children]
[css-transition-group {:in visible? :class-names {:exit exit-class :enter enter-class} :timeout timeout :onEnter (fn [] (reset! final-state true )) :onExited (fn [] (reset! final-state false))}
(if (or @final-state visible?)
(first children)
[:span])])))
(defn appearing-group []
(let [children (r/children (r/current-component))]
(into [transition-group {:exit true
:enter true}
(for [child children]
^{:key (:key (meta child))}
[transition
{:timeout 200
:exit true
:in true #_ (= current-stack- (:key (meta child)))}
(clj->js (fn [state]
(r/as-element
[:div {:style {
:transition "opacity 150ms ease-in-out"
:opacity (cond
(= "entered" state)
1.0
(= "entering" state)
0.0
(= "exiting" state)
0.0
(= "exited" state)
0.0)}}
child])))])])))
(defn coerce-date [d]
(cond (and (string? d)
(some->> (re-find #"^(\d{4})" d)
second
(js/parseInt)
(#(> % 2000))))
(try
(c/to-date-time (t/to-default-time-zone (t/from-default-time-zone (str->date d standard))))
(catch js/Error _
nil))
(instance? goog.date.DateTime d)
(c/to-date-time (t/to-default-time-zone (t/from-default-time-zone d)))
(instance? goog.date.Date d)
(c/to-date-time d)
:else
nil ))
(defn date-picker-internal [params]
(let [[text set-text ] (react/useState (some-> params :value coerce-date (date->str standard)))
[value set-value ] (react/useState (some-> params :value coerce-date))
swap-external-value (fn [new-value]
((:on-change params)
(cond (= :text (:output params))
(some-> new-value (date->str standard))
(= :cljs-date (:output params))
new-value
:else
(c/to-date new-value))))]
(react/useEffect (fn []
(let [prop-date (some-> params :value coerce-date)]
(when (not (t/= prop-date
value))
(set-value prop-date)
(if prop-date
(set-text (date->str prop-date standard))
(set-text ""))))))
[:div.field.has-addons
[:div.control
[:input.input (assoc params
:value text
:on-change (fn [e]
(set-text (.. e -target -value))
;; if it's a perfect match, change it on the spot
;; especially important for calendar clicking, don't
;; want to wait for blur
(when (or (re-matches date-regex (.. e -target -value))
(nil? (.. e -target -value)))
(swap-external-value (some-> (.. e -target -value) coerce-date))))
:on-blur (fn []
(swap-external-value (some-> text coerce-date))
(when (:on-blur params)
((:on-blur params))))
:type "date" :placeholder "12/1/2021")]
]]))
(defn date-picker []
[:f> date-picker-internal
(r/props (r/current-component))])
(defn local-now []
(t/to-default-time-zone (t/now)))
(defn local-today []
(t/at-midnight (t/to-default-time-zone (t/now))))
(def with-user
(re-frame/->interceptor
:id :with-user
:before (fn [context]
(-> context
(assoc-in [:coeffects :user] (get-in context [:coeffects :db :user]))))))
(def with-role
(re-frame/->interceptor
:id :with-role
:before (fn [context]
(-> context
(assoc-in [:coeffects :role] (-> (get-in context [:coeffects :db :user])
(str/split #"\.")
second
(base64/decodeString )
(#(.parse js/JSON % ))
(js->clj :keywordize-keys true)
:user/role))))))
(def with-is-admin?
(re-frame/->interceptor
:id :with-is-admin?
:before (fn [context]
(-> context
(assoc-in [:coeffects :is-admin?] (= "admin"
(-> (get-in context [:coeffects :db :user])
(str/split #"\.")
second
(base64/decodeString )
(#(.parse js/JSON % ))
(js->clj :keywordize-keys true)
:user/role)))))))
(defn query-params []
(reduce-kv
(fn [result k v]
(assoc result (keyword k) (edn/read-string v)))
{}
(:query (cemerick.url/url (.-location js/window)))))
(defn action-cell-width [cnt]
(str (inc (* cnt 51)) "px"))
(defn days-until [d]
(let [today (t/at-midnight (t/now))
d (t/at-midnight d)
in (if (t/after? today d)
(- (t/in-days (t/interval (t/minus d (t/days 1)) today)))
(t/in-days (t/interval today d )))]
in))
(defn copy-to-clipboard [text]
(let [el (js/document.createElement "textarea")]
(set! (.-value el) text)
(.appendChild js/document.body el)
(.select el)
(js/document.execCommand "copy")
(.removeChild js/document.body el)))
(defn str->int [x]
(cond
(nil? x)
nil
(and (string? x)
(str/blank? x))
nil
(string? x)
(js/parseInt x)
:else
x))
(defn parse-jwt [jwt]
(when-let [json (some-> jwt
(str/split #"\.")
second
base64/decodeString)]
(js->clj (.parse js/JSON json) :keywordize-keys true)))
(defn coerce-float [f]
(cond (str/blank? f)
nil
(float? f)
f
(and (string? f)
(not (js/Number.isNaN (js/parseFloat f))))
(js/parseFloat f)
:else
nil))