(ns microtables-frontend.core (:require [reagent.core :as r] ["mathjs" :as mathjs])) ; to generate random values ;for(let i = 0, s = new Set(); i < 10; i++){ let r = Math.floor(Math.random() * 15)+1, c = a[Math.floor(Math.random() * a.length)], k = `${c}${r}`; if(s.has(k)){ i--; continue; } s.add(k); v.push(`{:row ${r} :col "${c}" :value "${Math.floor(Math.random() * 10000)}"}`); } (def sample-data [{:row 1 :col "A" :value "59" :view :display} {:row 5 :col "C" :value "269" :view :display} {:row 4 :col "B" :value "7893" :view :display} {:row 2 :col "F" :value "8650" :view :display} {:row 6 :col "D" :value "4065" :view :display} {:row 7 :col "F" :value "5316" :view :display} {:row 12 :col "A" :value "2405" :view :display} {:row 5 :col "B" :value "7863" :view :display} {:row 9 :col "E" :value "3144" :view :display} {:row 10 :col "D" :value "8272" :view :display} {:row 11 :col "D" :value "2495" :view :display} {:row 15 :col "E" :value "8968" :view :display} {:row 7 :col "B" :value "=C5 + D6" :view :display} {:row 8 :col "B" :value "=B7 * 2" :view :display} {:row 7 :col "C" :value "=D1" :view :display}]) (defonce data-atom (r/atom sample-data)) (defn highest [dir data] (apply max (map dir data))) ; COLUMN NAMES (defn upgrade-letter-code [s] (let [l (last s)] (cond (empty? s) [65] (= l 90) (conj (upgrade-letter-code (subvec s 0 (dec (count s)))) 65) :else (conj (subvec s 0 (dec (count s))) (inc l))))) (defn next-letter [lc] (apply str (map char (upgrade-letter-code (mapv #(.charCodeAt % 0) lc))))) (def col-letters (iterate next-letter "A")) ; CHANGE VALUE FUNCTIONS (defn update-value [c r datum value] (if (nil? datum) (swap! data-atom conj {:row r :col c :value value}) (swap! data-atom (fn [d] (map #(if (and (= r (:row %)) (= c (:col %))) (assoc % :value value) %) d))))) (defn update-display [c r display] (println (str "trying to update " c r " to " display)) (swap! data-atom (fn [d] (map #(if (and (= r (:row %)) (= c (:col %))) (assoc % :display display) %) d)))) (defn toggle-display [c r view-mode] (println (str "toggling " c r " to " view-mode)) (swap! data-atom (fn [d] (map #(if (and (= r (:row %)) (= c (:col %))) (assoc % :view view-mode) %) d)))) ; CALCULATION / FORMULA EVALUATION FUNCTIONS (defn temp1 [data] (let [first-order (group-by #(not= (first (:value %)) "=") data) evaluated-cells (map #(keyword (str (:col %) (:row %))) (first-order true)) non-evaluated-cells (map #(keyword (str (:col %) (:row %))) (first-order false)) evaluated-values (map :value (first-order true)) non-evaluated-values (map #(subs (:value %) 1) (first-order false))] {:remaining (zipmap non-evaluated-cells non-evaluated-values) :evaluated (zipmap evaluated-cells evaluated-values)})) (def parse-variables (memoize (fn [expression] (js->clj (.getVariables mathjs expression))))) (defn temp3 [[k v]] (let [w (name k) c (re-find #"^[A-Z]+" w) r (.parseInt js/window (re-find #"[0-9]+$" w))] (do (update-display c r v) ) ) ) (defn temp2 [evaluated remaining] (if (empty? remaining) evaluated (let [vars (map #(parse-variables (val %)) remaining) remaining-vars (zipmap (keys remaining) vars) evaluated-vars (map (fn [x] (map #(let [item (keyword %)] (cond (contains? evaluated item) [% (evaluated item)] (contains? remaining item) [% :not-yet] :else [% 0] )) x)) vars) parsed (zipmap (keys remaining) evaluated-vars) ready-or-not (group-by (fn [[ k v ]] (some #(not= :not-yet (last %)) v)) parsed) ;TODO: detect circular references prepared (map (fn [[k v]] [k (.evaluate mathjs (remaining k) (apply js-obj (flatten v)))]) (ready-or-not true)) new-evaluated (reduce conj evaluated prepared) still-remaining (apply hash-map (flatten (map #(list (key %) (remaining (key %))) (ready-or-not nil)))) ;merged (map temp3 prepared) ;TODO: only merge after recursion is complete (outside this function) ] ;remaining ; {:B8 B7 * 2, :C7 D1, :B7 C5 + D6} ;vars ; ([B7] [D1] [C5 D6]) ;remaining-vars ; {:B8 [B7], :C7 [D1], :B7 [C5 D6]} ;parsed ; {:B8 ([B7 :not-yet]), :C7 ([D1 0]), :B7 ([C5 269] [D6 4065])} ;ready-or-not ; {nil [[:B8 ([B7 :not-yet])]], true [[:C7 ([D1 0])] [:B7 ([C5 269] [D6 4065])]]} ;prepared ; ([:C7 0] [:B7 4334]) ;still-remaining ; {:B8 B7 * 2} ;evaluated; TODO: change name, consider putting "data" directly in here (recur new-evaluated still-remaining) ;new-evaluated ) ) ) ;; ------------------------- ;; Views (defn cell [c r data] (let [datum (some #(if (and (= c (:col %)) (= r (:row %))) %) data)] ^{:key (str c r)} [:td [:input {:value (if (= (get datum :view nil) :value) (get datum :value "") (get datum :display (get datum :value ""))) :on-change #(update-value c r datum (.. % -target -value)) :on-blur (fn [e] (println (str "blur! " c r)) (toggle-display c r :display)) :on-focus (fn [e] (println (str "focus! " c r)) (toggle-display c r :value))}]] ) ) (defn row [r cols data] ^{:key (str "row-" r)} [:tr (cons ^{:key (str "row-head-" r)} [:th (str r)] (map #(cell % r data) cols))]) (defn header-row [cols] ^{:key "header"} [:tr (cons ^{:key "corner"} [:th] (map (fn [c] ^{:key (str "col-head-" c)} [:th c]) cols))]) (defn sheet [data] [:table [:tbody (let [maxrow (highest :row data) cols (take-while (partial not= (next-letter (highest :col data))) col-letters)] (cons (header-row cols) (map #(row % cols data) (range 1 (inc maxrow))))) ]]) (defn app [] [:div [:h3 "Microtables"] #_(do (println (.stringify js/JSON (.parse mathjs "3 + 4x"))) (println (js->clj (.parse js/JSON (.stringify js/JSON (.parse mathjs "3 + 4x"))))) (println (type (js->clj (.stringify js/JSON (.parse mathjs "3 + 4x"))))) (println (js-obj "a" 2 "b" "one")) (println (type (js-obj "a" 2 "b" "one"))) (println (type (.parse mathjs "3 + 4x"))) (println (.log js/console (.parse mathjs "3 + 4x"))) (println (.keys js/Object (.parse mathjs "3 + 4x"))) (println (js->clj (.parse mathjs "3 + 4x"))) (println (js->clj (.parse mathjs "3 + 4x") :keywordize-keys true)) (println (.-op (.parse mathjs "3 + 4x"))) (println (.-fn (.parse mathjs "3 + 4x"))) (println (js->clj (.-args (.parse mathjs "3 + 4x")))) (println (.-comment (.parse mathjs "3 + 4x"))) (println (.-implicit (.parse mathjs "3 + 4x"))) (println (.-value (.parse mathjs "3 + 4x"))) (println (second (.-args (.parse mathjs "3 + 4x")))) (println (.-implicit (second (.-args (.parse mathjs "3 + 4x"))))) (println (.-op (second (.-args (.parse mathjs "3 + 4x"))))) (println (.-fn (second (.-args (.parse mathjs "3 + 4x"))))) (println (.evaluate mathjs "3 + 4x" (js-obj "x" 10))) (println (filter #(not= (first (:value %)) "=") @data-atom)) (let [rt (temp1 @data-atom)] (do (println rt) (println (temp2 (:evaluated rt) (:remaining rt))))) (println "data-atom") (println @data-atom) "hi") [sheet @data-atom]]) ;; ------------------------- ;; Initialize app (defn mount-root [] (r/render [app] (.getElementById js/document "app"))) (defn init! [] (mount-root))