(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))