Created
October 27, 2025 07:56
-
-
Save borkdude/ff65e3c5a10cb657e7df68aec35defc6 to your computer and use it in GitHub Desktop.
Ohm's calculator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (ns reagami.core | |
| {:clj-kondo/config '{:linters {:unresolved-symbol {:exclude [update!]}}}}) | |
| (def svg-ns "http://www.w3.org/2000/svg") | |
| (defn- parse-tag | |
| "From hiccup, thanks @weavejester" | |
| [^String tag] | |
| (let [id-index (let [index (.indexOf tag "#")] (when (pos? index) index)) | |
| class-index (let [index (.indexOf tag ".")] (when (pos? index) index))] | |
| [(cond | |
| id-index (.substring tag 0 id-index) | |
| class-index (.substring tag 0 class-index) | |
| :else tag) | |
| (when id-index | |
| (if class-index | |
| (.substring tag (inc id-index) class-index) | |
| (.substring tag (inc id-index)))) | |
| (when class-index | |
| (.substring tag (inc class-index)))])) | |
| (def properties (js/Set. [#_"value" "checked" "disabled" "selected"])) | |
| (defn property? [^js x] | |
| (.has properties x)) | |
| (do | |
| #?@(:squint [] | |
| :cljs [(defn keyword->str [k] | |
| (if (keyword? k) | |
| (name k) | |
| k))])) | |
| #?(:squint (defn array-seq [s] | |
| s)) | |
| #?(:squint (defn name [s] | |
| s)) | |
| (defn camel->kebab [s] | |
| (.replace s (js/RegExp. "[A-Z]" "g") | |
| (fn [m] | |
| (str "-" (.toLowerCase m))))) | |
| (defn- create-node* | |
| [hiccup in-svg?] | |
| (cond | |
| (or (nil? hiccup) | |
| (string? hiccup) | |
| (number? hiccup) | |
| (boolean? hiccup)) | |
| (js/document.createTextNode (str hiccup)) | |
| (vector? hiccup) | |
| (let [[tag & children] hiccup | |
| #?@(:squint [] | |
| :cljs [tag (if (keyword? tag) | |
| (subs (str tag) 1) | |
| tag)]) | |
| [tag id class] (if (string? tag) (parse-tag tag) [tag]) | |
| classes (when class (.split class ".")) | |
| [attrs children] (if (map? (first children)) | |
| [(first children) (rest children)] | |
| [nil children]) | |
| in-svg? (or in-svg? (= :svg tag)) | |
| node (if (fn? tag) | |
| (let [res (apply tag (if attrs | |
| (cons attrs children) | |
| children))] | |
| (create-node* res in-svg?)) | |
| (let [node (if in-svg? | |
| (js/document.createElementNS svg-ns tag) | |
| (js/document.createElement tag))] | |
| (doseq [child children] | |
| (let [child-nodes (if (and (seq? child) | |
| (not (vector? child))) | |
| (mapv #(create-node* % in-svg?) child) | |
| [(create-node* child in-svg?)])] | |
| (doseq [child-node child-nodes] | |
| (.appendChild node child-node)))) | |
| (let [modified-attrs (js/Set.)] | |
| (doseq [[k v] attrs] | |
| (let [k (name k) | |
| #?@(:squint [] :cljs [v (keyword->str v)])] | |
| (if (.startsWith k "on") | |
| (let [event (-> k | |
| (.replaceAll "-" "") | |
| (.toLowerCase))] | |
| (aset node event v) | |
| (.add modified-attrs event)) | |
| (do | |
| (.add modified-attrs k) | |
| (cond | |
| (and (= "style" k) (map? v)) | |
| (do (let [style (reduce-kv | |
| (fn [s k v] | |
| (str s (camel->kebab (name k)) ": " (name v) ";")) | |
| "" v)] | |
| ;; set/get attribute is faster to set, get | |
| ;; and compare (in patch)than setting | |
| ;; individual props and using cssText | |
| (.setAttribute node "style" style))) | |
| (property? k) (aset node k v) | |
| :else (when v (.setAttribute node k v))))))) | |
| (when (seq classes) | |
| (.setAttribute node "class" | |
| (str (when-let [c (.getAttribute node "class")] | |
| (str c " ")) | |
| (.join classes " "))) | |
| (.add modified-attrs "class")) | |
| (when id | |
| (.setAttribute node "id" id) | |
| (.add modified-attrs "id")) | |
| (aset node ::attrs modified-attrs)) | |
| node))] | |
| node) | |
| :else | |
| (throw (do | |
| (js/console.error "Invalid hiccup:" hiccup) | |
| (js/Error. (str "Invalid hiccup: " hiccup)))))) | |
| (defn- create-node [hiccup] | |
| (create-node* hiccup false)) | |
| (defn- patch [^js parent new-children] | |
| (let [old-children (.-childNodes parent)] | |
| (if (not= (alength old-children) (alength new-children)) | |
| (.apply parent.replaceChildren parent new-children) | |
| (doseq [[^js old ^js new] (mapv vector (array-seq old-children) (array-seq new-children))] | |
| (cond | |
| (and old new (= (.-nodeName old) (.-nodeName new))) | |
| (if (= 3 (.-nodeType old)) | |
| (let [txt (.-textContent new)] | |
| (set! (.-textContent old) txt)) | |
| (do | |
| (let [^js old-attrs (aget old ::attrs) | |
| ^js new-attrs (aget new ::attrs)] | |
| (doseq [o old-attrs] | |
| (when-not (.has new-attrs o) | |
| (if (or (.startsWith o "on") (property? o)) | |
| (aset old o nil) | |
| (.removeAttribute old o)))) | |
| (doseq [n new-attrs] | |
| (if (or (.startsWith n "on") | |
| (property? n)) | |
| (let [new-prop (aget new n)] | |
| (when-not (identical? (aget old n) | |
| new-prop) | |
| (aset old n new-prop))) | |
| (let [new-attr (.getAttribute new n)] | |
| (when-not false #_(identical? new-attr (.getAttribute old n)) | |
| (.setAttribute old n (.getAttribute new n))))))) | |
| (when-let [new-children (.-childNodes new)] | |
| (patch old new-children)))) | |
| :else (.replaceChild parent new old)))))) | |
| (defn reagami:render [root hiccup] | |
| (let [new-node (create-node hiccup)] | |
| (patch root #js [new-node]))) | |
| (defn slider [the-atom calc-fn param value min max step invalidates] | |
| [:input {:type "range" | |
| :value value | |
| :min min | |
| :max max | |
| :step step | |
| :style {:width "50%"} | |
| :on-input (fn [e] | |
| (let [new-value (parse-double | |
| (aget (aget e "target") "value"))] | |
| (swap! the-atom | |
| (fn [data] | |
| (-> data | |
| (assoc param new-value) | |
| (dissoc invalidates) | |
| calc-fn)))))}]) | |
| (defn calc-ohms [{:keys [voltage current resistance] :as data}] | |
| (if (nil? voltage) | |
| (assoc data :voltage (* current resistance)) | |
| (assoc data :current (/ voltage resistance)))) | |
| (def ohms-data (atom {:voltage 12 :current 0.5 :resistance 24})) | |
| (defn ohms-law-page [] | |
| (let [{:keys [voltage current resistance]} @ohms-data] | |
| [:div | |
| [:h3 "Ohm's Law Calculator"] | |
| [:div | |
| "Voltage: " (.toFixed voltage 2) "V" | |
| [slider ohms-data calc-ohms :voltage voltage 0 30 0.1 :current]] | |
| [:div | |
| "Current: " (.toFixed current 2) "A" | |
| [slider ohms-data calc-ohms :current current 0 3 0.01 :voltage]] | |
| [:div | |
| "Resistance: " (.toFixed resistance 2) "" | |
| [slider ohms-data calc-ohms :resistance resistance 0 100 1 :voltage]]])) | |
| (defn render [] | |
| (reagami:render (js/document.querySelector "#app") | |
| [ohms-law-page])) | |
| (add-watch ohms-data :state (fn [_ _ _ _] | |
| (render))) | |
| (render) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment