mirror of
https://github.com/penpot/penpot.git
synced 2026-01-29 08:41:55 -05:00
Compare commits
1 Commits
develop
...
palba-fix-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53f5cec593 |
@@ -31,7 +31,7 @@
|
||||
- Fix unhandled exception on open-new-window helper [Github #7787](https://github.com/penpot/penpot/issues/7787)
|
||||
- Fix exception on uploading large fonts [Github #8135](https://github.com/penpot/penpot/pull/8135)
|
||||
- Fix boolean operators in menu for boards [Taiga #13174](https://tree.taiga.io/project/penpot/issue/13174)
|
||||
- Fix viewer can update library [Taiga #13186](https://tree.taiga.io/project/penpot/issue/13186)
|
||||
- Fix remove fill affects different element than selected [Taiga #13128](https://tree.taiga.io/project/penpot/issue/13128)
|
||||
|
||||
## 2.13.0 (Unreleased)
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
(db/insert-many! pool :audit-log event-columns events))))
|
||||
|
||||
(def valid-event-types
|
||||
#{"action" "identify" "trigger"})
|
||||
#{"action" "identify"})
|
||||
|
||||
(def schema:event
|
||||
[:map {:title "Event"}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
[app.main.data.profile :as dp]
|
||||
[app.main.data.websocket :as ws]
|
||||
[app.main.errors]
|
||||
[app.main.features :as feat]
|
||||
[app.main.rasterizer :as thr]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui :as ui]
|
||||
@@ -66,11 +65,8 @@
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ stream]
|
||||
(rx/merge
|
||||
(if (contains? cf/flags :audit-log)
|
||||
(rx/of (ev/initialize))
|
||||
(rx/empty))
|
||||
|
||||
(rx/of (dp/refresh-profile))
|
||||
(rx/of (ev/initialize)
|
||||
(dp/refresh-profile))
|
||||
|
||||
;; Watch for profile deletion events
|
||||
(->> stream
|
||||
@@ -91,12 +87,7 @@
|
||||
(rx/map deref)
|
||||
(rx/filter dp/is-authenticated?)
|
||||
(rx/take 1)
|
||||
(rx/map #(ws/initialize)))))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(when-not (feat/active-feature? state "render-wasm/v1")
|
||||
(thr/init!)))))
|
||||
(rx/map #(ws/initialize)))))))
|
||||
|
||||
(defn ^:export init
|
||||
[options]
|
||||
@@ -106,7 +97,7 @@
|
||||
(mw/init!)
|
||||
(i18n/init)
|
||||
(cur/init-styles)
|
||||
|
||||
(thr/init!)
|
||||
(init-ui)
|
||||
(st/emit! (plugins/initialize)
|
||||
(initialize)))
|
||||
|
||||
@@ -31,34 +31,40 @@
|
||||
(l/set-level! :info)
|
||||
|
||||
;; Defines the maximum buffer size, after events start discarding.
|
||||
(def ^:private ^:const max-buffer-size 1024)
|
||||
(def max-buffer-size 1024)
|
||||
|
||||
;; Defines the maximum number of events that can go in a single batch.
|
||||
(def ^:private ^:const max-chunk-size 100)
|
||||
(def max-chunk-size 100)
|
||||
|
||||
;; Defines the time window (in ms) within events belong to the same session.
|
||||
(def ^:private ^:const session-timeout (* 1000 60 30))
|
||||
(def session-timeout (* 1000 60 30))
|
||||
|
||||
|
||||
;; Min time for a long task to be reported to telemetry
|
||||
(def ^:private ^:const min-longtask-time 1000)
|
||||
(def min-longtask-time 1000)
|
||||
|
||||
;; Min time between long task reports
|
||||
(def ^:private ^:const debounce-longtask-time 1000)
|
||||
(def debounce-longtask-time 1000)
|
||||
|
||||
;; Min time for a long task to be reported to telemetry
|
||||
(def ^:private ^:const min-browser-event-time 1000)
|
||||
(def min-browser-event-time 1000)
|
||||
|
||||
;; Min time between long task reports
|
||||
(def ^:private ^:const debounce-browser-event-time 1000)
|
||||
(def debounce-browser-event-time 1000)
|
||||
|
||||
;; Min time for a long task to be reported to telemetry
|
||||
(def ^:private ^:const min-performace-event-time 1000)
|
||||
(def min-performace-event-time 1000)
|
||||
|
||||
;; Min time between long task reports
|
||||
(def ^:private ^:const debounce-performance-event-time 1000)
|
||||
(def debounce-performance-event-time 1000)
|
||||
|
||||
;; Default micro-benchmark iterations
|
||||
(def ^:private ^:const micro-benchmark-iterations 1e6)
|
||||
;; Def micro-benchmark iterations
|
||||
(def micro-benchmark-iterations 1e6)
|
||||
|
||||
;; Performance logs
|
||||
(defonce ^:private longtask-observer* (atom nil))
|
||||
(defonce ^:private stall-timer* (atom nil))
|
||||
(defonce ^:private current-op* (atom nil))
|
||||
|
||||
;; --- CONTEXT
|
||||
|
||||
@@ -136,12 +142,12 @@
|
||||
data
|
||||
data))
|
||||
|
||||
(defn- add-external-context-info
|
||||
(defn add-external-context-info
|
||||
[context]
|
||||
(let [external-context-info (json/->clj (cf/external-context-info))]
|
||||
(merge context external-context-info)))
|
||||
|
||||
(defn- make-proto-event
|
||||
(defn- process-event-by-proto
|
||||
[event]
|
||||
(let [data (d/deep-merge (-data event) (meta event))
|
||||
type (ptk/type event)
|
||||
@@ -150,6 +156,7 @@
|
||||
(assoc :event-origin (::origin data))
|
||||
(assoc :event-namespace (namespace type))
|
||||
(assoc :event-symbol ev-name)
|
||||
(add-external-context-info)
|
||||
(d/without-nils))
|
||||
props (-> data d/without-qualified simplify-props)]
|
||||
|
||||
@@ -158,7 +165,7 @@
|
||||
:context context
|
||||
:props props}))
|
||||
|
||||
(defn- make-data-event
|
||||
(defn- process-data-event
|
||||
[event]
|
||||
(let [data (deref event)
|
||||
name (::name data)]
|
||||
@@ -167,6 +174,7 @@
|
||||
(let [type (::type data "action")
|
||||
context (-> (::context data)
|
||||
(assoc :event-origin (::origin data))
|
||||
(add-external-context-info)
|
||||
(d/without-nils))
|
||||
props (-> data d/without-qualified simplify-props)]
|
||||
{:type type
|
||||
@@ -174,62 +182,57 @@
|
||||
:context context
|
||||
:props props}))))
|
||||
|
||||
(defn- make-event
|
||||
"Create a standard event"
|
||||
(defn performance-payload
|
||||
([result]
|
||||
(let [props (aget result 0)
|
||||
profile-id (aget result 1)]
|
||||
(make-event profile-id props)))
|
||||
([profile-id event]
|
||||
(when-let [event (cond
|
||||
(satisfies? Event event)
|
||||
(make-proto-event event)
|
||||
|
||||
(ptk/data-event? event)
|
||||
(make-data-event event))]
|
||||
(assoc event :profile-id profile-id))))
|
||||
|
||||
(defn- make-performance-event
|
||||
"Create a performance trigger event"
|
||||
([result]
|
||||
(let [props (aget result 0)
|
||||
profile-id (aget result 1)]
|
||||
(make-performance-event profile-id props)))
|
||||
(performance-payload profile-id props)))
|
||||
([profile-id props]
|
||||
(let [perf-info (get @st/state :performance-info)
|
||||
name (get props ::name)]
|
||||
{:type "trigger"
|
||||
:name (str "performance-" name)
|
||||
:context {:file-stats (:counters perf-info)}
|
||||
:props (-> props
|
||||
(dissoc ::name)
|
||||
(assoc :file-id (:file-id perf-info)))
|
||||
(let [{:keys [performance-info]} @st/state]
|
||||
{:type "action"
|
||||
:name "performance"
|
||||
:context (merge @context performance-info)
|
||||
:props props
|
||||
:profile-id profile-id})))
|
||||
|
||||
(defn- process-performance-event
|
||||
"Process performance sensitive events"
|
||||
[result]
|
||||
(let [event (aget result 0)
|
||||
profile-id (aget result 1)]
|
||||
(if (satisfies? PerformanceEvent event)
|
||||
|
||||
(if (and (satisfies? PerformanceEvent event)
|
||||
(exists? js/globalThis)
|
||||
(exists? (.-requestAnimationFrame js/globalThis))
|
||||
(exists? (.-scheduler js/globalThis))
|
||||
(exists? (.-postTask (.-scheduler js/globalThis))))
|
||||
(rx/create
|
||||
(fn [subs]
|
||||
(let [start (perf/now)]
|
||||
(let [start (perf/timestamp)]
|
||||
(js/requestAnimationFrame
|
||||
#(.postTask js/scheduler
|
||||
(fn []
|
||||
(let [time (- (perf/now) start)]
|
||||
(when (> time min-performace-event-time)
|
||||
(rx/push! subs
|
||||
(make-performance-event profile-id
|
||||
{::name "blocking-event"
|
||||
:event-name (d/name (ptk/type event))
|
||||
:duration time})))
|
||||
(rx/end! subs)))
|
||||
#js {:priority "user-blocking"}))
|
||||
nil)))
|
||||
#(js/scheduler.postTask
|
||||
(fn []
|
||||
(let [time (- (perf/timestamp) start)]
|
||||
(when (> time min-performace-event-time)
|
||||
(rx/push!
|
||||
subs
|
||||
(performance-payload
|
||||
profile-id
|
||||
{::event (str (ptk/type event))
|
||||
:time time}))))
|
||||
(rx/end! subs))
|
||||
#js {"priority" "user-blocking"})))
|
||||
nil))
|
||||
(rx/empty))))
|
||||
|
||||
(defn- process-event
|
||||
[event]
|
||||
(cond
|
||||
(satisfies? Event event)
|
||||
(process-event-by-proto event)
|
||||
|
||||
(ptk/data-event? event)
|
||||
(process-data-event event)))
|
||||
|
||||
;; --- MAIN LOOP
|
||||
|
||||
(defn- append-to-buffer
|
||||
@@ -257,8 +260,7 @@
|
||||
(rx/of nil)))
|
||||
|
||||
|
||||
(defn- user-input-observer
|
||||
"Create user interaction/input event observer. Returns rx stream."
|
||||
(defn performance-observer-event-stream
|
||||
[]
|
||||
(if (and (exists? js/globalThis)
|
||||
(exists? (.-PerformanceObserver js/globalThis)))
|
||||
@@ -271,17 +273,18 @@
|
||||
(fn [entry]
|
||||
(when (and (= "event" (.-entryType entry))
|
||||
(> (.-duration entry) min-browser-event-time))
|
||||
(rx/push! subs {::name "user-input"
|
||||
:duration (.-duration entry)
|
||||
:event-name (.-name entry)})))
|
||||
(rx/push!
|
||||
subs
|
||||
{::event :observer-event
|
||||
:duration (.-duration entry)
|
||||
:event-name (.-name entry)})))
|
||||
(.getEntries list))))]
|
||||
(.observe observer #js {:entryTypes #js ["event"]})
|
||||
(fn []
|
||||
(.disconnect observer)))))
|
||||
(rx/empty)))
|
||||
|
||||
(defn- longtask-observer
|
||||
"Create a Long-Task performance observer. Returns rx stream."
|
||||
(defn performance-observer-longtask-stream
|
||||
[]
|
||||
(if (and (exists? js/globalThis)
|
||||
(exists? (.-PerformanceObserver js/globalThis)))
|
||||
@@ -295,7 +298,7 @@
|
||||
(when (and (= "longtask" (.-entryType entry))
|
||||
(> (.-duration entry) min-longtask-time))
|
||||
(rx/push! subs
|
||||
{::name "long-task"
|
||||
{::event :observer-longtask
|
||||
:duration (.-duration entry)})))
|
||||
(.getEntries list))))]
|
||||
(.observe observer #js {:entryTypes #js ["longtask"]})
|
||||
@@ -303,156 +306,238 @@
|
||||
(.disconnect observer)))))
|
||||
(rx/empty)))
|
||||
|
||||
(defn- snapshot-performance-info
|
||||
[{:keys [file-id]}]
|
||||
|
||||
(letfn [(count-shapes [file]
|
||||
(->> file :data :pages-index
|
||||
(reduce-kv
|
||||
(fn [sum _ page]
|
||||
(+ sum (count (:objects page))))
|
||||
0)))
|
||||
|
||||
(add-libraries-counters [state files]
|
||||
(reduce (fn [state library-id]
|
||||
(let [data (dm/get-in files [library-id :data])]
|
||||
(-> state
|
||||
(update :total-components + (count (:components data)))
|
||||
(update :total-colors + (count (:colors data)))
|
||||
(update :total-typographies + (count (:typographies data))))))
|
||||
state
|
||||
(refs/select-libraries files file-id)))]
|
||||
|
||||
(ptk/reify ::snapshot-performance-info
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :performance-info
|
||||
(fn [info]
|
||||
(let [files (get state :files)
|
||||
file (get files file-id)]
|
||||
(-> info
|
||||
(assoc :file-id file-id)
|
||||
(update :counters assoc :total-shapes (count-shapes file))
|
||||
(update :counters add-libraries-counters files)))))))))
|
||||
|
||||
(defn- store-performace-info
|
||||
(defn- save-performance-info
|
||||
[]
|
||||
(ptk/reify ::store-performace-info
|
||||
(ptk/reify ::save-performance-info
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [start (perf/now)
|
||||
_ (loop [i micro-benchmark-iterations]
|
||||
(when-not (zero? i)
|
||||
(* (math/sin i) (math/sqrt i))
|
||||
(recur (dec i))))
|
||||
end (perf/now)]
|
||||
(letfn [(count-shapes [file]
|
||||
(->> file :data :pages-index
|
||||
(reduce-kv
|
||||
(fn [sum _ page]
|
||||
(+ sum (count (:objects page))))
|
||||
0)))
|
||||
(count-library-data [files {:keys [id]}]
|
||||
(let [data (dm/get-in files [id :data])]
|
||||
{:components (count (:components data))
|
||||
:colors (count (:colors data))
|
||||
:typographies (count (:typographies data))}))]
|
||||
(let [file-id (get state :current-file-id)
|
||||
file (get-in state [:files file-id])
|
||||
file-size (count-shapes file)
|
||||
|
||||
(update state :performance-info assoc :bench (- end start))))
|
||||
libraries
|
||||
(-> (refs/select-libraries (:files state) (:id file))
|
||||
(d/update-vals (partial count-library-data (:files state))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ stream]
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? :app.main.data.workspace/all-libraries-resolved))
|
||||
(rx/take 1)
|
||||
(rx/map deref)
|
||||
(rx/map snapshot-performance-info)))))
|
||||
lib-sizes
|
||||
(->> libraries
|
||||
(reduce-kv
|
||||
(fn [acc _ {:keys [components colors typographies]}]
|
||||
(-> acc
|
||||
(update :components + components)
|
||||
(update :colors + colors)
|
||||
(update :typographies + typographies)))
|
||||
{}))]
|
||||
(update state :performance-info
|
||||
(fn [info]
|
||||
(-> info
|
||||
(assoc :file-size file-size)
|
||||
(assoc :library-sizes lib-sizes)
|
||||
(assoc :file-start-time (perf/now))))))))))
|
||||
|
||||
(defn store-performace-info
|
||||
[]
|
||||
(letfn [(micro-benchmark [state]
|
||||
(let [start (perf/now)]
|
||||
(loop [i micro-benchmark-iterations]
|
||||
(when-not (zero? i)
|
||||
(* (math/sin i) (math/sqrt i))
|
||||
(recur (dec i))))
|
||||
(let [end (perf/now)]
|
||||
(update state :performance-info assoc :bench-result (- end start)))))]
|
||||
|
||||
(ptk/reify ::store-performace-info
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
micro-benchmark
|
||||
(assoc-in [:performance-info :app-start-time] (perf/now))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ stream]
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? :app.main.data.workspace/all-libraries-resolved))
|
||||
(rx/take 1)
|
||||
(rx/map save-performance-info))))))
|
||||
|
||||
(defn initialize
|
||||
[]
|
||||
(ptk/reify ::initialize
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (store-performace-info)))
|
||||
(when (contains? cf/flags :audit-log)
|
||||
(ptk/reify ::initialize
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (store-performace-info)))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ _ stream]
|
||||
(let [session (atom nil)
|
||||
stopper (rx/filter (ptk/type? ::initialize) stream)
|
||||
buffer (atom #queue [])
|
||||
profile (->> (rx/from-atom storage/user {:emit-current-value? true})
|
||||
(rx/map :profile)
|
||||
(rx/map :id)
|
||||
(rx/pipe (rxo/distinct-contiguous)))]
|
||||
ptk/EffectEvent
|
||||
(effect [_ _ stream]
|
||||
(let [session (atom nil)
|
||||
stopper (rx/filter (ptk/type? ::initialize) stream)
|
||||
buffer (atom #queue [])
|
||||
profile (->> (rx/from-atom storage/user {:emit-current-value? true})
|
||||
(rx/map :profile)
|
||||
(rx/map :id)
|
||||
(rx/pipe (rxo/distinct-contiguous)))]
|
||||
|
||||
(l/debug :hint "event instrumentation initialized")
|
||||
(l/debug :hint "event instrumentation initialized")
|
||||
|
||||
(->> (rx/merge
|
||||
(->> (rx/from-atom buffer)
|
||||
(rx/filter #(pos? (count %)))
|
||||
(rx/debounce 2000))
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? :app.main.data.profile/logout))
|
||||
(rx/observe-on :async)))
|
||||
(rx/map (fn [_]
|
||||
(into [] (take max-chunk-size) @buffer)))
|
||||
(rx/with-latest-from profile)
|
||||
(rx/mapcat (fn [[chunk profile-id]]
|
||||
(let [events (filterv #(= profile-id (:profile-id %)) chunk)]
|
||||
(->> (persist-events events)
|
||||
(rx/tap (fn [_]
|
||||
(l/debug :hint "events chunk persisted" :total (count chunk))))
|
||||
(rx/map (constantly chunk))))))
|
||||
(rx/take-until stopper)
|
||||
(rx/subs! (fn [chunk]
|
||||
(swap! buffer remove-from-buffer (count chunk)))
|
||||
(fn [cause]
|
||||
(l/error :hint "unexpected error on audit persistence" :cause cause))
|
||||
(fn []
|
||||
(l/debug :hint "audit persistence terminated"))))
|
||||
(->> (rx/merge
|
||||
(->> (rx/from-atom buffer)
|
||||
(rx/filter #(pos? (count %)))
|
||||
(rx/debounce 2000))
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? :app.main.data.profile/logout))
|
||||
(rx/observe-on :async)))
|
||||
(rx/map (fn [_]
|
||||
(into [] (take max-buffer-size) @buffer)))
|
||||
(rx/with-latest-from profile)
|
||||
(rx/mapcat (fn [[chunk profile-id]]
|
||||
(let [events (filterv #(= profile-id (:profile-id %)) chunk)]
|
||||
(->> (persist-events events)
|
||||
(rx/tap (fn [_]
|
||||
(l/debug :hint "events chunk persisted" :total (count chunk))))
|
||||
(rx/map (constantly chunk))))))
|
||||
(rx/take-until stopper)
|
||||
(rx/subs! (fn [chunk]
|
||||
(swap! buffer remove-from-buffer (count chunk)))
|
||||
(fn [cause]
|
||||
(l/error :hint "unexpected error on audit persistence" :cause cause))
|
||||
(fn []
|
||||
(l/debug :hint "audit persistence terminated"))))
|
||||
|
||||
(->> (rx/merge
|
||||
(->> stream
|
||||
(rx/with-latest-from profile)
|
||||
(rx/map make-event))
|
||||
(->> (rx/merge
|
||||
(->> stream
|
||||
(rx/with-latest-from profile)
|
||||
(rx/map (fn [result]
|
||||
(let [event (aget result 0)
|
||||
profile-id (aget result 1)]
|
||||
(some-> (process-event event)
|
||||
(update :profile-id #(or % profile-id)))))))
|
||||
|
||||
(->> (user-input-observer)
|
||||
(rx/with-latest-from profile)
|
||||
(rx/map make-performance-event)
|
||||
(rx/debounce debounce-browser-event-time))
|
||||
(->> (performance-observer-event-stream)
|
||||
(rx/with-latest-from profile)
|
||||
(rx/map performance-payload)
|
||||
(rx/debounce debounce-browser-event-time))
|
||||
|
||||
(->> (longtask-observer)
|
||||
(rx/with-latest-from profile)
|
||||
(rx/map make-performance-event)
|
||||
(rx/debounce debounce-longtask-time))
|
||||
(->> (performance-observer-longtask-stream)
|
||||
(rx/with-latest-from profile)
|
||||
(rx/map performance-payload)
|
||||
(rx/debounce debounce-longtask-time))
|
||||
|
||||
(if (and (exists? js/globalThis)
|
||||
(exists? (.-requestAnimationFrame js/globalThis))
|
||||
(exists? (.-scheduler js/globalThis))
|
||||
(exists? (.-postTask (.-scheduler js/globalThis))))
|
||||
(->> stream
|
||||
(rx/with-latest-from profile)
|
||||
(rx/merge-map process-performance-event)
|
||||
(rx/debounce debounce-performance-event-time))
|
||||
(rx/empty)))
|
||||
(rx/debounce debounce-performance-event-time)))
|
||||
|
||||
(rx/filter :profile-id)
|
||||
(rx/map (fn [event]
|
||||
(let [session* (or @session (ct/now))
|
||||
context (-> @context
|
||||
(merge (:context event))
|
||||
(assoc :session session*)
|
||||
(assoc :external-session-id (cf/external-session-id))
|
||||
(add-external-context-info)
|
||||
(d/without-nils))]
|
||||
(reset! session session*)
|
||||
(-> event
|
||||
(assoc :timestamp (ct/now))
|
||||
(assoc :context context)))))
|
||||
(rx/filter :profile-id)
|
||||
(rx/map (fn [event]
|
||||
(let [session* (or @session (ct/now))
|
||||
context (-> @context
|
||||
(merge (:context event))
|
||||
(assoc :session session*)
|
||||
(assoc :external-session-id (cf/external-session-id))
|
||||
(d/without-nils))]
|
||||
(reset! session session*)
|
||||
(-> event
|
||||
(assoc :timestamp (ct/now))
|
||||
(assoc :context context)))))
|
||||
|
||||
(rx/tap (fn [event]
|
||||
(l/debug :hint "event enqueued")
|
||||
(swap! buffer append-to-buffer event)))
|
||||
(rx/tap (fn [event]
|
||||
(l/debug :hint "event enqueued")
|
||||
(swap! buffer append-to-buffer event)))
|
||||
|
||||
(rx/switch-map #(rx/timer session-timeout))
|
||||
(rx/take-until stopper)
|
||||
(rx/subs! (fn [_]
|
||||
(l/debug :hint "session reinitialized")
|
||||
(reset! session nil))
|
||||
(fn [cause]
|
||||
(l/error :hint "error on event batching stream" :cause cause))
|
||||
(fn []
|
||||
(l/debug :hitn "events batching stream terminated"))))))))
|
||||
(rx/switch-map #(rx/timer session-timeout))
|
||||
(rx/take-until stopper)
|
||||
(rx/subs! (fn [_]
|
||||
(l/debug :hint "session reinitialized")
|
||||
(reset! session nil))
|
||||
(fn [cause]
|
||||
(l/error :hint "error on event batching stream" :cause cause))
|
||||
(fn []
|
||||
(l/debug :hitn "events batching stream terminated")))))))))
|
||||
|
||||
(defn event
|
||||
[props]
|
||||
(ptk/data-event ::event props))
|
||||
|
||||
;; --- DEVTOOLS PERF LOGGING
|
||||
|
||||
(defn install-long-task-observer! []
|
||||
(when (and (some? (.-PerformanceObserver js/window)) (nil? @longtask-observer*))
|
||||
(let [observer (js/PerformanceObserver.
|
||||
(fn [list _]
|
||||
(when (contains? cf/flags :perf-logs)
|
||||
(doseq [entry (.getEntries list)]
|
||||
(let [dur (.-duration entry)
|
||||
start (.-startTime entry)
|
||||
attrib (.-attribution entry)
|
||||
attrib-count (when attrib (.-length attrib))
|
||||
first-attrib (when (and attrib-count (> attrib-count 0)) (aget attrib 0))
|
||||
attrib-name (when first-attrib (.-name first-attrib))
|
||||
attrib-ctype (when first-attrib (.-containerType first-attrib))
|
||||
attrib-cid (when first-attrib (.-containerId first-attrib))
|
||||
attrib-csrc (when first-attrib (.-containerSrc first-attrib))]
|
||||
|
||||
(.warn js/console (str "[perf] long task " (Math/round dur) "ms at " (Math/round start) "ms"
|
||||
(when first-attrib
|
||||
(str " attrib:name=" attrib-name
|
||||
" ctype=" attrib-ctype
|
||||
" cid=" attrib-cid
|
||||
" csrc=" attrib-csrc)))))))))]
|
||||
(.observe observer #js{:entryTypes #js["longtask"]})
|
||||
(reset! longtask-observer* observer))))
|
||||
|
||||
(defn start-event-loop-stall-logger!
|
||||
"Log event loop stalls by measuring setInterval drift.
|
||||
interval-ms: base interval
|
||||
threshold-ms: drift over which we report"
|
||||
[interval-ms threshold-ms]
|
||||
(when (nil? @stall-timer*)
|
||||
(let [last (atom (.now js/performance))
|
||||
id (js/setInterval
|
||||
(fn []
|
||||
(when (contains? cf/flags :perf-logs)
|
||||
(let [now (.now js/performance)
|
||||
expected (+ @last interval-ms)
|
||||
drift (- now expected)
|
||||
current-op @current-op*
|
||||
measures (.getEntriesByType js/performance "measure")
|
||||
mlen (.-length measures)
|
||||
last-measure (when (> mlen 0) (aget measures (dec mlen)))
|
||||
meas-name (when last-measure (.-name last-measure))
|
||||
meas-detail (when last-measure (.-detail last-measure))
|
||||
meas-count (when meas-detail (unchecked-get meas-detail "count"))]
|
||||
(reset! last now)
|
||||
(when (> drift threshold-ms)
|
||||
(.warn js/console
|
||||
(str "[perf] event loop stall: " (Math/round drift) "ms"
|
||||
(when current-op (str " op=" current-op))
|
||||
(when meas-name (str " last=" meas-name))
|
||||
(when meas-count (str " count=" meas-count))))))))
|
||||
interval-ms)]
|
||||
(reset! stall-timer* id))))
|
||||
|
||||
(defn init!
|
||||
"Install perf observers in dev builds. Safe to call multiple times.
|
||||
Perf logs are disabled by default. Enable them with the :perf-logs flag in config."
|
||||
[]
|
||||
(when ^boolean js/goog.DEBUG
|
||||
(install-long-task-observer!)
|
||||
(start-event-loop-stall-logger! 50 100)
|
||||
;; Expose simple API on window for manual control in devtools
|
||||
(let [api #js {:reset (fn []
|
||||
(try
|
||||
(.clearMarks js/performance)
|
||||
(.clearMeasures js/performance)
|
||||
(catch :default _ nil)))}]
|
||||
(aset js/window "PenpotPerf" api))))
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.variant :as ctv]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.main.data.changes :as dch]
|
||||
[app.main.data.comments :as dcmt]
|
||||
[app.main.data.common :as dcm]
|
||||
@@ -76,7 +75,6 @@
|
||||
[app.util.dom :as dom]
|
||||
[app.util.globals :as ug]
|
||||
[app.util.http :as http]
|
||||
[app.util.perf :as perf]
|
||||
[app.util.storage :as storage]
|
||||
[app.util.timers :as tm]
|
||||
[app.util.webapi :as wapi]
|
||||
@@ -197,7 +195,7 @@
|
||||
(rx/of (check-libraries-synchronization file-id libraries))))))
|
||||
|
||||
;; This events marks that all the libraries have been resolved
|
||||
(rx/of (ptk/data-event ::all-libraries-resolved {:file-id file-id})))
|
||||
(rx/of (ptk/data-event ::all-libraries-resolved)))
|
||||
(rx/take-until stopper-s))))))
|
||||
|
||||
(defn- workspace-initialized
|
||||
@@ -350,11 +348,10 @@
|
||||
:file-id file-id}))))))
|
||||
|
||||
;; Install dev perf observers once the workspace is ready
|
||||
(when (contains? cf/flags :perf-logs)
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::workspace-initialized))
|
||||
(rx/take 1)
|
||||
(rx/tap (fn [_] (perf/setup)))))
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::workspace-initialized))
|
||||
(rx/take 1)
|
||||
(rx/map (fn [_] (ev/init!))))
|
||||
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::dps/persistence-notification))
|
||||
|
||||
@@ -1205,7 +1205,6 @@
|
||||
file (dsh/lookup-file state file-id)
|
||||
file-data (get file :data)
|
||||
ignore-until (get file :ignore-sync-until)
|
||||
permissions (:permissions state)
|
||||
|
||||
libraries-need-sync
|
||||
(->> (vals (get state :files))
|
||||
@@ -1225,8 +1224,7 @@
|
||||
do-dismiss
|
||||
#(st/emit! ignore-sync (ntf/hide))]
|
||||
|
||||
(when (and (:can-edit permissions)
|
||||
(seq libraries-need-sync))
|
||||
(when (seq libraries-need-sync)
|
||||
(rx/of (ntf/dialog
|
||||
:content (tr "workspace.updates.there-are-updates")
|
||||
:controls :inline-actions
|
||||
|
||||
@@ -105,15 +105,9 @@
|
||||
(if (dsh/lookup-page state file-id page-id)
|
||||
(rx/concat
|
||||
(rx/of (initialize-page* file-id page-id)
|
||||
(fdf/fix-deleted-fonts-for-page file-id page-id))
|
||||
|
||||
;; Disable thumbnail generation in wasm renderer
|
||||
(if (features/active-feature? state "render-wasm/v1")
|
||||
(rx/empty)
|
||||
(rx/of (dwth/watch-state-changes file-id page-id)))
|
||||
|
||||
(rx/of (dwl/watch-component-changes))
|
||||
|
||||
(fdf/fix-deleted-fonts-for-page file-id page-id)
|
||||
(dwth/watch-state-changes file-id page-id)
|
||||
(dwl/watch-component-changes))
|
||||
(let [profile (:profile state)
|
||||
props (get profile :props)]
|
||||
(when (not (:workspace-visited props))
|
||||
|
||||
@@ -191,63 +191,59 @@
|
||||
[page-id [event [old-data new-data]]]
|
||||
|
||||
(let [changes (:changes event)
|
||||
;; cache for the get-frame-ids function
|
||||
frame-id-cache (atom {})]
|
||||
lookup-data-objects
|
||||
(fn [data page-id]
|
||||
(dm/get-in data [:pages-index page-id :objects]))
|
||||
|
||||
(letfn [(lookup-data-objects [data page-id]
|
||||
(dm/get-in data [:pages-index page-id :objects]))
|
||||
|
||||
(extract-ids [{:keys [page-id type] :as change}]
|
||||
(case type
|
||||
:add-obj [[page-id (:id change)]]
|
||||
:mod-obj [[page-id (:id change)]]
|
||||
:del-obj [[page-id (:id change)]]
|
||||
:mov-objects (->> (:shapes change) (map #(vector page-id %)))
|
||||
[]))
|
||||
extract-ids
|
||||
(fn [{:keys [page-id type] :as change}]
|
||||
(case type
|
||||
:add-obj [[page-id (:id change)]]
|
||||
:mod-obj [[page-id (:id change)]]
|
||||
:del-obj [[page-id (:id change)]]
|
||||
:mov-objects (->> (:shapes change) (map #(vector page-id %)))
|
||||
[]))
|
||||
|
||||
(get-frame-ids [id]
|
||||
(let [old-objects (lookup-data-objects old-data page-id)
|
||||
new-objects (lookup-data-objects new-data page-id)
|
||||
get-frame-ids
|
||||
(fn get-frame-ids [id]
|
||||
(let [old-objects (lookup-data-objects old-data page-id)
|
||||
new-objects (lookup-data-objects new-data page-id)
|
||||
|
||||
new-shape (get new-objects id)
|
||||
old-shape (get old-objects id)
|
||||
new-shape (get new-objects id)
|
||||
old-shape (get old-objects id)
|
||||
|
||||
old-frame-id (if (cfh/frame-shape? old-shape) id (:frame-id old-shape))
|
||||
new-frame-id (if (cfh/frame-shape? new-shape) id (:frame-id new-shape))
|
||||
old-frame-id (if (cfh/frame-shape? old-shape) id (:frame-id old-shape))
|
||||
new-frame-id (if (cfh/frame-shape? new-shape) id (:frame-id new-shape))
|
||||
|
||||
root-frame-old? (cfh/root-frame? old-objects old-frame-id)
|
||||
root-frame-new? (cfh/root-frame? new-objects new-frame-id)
|
||||
instance-root? (ctc/instance-root? new-shape)]
|
||||
root-frame-old? (cfh/root-frame? old-objects old-frame-id)
|
||||
root-frame-new? (cfh/root-frame? new-objects new-frame-id)
|
||||
instance-root? (ctc/instance-root? new-shape)]
|
||||
|
||||
(cond-> #{}
|
||||
root-frame-old?
|
||||
(conj ["frame" old-frame-id])
|
||||
(cond-> #{}
|
||||
root-frame-old?
|
||||
(conj ["frame" old-frame-id])
|
||||
|
||||
root-frame-new?
|
||||
(conj ["frame" new-frame-id])
|
||||
root-frame-new?
|
||||
(conj ["frame" new-frame-id])
|
||||
|
||||
instance-root?
|
||||
(conj ["component" id])
|
||||
instance-root?
|
||||
(conj ["component" id])
|
||||
|
||||
(and (uuid? (:frame-id old-shape))
|
||||
(not= uuid/zero (:frame-id old-shape)))
|
||||
(into (get-frame-ids (:frame-id old-shape)))
|
||||
(and (uuid? (:frame-id old-shape))
|
||||
(not= uuid/zero (:frame-id old-shape)))
|
||||
(into (get-frame-ids (:frame-id old-shape)))
|
||||
|
||||
(and (uuid? (:frame-id new-shape))
|
||||
(not= uuid/zero (:frame-id new-shape)))
|
||||
(into (get-frame-ids (:frame-id new-shape))))))
|
||||
(and (uuid? (:frame-id new-shape))
|
||||
(not= uuid/zero (:frame-id new-shape)))
|
||||
(into (get-frame-ids (:frame-id new-shape))))))]
|
||||
|
||||
(get-frame-ids-cached [id]
|
||||
(or (get @frame-id-cache id)
|
||||
(let [result (get-frame-ids id)]
|
||||
(swap! frame-id-cache assoc id result)
|
||||
result)))]
|
||||
(into #{}
|
||||
(comp (mapcat extract-ids)
|
||||
(filter (fn [[page-id']] (= page-id page-id')))
|
||||
(map (fn [[_ id]] id))
|
||||
(mapcat get-frame-ids-cached))
|
||||
changes))))
|
||||
(into #{}
|
||||
(comp (mapcat extract-ids)
|
||||
(filter (fn [[page-id']] (= page-id page-id')))
|
||||
(map (fn [[_ id]] id))
|
||||
(mapcat get-frame-ids))
|
||||
changes)))
|
||||
|
||||
(defn watch-state-changes
|
||||
"Watch the state for changes inside frames. If a change is detected will force a rendering
|
||||
|
||||
@@ -433,18 +433,11 @@
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [data (dsh/lookup-file-data state)
|
||||
token-set (if set-id
|
||||
(lookup-token-set state set-id)
|
||||
(lookup-token-set state))
|
||||
token (-> (get-tokens-lib state)
|
||||
(ctob/get-token (ctob/get-id token-set) token-id))
|
||||
token-type (:type token)
|
||||
|
||||
changes (-> (pcb/empty-changes it)
|
||||
(pcb/with-library-data data)
|
||||
(pcb/set-token set-id token-id nil))]
|
||||
(rx/of (dch/commit-changes changes)
|
||||
(ptk/data-event ::ev/event {::ev/name "delete-token" :type token-type}))))))
|
||||
(rx/of (dch/commit-changes changes))))))
|
||||
|
||||
(defn bulk-delete-tokens
|
||||
[set-id token-ids]
|
||||
@@ -452,15 +445,9 @@
|
||||
(dm/assert! (every? uuid? token-ids))
|
||||
(ptk/reify ::bulk-delete-tokens
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [data (dsh/lookup-file-data state)
|
||||
changes (reduce (fn [changes token-id]
|
||||
(pcb/set-token changes set-id token-id nil))
|
||||
(-> (pcb/empty-changes it)
|
||||
(pcb/with-library-data data))
|
||||
token-ids)]
|
||||
(rx/of (dch/commit-changes changes)
|
||||
(ptk/data-event ::ev/event {::ev/name "delete-token-node"}))))))
|
||||
(watch [_ _ _]
|
||||
(apply rx/of
|
||||
(map #(delete-token set-id %) token-ids)))))
|
||||
|
||||
(defn duplicate-token
|
||||
[token-id]
|
||||
|
||||
@@ -111,7 +111,6 @@
|
||||
"Initializes the rasterizer."
|
||||
[]
|
||||
(let [iframe (dom/create-element "iframe")]
|
||||
(dom/set-attribute! iframe "id" "rasterizer")
|
||||
(dom/set-attribute! iframe "src" origin)
|
||||
(dom/set-attribute! iframe "hidden" true)
|
||||
(.addEventListener js/window "message" on-message)
|
||||
|
||||
@@ -674,7 +674,7 @@
|
||||
token-value (or (get token :resolved-value)
|
||||
(or (mf/ref-val last-value*)
|
||||
(fmt/format-number value)))
|
||||
token-value (if (and (some? id) (= name :opacity))
|
||||
token-value (if (= name :opacity)
|
||||
(* 100 token-value)
|
||||
token-value)]
|
||||
(mf/spread-props props
|
||||
|
||||
@@ -67,11 +67,6 @@
|
||||
:on-key-down handle-keydown
|
||||
:disabled disabled?})]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps default-checked)
|
||||
(fn []
|
||||
(reset! checked* default-checked)))
|
||||
|
||||
[:> :div props
|
||||
[:div {:id id
|
||||
:class (stl/css :switch-track)}
|
||||
|
||||
@@ -3,15 +3,17 @@
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.shape.radius :as ctsr]
|
||||
[app.common.types.token :as tk]
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[app.main.data.workspace.tokens.application :as dwta]
|
||||
[app.main.features :as features]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.numeric-input :as deprecated-input]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.numeric-input :refer [numeric-input*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.workspace.sidebar.options.menus.input-wrapper-tokens :refer [numeric-input-wrapper*]]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]
|
||||
@@ -44,6 +46,63 @@
|
||||
(identical? (get old-values :r4)
|
||||
(get new-values :r4)))))
|
||||
|
||||
(mf/defc numeric-input-wrapper*
|
||||
{::mf/private true}
|
||||
[{:keys [values name applied-tokens align on-detach radius] :rest props}]
|
||||
(let [tokens (mf/use-ctx muc/active-tokens-by-type)
|
||||
tokens (mf/with-memo [tokens name]
|
||||
(delay
|
||||
(-> (deref tokens)
|
||||
(select-keys (get tk/tokens-by-input name))
|
||||
(not-empty))))
|
||||
on-detach-attr
|
||||
(mf/use-fn
|
||||
(mf/deps on-detach name)
|
||||
#(on-detach % name))
|
||||
|
||||
r1-value (get applied-tokens :r1)
|
||||
all-token-equal? (and (seq applied-tokens) (all-equal? applied-tokens))
|
||||
all-values-equal? (all-equal? values)
|
||||
|
||||
applied-token (cond
|
||||
(not (seq applied-tokens))
|
||||
nil
|
||||
|
||||
(and (= radius :all) (or (not all-values-equal?) (not all-token-equal?)))
|
||||
:multiple
|
||||
|
||||
(and all-token-equal? all-values-equal? (= radius :all))
|
||||
r1-value
|
||||
|
||||
:else
|
||||
(get applied-tokens radius))
|
||||
|
||||
|
||||
placeholder (if (= radius :all)
|
||||
(cond
|
||||
(or (not all-values-equal?)
|
||||
(not all-token-equal?))
|
||||
(tr "settings.multiple")
|
||||
:else
|
||||
"--")
|
||||
|
||||
(cond
|
||||
(or (= :multiple (:applied-tokens values))
|
||||
(= :multiple (get values name)))
|
||||
(tr "settings.multiple")
|
||||
:else
|
||||
"--"))
|
||||
|
||||
|
||||
props (mf/spread-props props
|
||||
{:placeholder placeholder
|
||||
:applied-token applied-token
|
||||
:tokens (if (delay? tokens) @tokens tokens)
|
||||
:align align
|
||||
:on-detach on-detach-attr
|
||||
:value values})]
|
||||
[:> numeric-input* props]))
|
||||
|
||||
(mf/defc border-radius-menu*
|
||||
{::mf/wrap [#(mf/memo' % check-border-radius-menu-props)]}
|
||||
[{:keys [class ids values applied-tokens]}]
|
||||
@@ -51,7 +110,6 @@
|
||||
(features/use-feature "tokens/numeric-input")
|
||||
|
||||
all-values-equal? (all-equal? values)
|
||||
all-token-equal? (and (seq applied-tokens) (all-equal? applied-tokens))
|
||||
|
||||
radius-expanded* (mf/use-state false)
|
||||
radius-expanded (deref radius-expanded*)
|
||||
@@ -177,30 +235,18 @@
|
||||
:on-detach on-detach-all
|
||||
:icon i/corner-radius
|
||||
:min 0
|
||||
:attr :border-radius
|
||||
:name :border-radius
|
||||
:nillable true
|
||||
:property (tr "workspace.options.radius")
|
||||
:applied-token (cond
|
||||
(not (seq applied-tokens))
|
||||
nil
|
||||
|
||||
(or (not all-values-equal?) (not all-token-equal?))
|
||||
:multiple
|
||||
|
||||
:else
|
||||
(get applied-tokens :r1))
|
||||
:class (stl/css :radius-wrapper)
|
||||
:applied-tokens applied-tokens
|
||||
:radius :all
|
||||
:align :right
|
||||
:placeholder (cond
|
||||
(or (not all-values-equal?)
|
||||
(not all-token-equal?))
|
||||
(tr "settings.multiple")
|
||||
:else
|
||||
"--")
|
||||
:value (if all-values-equal?
|
||||
(if (nil? (:r1 values))
|
||||
0
|
||||
(:r1 values))
|
||||
nil)}]
|
||||
:values (if all-values-equal?
|
||||
(if (nil? (:r1 values))
|
||||
0
|
||||
(:r1 values))
|
||||
nil)}]
|
||||
|
||||
[:div {:class (stl/css :radius-1)
|
||||
:title (tr "workspace.options.radius")}
|
||||
@@ -230,75 +276,56 @@
|
||||
{:on-change on-radius-r1-change
|
||||
:on-detach on-detach-r1
|
||||
:min 0
|
||||
:attr :border-radius
|
||||
:name :border-radius
|
||||
:property (tr "workspace.options.radius-top-left")
|
||||
:applied-token (get applied-tokens :r1)
|
||||
:applied-tokens applied-tokens
|
||||
:radius :r1
|
||||
:align :right
|
||||
:placeholder (cond
|
||||
(or (= :multiple (get applied-tokens :r1))
|
||||
(= :multiple (get values :r1)))
|
||||
(tr "settings.multiple")
|
||||
:else
|
||||
"--")
|
||||
:class (stl/css :dropdown-offset)
|
||||
:class (stl/css :radius-wrapper :dropdown-offset)
|
||||
:inner-class (stl/css :no-icon-input)
|
||||
:value (:r1 values)}]
|
||||
:values (:r1 values)}]
|
||||
|
||||
[:> numeric-input-wrapper*
|
||||
{:on-change on-radius-r2-change
|
||||
:on-detach on-detach-r2
|
||||
:min 0
|
||||
:attr :border-radius
|
||||
:name :border-radius
|
||||
:nillable true
|
||||
:property (tr "workspace.options.radius-top-right")
|
||||
:applied-token (get applied-tokens :r2)
|
||||
:applied-tokens applied-tokens
|
||||
:align :right
|
||||
:class (stl/css :radius-wrapper)
|
||||
:inner-class (stl/css :no-icon-input)
|
||||
:placeholder (cond
|
||||
(or (= :multiple (get applied-tokens :r2))
|
||||
(= :multiple (get values :r2)))
|
||||
(tr "settings.multiple")
|
||||
:else
|
||||
"--")
|
||||
:value (:r2 values)}]
|
||||
:radius :r2
|
||||
:values (:r2 values)}]
|
||||
|
||||
[:> numeric-input-wrapper*
|
||||
{:on-change on-radius-r4-change
|
||||
:on-detach on-detach-r4
|
||||
:min 0
|
||||
:attr :border-radius
|
||||
:name :border-radius
|
||||
:nillable true
|
||||
:property (tr "workspace.options.radius-bottom-left")
|
||||
:applied-token (get applied-tokens :r4)
|
||||
:class (stl/css :dropdown-offset)
|
||||
:applied-tokens applied-tokens
|
||||
:class (stl/css :radius-wrapper :dropdown-offset)
|
||||
:inner-class (stl/css :no-icon-input)
|
||||
:placeholder (cond
|
||||
(or (= :multiple (get applied-tokens :r4))
|
||||
(= :multiple (get values :r4)))
|
||||
(tr "settings.multiple")
|
||||
:else
|
||||
"--")
|
||||
:radius :r4
|
||||
:align :right
|
||||
:value (:r4 values)}]
|
||||
:values (:r4 values)}]
|
||||
|
||||
[:> numeric-input-wrapper*
|
||||
{:on-change on-radius-r3-change
|
||||
:on-detach on-detach-r3
|
||||
:min 0
|
||||
:attr :border-radius
|
||||
:name :border-radius
|
||||
:nillable true
|
||||
:property (tr "workspace.options.radius-bottom-right")
|
||||
:applied-token (get applied-tokens :r3)
|
||||
:placeholder (cond
|
||||
(or (= :multiple (get applied-tokens :r3))
|
||||
(= :multiple (get values :r3)))
|
||||
(tr "settings.multiple")
|
||||
:else
|
||||
"--")
|
||||
:applied-tokens applied-tokens
|
||||
:radius :r3
|
||||
:align :right
|
||||
:class (stl/css :radius-wrapper)
|
||||
:inner-class (stl/css :no-icon-input)
|
||||
:value (:r3 values)}]]
|
||||
:values (:r3 values)}]]
|
||||
|
||||
[:div {:class (stl/css :radius-4)}
|
||||
[:div {:class (stl/css :small-input)}
|
||||
|
||||
@@ -40,6 +40,10 @@
|
||||
margin-inline: var(--sp-xs);
|
||||
}
|
||||
|
||||
.radius-wrapper {
|
||||
--dropdown-width: var(--7-columns-dropdown-width);
|
||||
}
|
||||
|
||||
.no-icon-input {
|
||||
padding-inline-start: px2rem(6);
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
(ns app.main.ui.workspace.sidebar.options.menus.input-wrapper-tokens
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.types.token :as tk]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.main.ui.ds.controls.numeric-input :refer [numeric-input*]]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(mf/defc numeric-input-wrapper*
|
||||
[{:keys [value attr applied-token align on-detach placeholder input-type class] :rest props}]
|
||||
(let [tokens (mf/use-ctx muc/active-tokens-by-type)
|
||||
|
||||
tokens (mf/with-memo [tokens input-type]
|
||||
(delay
|
||||
(-> (deref tokens)
|
||||
(select-keys (get tk/tokens-by-input (or input-type attr)))
|
||||
(not-empty))))
|
||||
|
||||
on-detach-attr
|
||||
(mf/use-fn
|
||||
(mf/deps on-detach attr)
|
||||
#(on-detach % attr))
|
||||
|
||||
props (mf/spread-props props
|
||||
{:placeholder (or placeholder
|
||||
(if (= :multiple value)
|
||||
(tr "settings.multiple")
|
||||
"--"))
|
||||
:class [class (stl/css :numeric-input-wrapper)]
|
||||
:applied-token applied-token
|
||||
:tokens (if (delay? tokens) @tokens tokens)
|
||||
:align align
|
||||
:on-detach on-detach-attr
|
||||
:name attr
|
||||
:value value})]
|
||||
[:> numeric-input* props]))
|
||||
@@ -1,9 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
.numeric-input-wrapper {
|
||||
--dropdown-width: var(--7-columns-dropdown-width);
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.token :as tk]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[app.main.data.workspace.tokens.application :as dwta]
|
||||
@@ -16,9 +17,10 @@
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.numeric-input :as deprecated-input]
|
||||
[app.main.ui.components.select :refer [select]]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.numeric-input :refer [numeric-input*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.workspace.sidebar.options.menus.input-wrapper-tokens :refer [numeric-input-wrapper*]]
|
||||
[app.render-wasm.api :as wasm.api]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.v2 :as mf]))
|
||||
@@ -60,6 +62,36 @@
|
||||
(identical? (get old-values :hidden)
|
||||
(get new-values :hidden)))))
|
||||
|
||||
(mf/defc numeric-input-wrapper*
|
||||
{::mf/private true}
|
||||
[{:keys [values name applied-tokens align on-detach] :rest props}]
|
||||
(let [tokens (mf/use-ctx muc/active-tokens-by-type)
|
||||
tokens (mf/with-memo [tokens name]
|
||||
(delay
|
||||
(-> (deref tokens)
|
||||
(select-keys (get tk/tokens-by-input name))
|
||||
(not-empty))))
|
||||
|
||||
on-detach-attr (mf/use-fn
|
||||
(mf/deps on-detach name)
|
||||
#(on-detach % name))
|
||||
|
||||
applied-token (get applied-tokens name)
|
||||
opacity-value (or (get values name) 1)
|
||||
|
||||
props (mf/spread-props props
|
||||
{:placeholder (if (or (= :multiple (:applied-tokens values))
|
||||
(= :multiple opacity-value))
|
||||
(tr "settings.multiple")
|
||||
"--")
|
||||
:applied-token applied-token
|
||||
:tokens (if (delay? tokens) @tokens tokens)
|
||||
:align align
|
||||
:on-detach on-detach-attr
|
||||
:name name
|
||||
:value (* 100 opacity-value)})]
|
||||
[:> numeric-input* props]))
|
||||
|
||||
(mf/defc layer-menu*
|
||||
{::mf/wrap [#(mf/memo' % check-layer-menu-props)]}
|
||||
[{:keys [ids values applied-tokens]}]
|
||||
@@ -218,24 +250,22 @@
|
||||
:on-pointer-enter-option handle-blend-mode-enter
|
||||
:on-pointer-leave-option handle-blend-mode-leave}]]
|
||||
|
||||
|
||||
|
||||
(if token-numeric-inputs
|
||||
|
||||
[:> numeric-input-wrapper*
|
||||
{:on-change on-opacity-change
|
||||
:on-detach on-detach-token
|
||||
:icon i/percentage
|
||||
:min 0
|
||||
:max 100
|
||||
:attr :opacity
|
||||
:name :opacity
|
||||
:property (tr "workspace.options.opacity")
|
||||
:applied-token (get applied-tokens :opacity)
|
||||
:placeholder (if (or (= :multiple (get applied-tokens :opacity))
|
||||
(= :multiple (or (get values name) 1)))
|
||||
(tr "settings.multiple")
|
||||
"--")
|
||||
:applied-tokens applied-tokens
|
||||
:align :right
|
||||
:class (stl/css :numeric-input-wrapper)
|
||||
:value (* 100
|
||||
(or (get values name) 1))}]
|
||||
:values values}]
|
||||
|
||||
[:div {:class (stl/css :input)
|
||||
:title (tr "workspace.options.opacity")}
|
||||
|
||||
@@ -47,5 +47,6 @@
|
||||
|
||||
.numeric-input-wrapper {
|
||||
grid-column: span 2;
|
||||
--dropdown-width: var(--7-columns-dropdown-width);
|
||||
--dropdown-offset: #{px2rem(-35)};
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.math :as mth]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.types.token :as tk]
|
||||
[app.config :as cf]
|
||||
[app.main.data.event :as-alias ev]
|
||||
[app.main.data.workspace :as udw]
|
||||
@@ -24,14 +25,15 @@
|
||||
[app.main.ui.components.numeric-input :as deprecated-input]
|
||||
[app.main.ui.components.select :refer [select]]
|
||||
[app.main.ui.components.title-bar :refer [title-bar*]]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.numeric-input :refer [numeric-input*]]
|
||||
[app.main.ui.ds.controls.radio-buttons :refer [radio-buttons*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.formats :as fmt]
|
||||
[app.main.ui.hooks :as h]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.main.ui.workspace.sidebar.options.menus.input-wrapper-tokens :refer [numeric-input-wrapper*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.keyboard :as kbd]
|
||||
@@ -46,6 +48,44 @@
|
||||
:column i/column
|
||||
:column-reverse i/column-reverse))
|
||||
|
||||
|
||||
(mf/defc numeric-input-wrapper*
|
||||
{::mf/private true}
|
||||
[{:keys [values name applied-tokens align on-detach] :rest props}]
|
||||
(let [tokens (mf/use-ctx muc/active-tokens-by-type)
|
||||
input-type (cond
|
||||
(some #{:p2 :p4} [name])
|
||||
:horizontal-padding
|
||||
|
||||
(some #{:p1 :p3} [name])
|
||||
:vertical-padding
|
||||
:else
|
||||
name)
|
||||
|
||||
tokens (mf/with-memo [tokens input-type]
|
||||
(delay
|
||||
(-> (deref tokens)
|
||||
(select-keys (get tk/tokens-by-input input-type))
|
||||
(not-empty))))
|
||||
on-detach-attr
|
||||
(mf/use-fn
|
||||
(mf/deps on-detach name)
|
||||
#(on-detach % name))
|
||||
|
||||
props (mf/spread-props props
|
||||
{:placeholder (if (or (= :multiple (:applied-tokens values))
|
||||
(= :multiple (get values name))
|
||||
(nil? (get values name)))
|
||||
(tr "settings.multiple")
|
||||
"--")
|
||||
:class (stl/css :numeric-input-layout)
|
||||
:applied-token (get applied-tokens name)
|
||||
:tokens tokens
|
||||
:align align
|
||||
:on-detach on-detach-attr
|
||||
:value (get values name)})]
|
||||
[:> numeric-input* props]))
|
||||
|
||||
;; FLEX COMPONENTS
|
||||
|
||||
(def layout-container-flex-attrs
|
||||
@@ -375,17 +415,11 @@
|
||||
:on-focus on-focus-p1
|
||||
:icon i/padding-top-bottom
|
||||
:min 0
|
||||
:attr :p1
|
||||
:input-type :vertical-padding
|
||||
:name :p1
|
||||
:property (tr "workspace.layout-grid.editor.padding.vertical")
|
||||
:nillable true
|
||||
:placeholder (if (or (= :multiple applied-to-p1)
|
||||
(= :multiple p1)
|
||||
(nil? p1))
|
||||
(tr "settings.multiple")
|
||||
"--")
|
||||
:applied-token applied-to-p1
|
||||
:value p1}]
|
||||
:applied-tokens {:p1 applied-to-p1}
|
||||
:values {:p1 p1}}]
|
||||
|
||||
[:div {:class (stl/css :padding-simple)
|
||||
:title (tr "workspace.layout-grid.editor.padding.vertical")}
|
||||
@@ -410,18 +444,12 @@
|
||||
:on-focus on-focus-p2
|
||||
:icon i/padding-left-right
|
||||
:min 0
|
||||
:attr :p2
|
||||
:input-type :horizontal-padding
|
||||
:name :p2
|
||||
:align :right
|
||||
:property (tr "workspace.layout-grid.editor.padding.horizontal")
|
||||
:nillable true
|
||||
:applied-token applied-to-p2
|
||||
:placeholder (if (or (= :multiple applied-to-p2)
|
||||
(= :multiple p2)
|
||||
(nil? p2))
|
||||
(tr "settings.multiple")
|
||||
"--")
|
||||
:value p2}]
|
||||
:applied-tokens {:p2 applied-to-p2}
|
||||
:values {:p2 p2}}]
|
||||
|
||||
[:div {:class (stl/css :padding-simple)
|
||||
:title (tr "workspace.layout-grid.editor.padding.horizontal")}
|
||||
@@ -448,11 +476,6 @@
|
||||
p3 (:p3 value)
|
||||
p4 (:p4 value)
|
||||
|
||||
applied-to-p1 (:p1 applied-tokens)
|
||||
applied-to-p2 (:p2 applied-tokens)
|
||||
applied-to-p3 (:p3 applied-tokens)
|
||||
applied-to-p4 (:p4 applied-tokens)
|
||||
|
||||
on-change'
|
||||
(mf/use-fn
|
||||
(mf/deps on-change ids)
|
||||
@@ -512,15 +535,10 @@
|
||||
:on-focus on-focus-p1
|
||||
:icon i/padding-top
|
||||
:min 0
|
||||
:attr :p1
|
||||
:input-type :vertical-padding
|
||||
:name :p1
|
||||
:property (tr "workspace.layout-grid.editor.padding.top")
|
||||
:placeholder (if (or (= :multiple applied-to-p1)
|
||||
(= :multiple p1))
|
||||
(tr "settings.multiple")
|
||||
"--")
|
||||
:applied-token applied-to-p1
|
||||
:value p1}]
|
||||
:applied-tokens applied-tokens
|
||||
:values value}]
|
||||
|
||||
[:div {:class (stl/css :padding-multiple)
|
||||
:title (tr "workspace.layout-grid.editor.padding.top")}
|
||||
@@ -545,16 +563,11 @@
|
||||
:on-focus on-focus-p2
|
||||
:icon i/padding-right
|
||||
:min 0
|
||||
:attr :p2
|
||||
:input-type :horizontal-padding
|
||||
:name :p2
|
||||
:align :right
|
||||
:property (tr "workspace.layout-grid.editor.padding.right")
|
||||
:placeholder (if (or (= :multiple applied-to-p2)
|
||||
(= :multiple p2))
|
||||
(tr "settings.multiple")
|
||||
"--")
|
||||
:applied-token applied-to-p2
|
||||
:value p2}]
|
||||
:applied-tokens applied-tokens
|
||||
:values value}]
|
||||
|
||||
[:div {:class (stl/css :padding-multiple)
|
||||
:title (tr "workspace.layout-grid.editor.padding.right")}
|
||||
@@ -579,15 +592,10 @@
|
||||
:on-focus on-focus-p3
|
||||
:icon i/padding-bottom
|
||||
:min 0
|
||||
:attr :p3
|
||||
:input-type :vertical-padding
|
||||
:name :p3
|
||||
:property (tr "workspace.layout-grid.editor.padding.bottom")
|
||||
:placeholder (if (or (= :multiple applied-to-p3)
|
||||
(= :multiple p3))
|
||||
(tr "settings.multiple")
|
||||
"--")
|
||||
:applied-token applied-to-p3
|
||||
:value p3}]
|
||||
:applied-tokens applied-tokens
|
||||
:values value}]
|
||||
|
||||
[:div {:class (stl/css :padding-multiple)
|
||||
:title (tr "workspace.layout-grid.editor.padding.bottom")}
|
||||
@@ -613,15 +621,10 @@
|
||||
:icon i/padding-left
|
||||
:min 0
|
||||
:align :right
|
||||
:attr :p4
|
||||
:input-type :horizontal-padding
|
||||
:name :p4
|
||||
:property (tr "workspace.layout-grid.editor.padding.left")
|
||||
:placeholder (if (or (= :multiple applied-to-p4)
|
||||
(= :multiple p4))
|
||||
(tr "settings.multiple")
|
||||
"--")
|
||||
:applied-token applied-to-p4
|
||||
:value p4}]
|
||||
:applied-tokens applied-tokens
|
||||
:values value}]
|
||||
|
||||
[:div {:class (stl/css :padding-multiple)
|
||||
:title (tr "workspace.layout-grid.editor.padding.left")}
|
||||
@@ -754,16 +757,11 @@
|
||||
:icon i/gap-vertical
|
||||
:nillable true
|
||||
:min 0
|
||||
:attr :row-gap
|
||||
:name :row-gap
|
||||
:applied-tokens applied-tokens
|
||||
:property "Row gap"
|
||||
:values {:row-gap (:row-gap value)}
|
||||
:disabled row-gap-disabled?
|
||||
:placeholder (if (or (= :multiple (:row-gap applied-tokens))
|
||||
(= :multiple (:row-gap value)))
|
||||
(tr "settings.multiple")
|
||||
"--")
|
||||
:applied-token (:row-gap applied-tokens)
|
||||
:value (:row-gap value)}]
|
||||
:disabled row-gap-disabled?}]
|
||||
|
||||
[:div {:class (stl/css-case
|
||||
:row-gap true
|
||||
@@ -793,15 +791,11 @@
|
||||
:icon i/gap-horizontal
|
||||
:nillable true
|
||||
:min 0
|
||||
:attr :column-gap
|
||||
:name :column-gap
|
||||
:align :right
|
||||
:applied-tokens applied-tokens
|
||||
:property "Column gap"
|
||||
:placeholder (if (or (= :multiple (:column-gap applied-tokens))
|
||||
(= :multiple (:column-gap value)))
|
||||
(tr "settings.multiple")
|
||||
"--")
|
||||
:applied-token (:column-gap applied-tokens)
|
||||
:value (:column-gap value)
|
||||
:values {:column-gap (:column-gap value)}
|
||||
:disabled col-gap-disabled?}]
|
||||
|
||||
[:div {:class (stl/css-case
|
||||
|
||||
@@ -357,3 +357,7 @@
|
||||
grid-column: 1 / -1;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.numeric-input-layout {
|
||||
--dropdown-width: var(--7-columns-dropdown-width);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
[app.common.data :as d]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.types.token :as tk]
|
||||
[app.main.data.workspace :as udw]
|
||||
[app.main.data.workspace.shape-layout :as dwsl]
|
||||
[app.main.data.workspace.tokens.application :as dwta]
|
||||
@@ -18,16 +19,62 @@
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.numeric-input :as deprecated-input]
|
||||
[app.main.ui.components.title-bar :refer [title-bar*]]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.numeric-input :refer [numeric-input*]]
|
||||
[app.main.ui.ds.controls.radio-buttons :refer [radio-buttons*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.main.ui.workspace.sidebar.options.menus.input-wrapper-tokens :refer [numeric-input-wrapper*]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.layout-container :refer [get-layout-flex-icon]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(mf/defc numeric-input-wrapper*
|
||||
{::mf/private true}
|
||||
[{:keys [values name applied-tokens align on-detach placeholder] :rest props}]
|
||||
(let [tokens (mf/use-ctx muc/active-tokens-by-type)
|
||||
input-type (cond
|
||||
(some #{:m2 :m4} [name])
|
||||
:horizontal-margin
|
||||
|
||||
(some #{:m1 :m3} [name])
|
||||
:vertical-margin
|
||||
|
||||
(= name :layout-item-max-w)
|
||||
:max-width
|
||||
|
||||
(= name :layout-item-max-h)
|
||||
:max-height
|
||||
|
||||
(= name :layout-item-min-w)
|
||||
:min-width
|
||||
|
||||
(= name :layout-item-min-h)
|
||||
:min-height
|
||||
|
||||
:else
|
||||
name)
|
||||
|
||||
tokens (mf/with-memo [tokens input-type]
|
||||
(delay
|
||||
(-> (deref tokens)
|
||||
(select-keys (get tk/tokens-by-input input-type))
|
||||
(not-empty))))
|
||||
on-detach-attr
|
||||
(mf/use-fn
|
||||
(mf/deps on-detach name)
|
||||
#(on-detach % name))
|
||||
|
||||
props (mf/spread-props props
|
||||
{:placeholder (or placeholder "--")
|
||||
:applied-token (get applied-tokens name)
|
||||
:tokens tokens
|
||||
:align align
|
||||
:on-detach on-detach-attr
|
||||
:value (get values name)})]
|
||||
[:> numeric-input* props]))
|
||||
|
||||
(def layout-item-attrs
|
||||
[:layout-item-margin ;; {:m1 0 :m2 0 :m3 0 :m4 0}
|
||||
:layout-item-margin-type ;; :simple :multiple
|
||||
@@ -151,12 +198,11 @@
|
||||
:placeholder m1-placeholder
|
||||
:icon i/margin-top-bottom
|
||||
:min 0
|
||||
:attr :m1
|
||||
:input-type :vertical-margin
|
||||
:name :m1
|
||||
:property "Vertical margin "
|
||||
:nillable true
|
||||
:applied-token token-applied-m1
|
||||
:value m1}]
|
||||
:applied-tokens {:m1 token-applied-m1}
|
||||
:values {:m1 m1}}]
|
||||
|
||||
[:div {:class (stl/css :vertical-margin)
|
||||
:title "Vertical margin"}
|
||||
@@ -181,13 +227,12 @@
|
||||
:icon i/margin-left-right
|
||||
:class (stl/css :horizontal-margin-wrapper)
|
||||
:min 0
|
||||
:attr :m2
|
||||
:name :m2
|
||||
:align :right
|
||||
:input-type :horizontal-margin
|
||||
:property "Horizontal margin"
|
||||
:nillable true
|
||||
:applied-token token-applied-m2
|
||||
:value m2}]
|
||||
:applied-tokens {:m2 token-applied-m2}
|
||||
:values {:m2 m2}}]
|
||||
|
||||
[:div {:class (stl/css :horizontal-margin)
|
||||
:title "Horizontal margin"}
|
||||
@@ -279,12 +324,11 @@
|
||||
:icon i/margin-top
|
||||
:class (stl/css :top-margin-wrapper)
|
||||
:min 0
|
||||
:attr :m1
|
||||
:input-type :vertical-margin
|
||||
:name :m1
|
||||
:property "Top margin"
|
||||
:nillable true
|
||||
:applied-token applied-token-to-m1
|
||||
:value m1}]
|
||||
:applied-tokens {:m1 applied-token-to-m1}
|
||||
:values {:m1 m1}}]
|
||||
|
||||
[:div {:class (stl/css :top-margin)
|
||||
:title "Top margin"}
|
||||
@@ -307,13 +351,12 @@
|
||||
:icon i/margin-right
|
||||
:class (stl/css :right-margin-wrapper)
|
||||
:min 0
|
||||
:attr :m2
|
||||
:name :m2
|
||||
:align :right
|
||||
:input-type :horizontal-margin
|
||||
:property "Right margin"
|
||||
:nillable true
|
||||
:applied-token applied-token-to-m2
|
||||
:value m2}]
|
||||
:applied-tokens {:m2 applied-token-to-m2}
|
||||
:values {:m2 m2}}]
|
||||
|
||||
[:div {:class (stl/css :right-margin)
|
||||
:title "Right margin"}
|
||||
@@ -337,13 +380,12 @@
|
||||
:icon i/margin-bottom
|
||||
:class (stl/css :bottom-margin-wrapper)
|
||||
:min 0
|
||||
:attr :m3
|
||||
:name :m3
|
||||
:align :right
|
||||
:input-type :vertical-margin
|
||||
:property "Bottom margin"
|
||||
:nillable true
|
||||
:applied-token applied-token-to-m3
|
||||
:value m3}]
|
||||
:applied-tokens {:m3 applied-token-to-m3}
|
||||
:values {:m3 m3}}]
|
||||
|
||||
[:div {:class (stl/css :bottom-margin)
|
||||
:title "Bottom margin"}
|
||||
@@ -367,12 +409,11 @@
|
||||
:icon i/margin-left
|
||||
:class (stl/css :left-margin-wrapper)
|
||||
:min 0
|
||||
:attr :m4
|
||||
:name :m4
|
||||
:property "Left margin"
|
||||
:input-type :horizontal-margin
|
||||
:nillable true
|
||||
:applied-token applied-token-to-m4
|
||||
:value m4}]
|
||||
:applied-tokens {:m4 applied-token-to-m4}
|
||||
:values {:m4 m4}}]
|
||||
|
||||
[:div {:class (stl/css :left-margin)
|
||||
:title "Left margin"}
|
||||
@@ -520,7 +561,7 @@
|
||||
(def ^:private schema:layout-size-constraints
|
||||
[:map
|
||||
[:values schema:layout-item-props-schema]
|
||||
[:applied-tokens [:maybe [:map-of :keyword :string]]]
|
||||
[:applied-tokens [:map-of :keyword :string]]
|
||||
[:ids [::sm/vec ::sm/uuid]]
|
||||
[:v-sizing {:optional true} [:maybe [:= :fill]]]])
|
||||
|
||||
@@ -586,15 +627,15 @@
|
||||
[:> numeric-input-wrapper*
|
||||
{:on-change on-layout-item-min-w-change
|
||||
:on-detach on-detach-token
|
||||
:class (stl/css :min-w-wrapper)
|
||||
:min 0
|
||||
:attr :layout-item-min-w
|
||||
:name :layout-item-min-w
|
||||
:property (tr "workspace.options.layout-item.layout-item-min-w")
|
||||
:text-icon "MIN W"
|
||||
:input-type :min-width
|
||||
:nillable true
|
||||
:applied-token applied-token-to-min-w
|
||||
:applied-tokens {:layout-item-min-w applied-token-to-min-w}
|
||||
:tooltip-class (stl/css :tooltip-wrapper)
|
||||
:value min-w}]
|
||||
:values {:layout-item-min-w min-w}}]
|
||||
|
||||
[:div {:class (stl/css :layout-item-min-w)
|
||||
:title (tr "workspace.options.layout-item.layout-item-min-w")}
|
||||
@@ -617,15 +658,15 @@
|
||||
{:on-change on-layout-item-max-w-change
|
||||
:on-detach on-detach-token
|
||||
:text-icon "MAX W"
|
||||
:class (stl/css :max-w-wrapper)
|
||||
:min 0
|
||||
:name :layout-item-max-w
|
||||
:align :right
|
||||
:input-type :max-width
|
||||
:attr :layout-item-max-w
|
||||
:property (tr "workspace.options.layout-item.layout-item-max-w")
|
||||
:nillable true
|
||||
:tooltip-class (stl/css :tooltip-wrapper)
|
||||
:applied-token applied-token-to-max-w
|
||||
:value max-w}]
|
||||
:applied-tokens {:layout-item-max-w applied-token-to-max-w}
|
||||
:values {:layout-item-max-w max-w}}]
|
||||
|
||||
[:div {:class (stl/css :layout-item-max-w)
|
||||
:title (tr "workspace.options.layout-item.layout-item-max-w")}
|
||||
@@ -649,15 +690,14 @@
|
||||
{:on-change on-layout-item-min-h-change
|
||||
:on-detach on-detach-token
|
||||
:text-icon "MIN H"
|
||||
:input-type :max-height
|
||||
:class (stl/css :min-h-wrapper)
|
||||
:min 0
|
||||
:attr :layout-item-min-h
|
||||
:name :layout-item-min-h
|
||||
:property (tr "workspace.options.layout-item.layout-item-min-h")
|
||||
:nillable true
|
||||
:tooltip-class (stl/css :tooltip-wrapper)
|
||||
|
||||
:applied-token applied-token-to-min-h
|
||||
:value min-h}]
|
||||
:applied-tokens {:layout-item-min-h applied-token-to-min-h}
|
||||
:values {:layout-item-min-h min-h}}]
|
||||
|
||||
[:div {:class (stl/css :layout-item-min-h)
|
||||
:title (tr "workspace.options.layout-item.layout-item-min-h")}
|
||||
@@ -678,16 +718,16 @@
|
||||
[:> numeric-input-wrapper*
|
||||
{:on-change on-layout-item-max-h-change
|
||||
:on-detach on-detach-token
|
||||
:class (stl/css :max-h-wrapper)
|
||||
:min 0
|
||||
:text-icon "MAX H"
|
||||
:name :layout-item-max-h
|
||||
:align :right
|
||||
:input-type :max-height
|
||||
:attr :layout-item-max-h
|
||||
:property (tr "workspace.options.layout-item.layout-item-max-h")
|
||||
:nillable true
|
||||
:tooltip-class (stl/css :tooltip-wrapper)
|
||||
:applied-token applied-token-to-max-h
|
||||
:value max-h}]
|
||||
:applied-tokens {:layout-item-max-h applied-token-to-max-h}
|
||||
:values {:layout-item-max-h max-h}}]
|
||||
|
||||
[:div {:class (stl/css :layout-item-max-h)
|
||||
:title (tr "workspace.options.layout-item.layout-item-max-h")}
|
||||
|
||||
@@ -91,10 +91,12 @@
|
||||
|
||||
.vertical-margin-wrapper {
|
||||
grid-column: 1;
|
||||
--dropdown-width: var(--7-columns-dropdown-width);
|
||||
}
|
||||
|
||||
.horizontal-margin-wrapper {
|
||||
grid-column: 2;
|
||||
--dropdown-width: var(--7-columns-dropdown-width);
|
||||
}
|
||||
|
||||
.margin-multiple {
|
||||
@@ -113,28 +115,39 @@
|
||||
|
||||
.top-margin,
|
||||
.top-margin-wrapper {
|
||||
--dropdown-width: var(--7-columns-dropdown-width);
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.bottom-margin,
|
||||
.bottom-margin-wrapper {
|
||||
--dropdown-width: var(--7-columns-dropdown-width);
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.left-margin,
|
||||
.left-margin-wrapper {
|
||||
--dropdown-width: var(--7-columns-dropdown-width);
|
||||
grid-column: 1;
|
||||
grid-row: 2;
|
||||
}
|
||||
|
||||
.right-margin,
|
||||
.right-margin-wrapper {
|
||||
--dropdown-width: var(--7-columns-dropdown-width);
|
||||
grid-column: 2;
|
||||
grid-row: 2;
|
||||
}
|
||||
|
||||
.min-w-wrapper,
|
||||
.max-w-wrapper,
|
||||
.min-h-wrapper,
|
||||
.max-h-wrapper {
|
||||
--dropdown-width: var(--7-columns-dropdown-width);
|
||||
}
|
||||
|
||||
.advanced-options {
|
||||
display: grid;
|
||||
grid-template-columns:
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.logic.shapes :as cls]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.types.token :as tk]
|
||||
[app.main.constants :refer [size-presets]]
|
||||
[app.main.data.workspace :as udw]
|
||||
[app.main.data.workspace.interactions :as dwi]
|
||||
@@ -25,12 +26,13 @@
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.dropdown :refer [dropdown]]
|
||||
[app.main.ui.components.numeric-input :as deprecated-input]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.numeric-input :refer [numeric-input*]]
|
||||
[app.main.ui.ds.controls.radio-buttons :refer [radio-buttons*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.icons :as deprecated-icon]
|
||||
[app.main.ui.workspace.sidebar.options.menus.border-radius :refer [border-radius-menu*]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.input-wrapper-tokens :refer [numeric-input-wrapper*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[clojure.set :as set]
|
||||
@@ -89,6 +91,32 @@
|
||||
shape)]
|
||||
(select-keys shape measure-attrs)))
|
||||
|
||||
(mf/defc numeric-input-wrapper*
|
||||
{::mf/private true}
|
||||
[{:keys [values name applied-tokens align on-detach] :rest props}]
|
||||
(let [tokens (mf/use-ctx muc/active-tokens-by-type)
|
||||
tokens (mf/with-memo [tokens name]
|
||||
(delay
|
||||
(-> (deref tokens)
|
||||
(select-keys (get tk/tokens-by-input name))
|
||||
(not-empty))))
|
||||
on-detach-attr
|
||||
(mf/use-fn
|
||||
(mf/deps on-detach name)
|
||||
#(on-detach % name))
|
||||
|
||||
props (mf/spread-props props
|
||||
{:placeholder (if (or (= :multiple (:applied-tokens values))
|
||||
(= :multiple (get values name)))
|
||||
(tr "settings.multiple") "--")
|
||||
:class (stl/css :numeric-input-measures)
|
||||
:applied-token (get applied-tokens name)
|
||||
:tokens (if (delay? tokens) @tokens tokens)
|
||||
:align align
|
||||
:on-detach on-detach-attr
|
||||
:value (get values name)})]
|
||||
[:> numeric-input* props]))
|
||||
|
||||
(def ^:private xf:map-type (map :type))
|
||||
(def ^:private xf:mapcat-type-to-options (mapcat type->options))
|
||||
|
||||
@@ -416,13 +444,10 @@
|
||||
:on-detach on-detach-token
|
||||
:icon i/character-w
|
||||
:min 0.01
|
||||
:attr :width
|
||||
:name :width
|
||||
:property (tr "workspace.options.width")
|
||||
:applied-token (get applied-tokens :width)
|
||||
:placeholder (if (or (= :multiple (get applied-tokens :width))
|
||||
(= :multiple (get values :width)))
|
||||
(tr "settings.multiple") "--")
|
||||
:value (get values :width)}]
|
||||
:applied-tokens applied-tokens
|
||||
:values values}]
|
||||
|
||||
[:> numeric-input-wrapper*
|
||||
{:disabled disabled-height-sizing?
|
||||
@@ -430,11 +455,11 @@
|
||||
:on-detach on-detach-token
|
||||
:min 0.01
|
||||
:icon i/character-h
|
||||
:attr :height
|
||||
:name :height
|
||||
:align :right
|
||||
:property (tr "workspace.options.height")
|
||||
:applied-token (get applied-tokens :height)
|
||||
:value (get values :height)}]]
|
||||
:applied-tokens applied-tokens
|
||||
:values values}]]
|
||||
|
||||
[:*
|
||||
[:div {:class (stl/css-case :width true
|
||||
@@ -478,26 +503,20 @@
|
||||
:on-change on-pos-x-change
|
||||
:on-detach on-detach-token
|
||||
:icon i/character-x
|
||||
:attr :x
|
||||
:name :x
|
||||
:property (tr "workspace.options.x")
|
||||
:applied-token (get applied-tokens :x)
|
||||
:placeholder (if (or (= :multiple (get applied-tokens :x))
|
||||
(= :multiple (get values :x)))
|
||||
(tr "settings.multiple") "--")
|
||||
:value (get values :x)}]
|
||||
:applied-tokens applied-tokens
|
||||
:values values}]
|
||||
[:> numeric-input-wrapper*
|
||||
{:disabled disabled-position?
|
||||
:on-change on-pos-y-change
|
||||
:on-detach on-detach-token
|
||||
:icon i/character-y
|
||||
:attr :y
|
||||
:name :y
|
||||
:align :right
|
||||
:property (tr "workspace.options.y")
|
||||
:applied-token (get applied-tokens :y)
|
||||
:placeholder (if (or (= :multiple (get applied-tokens :y))
|
||||
(= :multiple (get values :y)))
|
||||
(tr "settings.multiple") "--")
|
||||
:value (get values :y)}]]
|
||||
:applied-tokens applied-tokens
|
||||
:values values}]]
|
||||
|
||||
[:*
|
||||
[:div {:class (stl/css-case :x-position true
|
||||
@@ -532,13 +551,10 @@
|
||||
:icon i/rotation
|
||||
:min -359
|
||||
:max 359
|
||||
:attr :rotation
|
||||
:name :rotation
|
||||
:property (tr "workspace.options.rotation")
|
||||
:applied-token (get applied-tokens :rotation)
|
||||
:placeholder (if (or (= :multiple (get applied-tokens :rotation))
|
||||
(= :multiple (get values :rotation)))
|
||||
(tr "settings.multiple") "--")
|
||||
:value (get values :rotation)}]
|
||||
:applied-tokens applied-tokens
|
||||
:values values}]
|
||||
|
||||
[:div {:class (stl/css :rotation)
|
||||
:title (tr "workspace.options.rotation")}
|
||||
|
||||
@@ -156,3 +156,7 @@
|
||||
justify-content: flex-start;
|
||||
gap: deprecated.$s-4;
|
||||
}
|
||||
|
||||
.numeric-input-measures {
|
||||
--dropdown-width: var(--7-columns-dropdown-width);
|
||||
}
|
||||
|
||||
@@ -309,7 +309,7 @@
|
||||
|
||||
on-remove'
|
||||
(mf/use-fn
|
||||
(mf/deps index)
|
||||
(mf/deps index on-remove)
|
||||
(fn [_]
|
||||
(when on-remove
|
||||
(on-remove index))))
|
||||
|
||||
@@ -9,20 +9,50 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.types.color :as ctc]
|
||||
[app.common.types.token :as tk]
|
||||
[app.main.data.workspace.tokens.application :as dwta]
|
||||
[app.main.features :as features]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.numeric-input :as deprecated-input]
|
||||
[app.main.ui.components.reorder-handler :refer [reorder-handler*]]
|
||||
[app.main.ui.components.select :refer [select]]
|
||||
[app.main.ui.context :as muc]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.numeric-input :refer [numeric-input*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
|
||||
[app.main.ui.hooks :as h]
|
||||
[app.main.ui.workspace.sidebar.options.menus.input-wrapper-tokens :refer [numeric-input-wrapper*]]
|
||||
[app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row*]]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(mf/defc numeric-input-wrapper*
|
||||
{::mf/private true}
|
||||
[{:keys [values name applied-tokens align on-detach] :rest props}]
|
||||
(let [tokens (mf/use-ctx muc/active-tokens-by-type)
|
||||
tokens (mf/with-memo [tokens name]
|
||||
(delay
|
||||
(-> (deref tokens)
|
||||
(select-keys (get tk/tokens-by-input name))
|
||||
(not-empty))))
|
||||
|
||||
on-detach-attr (mf/use-fn
|
||||
(mf/deps on-detach name)
|
||||
#(on-detach % name))
|
||||
|
||||
applied-token (get applied-tokens name)
|
||||
|
||||
props (mf/spread-props props
|
||||
{:placeholder (if (= :multiple values)
|
||||
(tr "settings.multiple")
|
||||
"--")
|
||||
:applied-token applied-token
|
||||
:tokens (if (delay? tokens) @tokens tokens)
|
||||
:align align
|
||||
:on-detach on-detach-attr
|
||||
:name name
|
||||
:value values})]
|
||||
[:> numeric-input* props]))
|
||||
|
||||
(mf/defc stroke-row*
|
||||
[{:keys [index
|
||||
stroke
|
||||
@@ -220,11 +250,11 @@
|
||||
:min 0
|
||||
:on-focus on-focus
|
||||
:on-blur on-blur
|
||||
:attr :stroke-width
|
||||
:name :stroke-width
|
||||
:class (stl/css :numeric-input-wrapper)
|
||||
:property (tr "workspace.options.stroke-width")
|
||||
:applied-token (get applied-tokens :stroke-width)
|
||||
:value stroke-width}]
|
||||
:applied-tokens applied-tokens
|
||||
:values stroke-width}]
|
||||
|
||||
[:div {:class (stl/css :stroke-width-input)
|
||||
:title (tr "workspace.options.stroke-width")}
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
|
||||
.numeric-input-wrapper {
|
||||
grid-column: span 2;
|
||||
--dropdown-width: var(--7-columns-dropdown-width);
|
||||
}
|
||||
|
||||
.stroke-alignment-select {
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
.modal-title {
|
||||
@include t.use-typography("headline-medium");
|
||||
color: var(--modal-title-foreground-color);
|
||||
word-break: break-word;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
|
||||
@@ -169,81 +169,3 @@
|
||||
(let [end (timestamp)]
|
||||
(println (str "[" event "]" (- end start)))))
|
||||
#js {"priority" "user-blocking"})))))
|
||||
|
||||
;; --- DEVTOOLS PERF LOGGING
|
||||
|
||||
(defonce ^:private longtask-observer* (atom nil))
|
||||
(defonce ^:private stall-timer* (atom nil))
|
||||
(defonce ^:private current-op* (atom nil))
|
||||
|
||||
(defn- install-long-task-observer
|
||||
[]
|
||||
(when (and (some? (.-PerformanceObserver js/window)) (nil? @longtask-observer*))
|
||||
(let [observer (js/PerformanceObserver.
|
||||
(fn [list _]
|
||||
(doseq [entry (.getEntries list)]
|
||||
(let [dur (.-duration entry)
|
||||
start (.-startTime entry)
|
||||
attrib (.-attribution entry)
|
||||
attrib-count (when attrib (.-length attrib))
|
||||
first-attrib (when (and attrib-count (> attrib-count 0)) (aget attrib 0))
|
||||
attrib-name (when first-attrib (.-name first-attrib))
|
||||
attrib-ctype (when first-attrib (.-containerType first-attrib))
|
||||
attrib-cid (when first-attrib (.-containerId first-attrib))
|
||||
attrib-csrc (when first-attrib (.-containerSrc first-attrib))]
|
||||
|
||||
(.warn js/console (str "[perf] long task " (Math/round dur) "ms at " (Math/round start) "ms"
|
||||
(when first-attrib
|
||||
(str " attrib:name=" attrib-name
|
||||
" ctype=" attrib-ctype
|
||||
" cid=" attrib-cid
|
||||
" csrc=" attrib-csrc))))))))]
|
||||
(.observe observer #js{:entryTypes #js["longtask"]})
|
||||
(reset! longtask-observer* observer))))
|
||||
|
||||
(defn- start-event-loop-stall-logger
|
||||
"Log event loop stalls by measuring setInterval drift.
|
||||
|
||||
Params:
|
||||
- interval-ms: base interval
|
||||
- threshold-ms: drift over which we report
|
||||
"
|
||||
[interval-ms threshold-ms]
|
||||
(when (nil? @stall-timer*)
|
||||
(let [last (atom (.now js/performance))
|
||||
id (js/setInterval
|
||||
(fn []
|
||||
(let [now (.now js/performance)
|
||||
expected (+ @last interval-ms)
|
||||
drift (- now expected)
|
||||
current-op @current-op*
|
||||
measures (.getEntriesByType js/performance "measure")
|
||||
mlen (.-length measures)
|
||||
last-measure (when (> mlen 0) (aget measures (dec mlen)))
|
||||
meas-name (when last-measure (.-name last-measure))
|
||||
meas-detail (when last-measure (.-detail last-measure))
|
||||
meas-count (when meas-detail (unchecked-get meas-detail "count"))]
|
||||
(reset! last now)
|
||||
(when (> drift threshold-ms)
|
||||
(.warn js/console
|
||||
(str "[perf] event loop stall: " (Math/round drift) "ms"
|
||||
(when current-op (str " op=" current-op))
|
||||
(when meas-name (str " last=" meas-name))
|
||||
(when meas-count (str " count=" meas-count)))))))
|
||||
interval-ms)]
|
||||
(reset! stall-timer* id))))
|
||||
|
||||
(defn setup
|
||||
"Install perf observers in dev builds. Safe to call multiple times.
|
||||
Perf logs are disabled by default. Enable them with the :perf-logs
|
||||
flag in config."
|
||||
[]
|
||||
(install-long-task-observer)
|
||||
(start-event-loop-stall-logger 50 100)
|
||||
;; Expose simple API on window for manual control in devtools
|
||||
(let [api #js {:reset (fn []
|
||||
(try
|
||||
(.clearMarks js/performance)
|
||||
(.clearMeasures js/performance)
|
||||
(catch :default _ nil)))}]
|
||||
(unchecked-set js/window "PenpotPerf" api)))
|
||||
|
||||
Reference in New Issue
Block a user