mirror of
https://github.com/logseq/logseq.git
synced 2026-04-29 00:24:51 +00:00
154 lines
5.5 KiB
Clojure
154 lines
5.5 KiB
Clojure
(ns frontend.rum
|
|
"Utility fns for rum"
|
|
(:require [clojure.string :as s]
|
|
[clojure.set :as set]
|
|
[clojure.walk :as w]
|
|
[rum.core :refer [use-state use-effect!] :as rum]
|
|
[daiquiri.interpreter :as interpreter]
|
|
[cljs-bean.core :as bean]))
|
|
|
|
;; copy from https://github.com/priornix/antizer/blob/35ba264cf48b84e6597743e28b3570d8aa473e74/src/antizer/core.cljs
|
|
|
|
(defn kebab-case->camel-case
|
|
"Converts from kebab case to camel case, eg: on-click to onClick"
|
|
[input]
|
|
(s/replace input #"-([a-z])" (fn [[_ c]] (s/upper-case c))))
|
|
|
|
(defn map-keys->camel-case
|
|
"Stringifys all the keys of a cljs hashmap and converts them
|
|
from kebab case to camel case. If :html-props option is specified,
|
|
then rename the html properties values to their dom equivalent
|
|
before conversion"
|
|
[data & {:keys [html-props]}]
|
|
(let [convert-to-camel (fn [[key value]]
|
|
[(kebab-case->camel-case (name key)) value])]
|
|
(w/postwalk (fn [x]
|
|
(if (map? x)
|
|
(let [new-map (if html-props
|
|
(set/rename-keys x {:class :className :for :htmlFor})
|
|
x)]
|
|
(into {} (map convert-to-camel new-map)))
|
|
x))
|
|
data)))
|
|
|
|
;; TODO: Replace this with rum's built in rum.core/adapt-class
|
|
;; adapted from https://github.com/tonsky/rum/issues/20
|
|
(defn adapt-class
|
|
([react-class]
|
|
(adapt-class react-class false))
|
|
([react-class skip-opts-transform?]
|
|
(fn [& args]
|
|
(let [[opts children] (if (map? (first args))
|
|
[(first args) (rest args)]
|
|
[{} args])
|
|
type# (first children)
|
|
;; we have to make sure to check if the children is sequential
|
|
;; as a list can be returned, eg: from a (for)
|
|
new-children (if (sequential? type#)
|
|
(let [result (interpreter/interpret children)]
|
|
(if (sequential? result)
|
|
result
|
|
[result]))
|
|
children)
|
|
;; convert any options key value to a react element, if
|
|
;; a valid html element tag is used, using sablono
|
|
vector->react-elems (fn [[key val]]
|
|
(if (sequential? val)
|
|
[key (interpreter/interpret val)]
|
|
[key val]))
|
|
new-options (into {}
|
|
(if skip-opts-transform?
|
|
opts
|
|
(map vector->react-elems opts)))]
|
|
(apply js/React.createElement react-class
|
|
;; sablono html-to-dom-attrs does not work for nested hashmaps
|
|
(bean/->js (map-keys->camel-case new-options :html-props true))
|
|
new-children)))))
|
|
|
|
(defn use-atom-fn
|
|
[a getter-fn setter-fn]
|
|
(let [[val set-val] (use-state (getter-fn @a))]
|
|
(use-effect!
|
|
(fn []
|
|
(let [id (str (random-uuid))]
|
|
(add-watch a id (fn [_ _ prev-state next-state]
|
|
(let [prev-value (getter-fn prev-state)
|
|
next-value (getter-fn next-state)]
|
|
(when-not (= prev-value next-value)
|
|
(set-val next-value)))))
|
|
#(remove-watch a id)))
|
|
[])
|
|
[val #(swap! a setter-fn %)]))
|
|
|
|
(defn use-atom
|
|
"(use-atom my-atom)"
|
|
[a]
|
|
(use-atom-fn a identity (fn [_ v] v)))
|
|
|
|
(defn use-atom-in
|
|
[a ks]
|
|
(let [ks (if (keyword? ks) [ks] ks)]
|
|
(use-atom-fn a #(get-in % ks) (fn [a' v] (assoc-in a' ks v)))))
|
|
|
|
(defn use-mounted
|
|
[]
|
|
(let [*mounted (rum/use-ref false)]
|
|
(use-effect!
|
|
(fn []
|
|
(rum/set-ref! *mounted true)
|
|
#(rum/set-ref! *mounted false))
|
|
[])
|
|
#(rum/deref *mounted)))
|
|
|
|
(defn use-bounding-client-rect
|
|
"Returns the bounding client rect for a given dom node
|
|
You can manually change the tick value, if you want to force refresh the value, you can manually change the tick value"
|
|
([] (use-bounding-client-rect nil))
|
|
([tick]
|
|
(let [[ref set-ref] (rum/use-state nil)
|
|
[rect set-rect] (rum/use-state nil)]
|
|
(rum/use-effect!
|
|
(if ref
|
|
(fn []
|
|
(let [update-rect #(set-rect (. ref getBoundingClientRect))
|
|
updator (fn [entries]
|
|
(when (.-contentRect (first (js->clj entries))) (update-rect)))
|
|
observer (js/ResizeObserver. updator)]
|
|
(update-rect)
|
|
(.observe observer ref)
|
|
#(.disconnect observer)))
|
|
#())
|
|
[ref tick])
|
|
[set-ref rect])))
|
|
|
|
(defn ->breakpoint
|
|
"Converts a number to a breakpoint string
|
|
Values come from https://tailwindcss.com/docs/responsive-design"
|
|
[size]
|
|
(cond
|
|
(nil? size) :md
|
|
(<= size 640) :sm
|
|
(<= size 768) :md
|
|
(<= size 1024) :lg
|
|
(<= size 1280) :xl
|
|
(<= size 1536) :xl
|
|
:else :2xl))
|
|
|
|
(defn use-breakpoint
|
|
"Returns the current breakpoint
|
|
You can manually change the tick value, if you want to force refresh the value, you can manually change the tick value"
|
|
([] (use-breakpoint nil))
|
|
([tick]
|
|
(let [[ref rect] (use-bounding-client-rect tick)
|
|
bp (->breakpoint (when (some? rect) (.-width rect)))]
|
|
[ref bp])))
|
|
|
|
(defonce *key->atom (atom {}))
|
|
(defn cached-derived-atom
|
|
"Make sure to return the same atom if `key` is the same."
|
|
[ref key f]
|
|
(or (get @*key->atom key)
|
|
(let [a (rum/derived-atom [ref] key f)]
|
|
(swap! *key->atom assoc key a)
|
|
a)))
|