mirror of
https://github.com/penpot/penpot.git
synced 2026-09-18 18:06:14 +00:00
Merge remote-tracking branch 'origin/staging' into develop
This commit is contained in:
commit
56b28b5440
@ -1191,9 +1191,9 @@
|
|||||||
; Check if the shape has changed any
|
; Check if the shape has changed any
|
||||||
; attribute that participates in components synchronization.
|
; attribute that participates in components synchronization.
|
||||||
(and (= (:type operation) :set)
|
(and (= (:type operation) :set)
|
||||||
(get ctk/sync-attrs (:attr operation))))
|
(contains? ctk/sync-attrs (:attr operation))))]
|
||||||
any-sync? (some need-sync? operations)]
|
|
||||||
(when any-sync?
|
(when (some need-sync? operations)
|
||||||
(parents-frames id (:objects page))))))
|
(parents-frames id (:objects page))))))
|
||||||
|
|
||||||
(defmethod frames-changed :mov-objects
|
(defmethod frames-changed :mov-objects
|
||||||
|
|||||||
@ -48,11 +48,12 @@
|
|||||||
(def log-shape-ids #{})
|
(def log-shape-ids #{})
|
||||||
(def log-container-ids #{})
|
(def log-container-ids #{})
|
||||||
|
|
||||||
(def updatable-attrs (->> (seq (keys ctk/sync-attrs))
|
(def updatable-attrs
|
||||||
;; We don't update the flex-child attrs
|
(->> (keys ctk/sync-attrs)
|
||||||
(remove ctk/swap-keep-attrs)
|
;; We don't update the flex-child attrs
|
||||||
;; We don't do automatic update of the `layout-grid-cells` property.
|
(remove ctk/swap-keep-attrs)
|
||||||
(remove #(= :layout-grid-cells %))))
|
;; We don't do automatic update of the `layout-grid-cells` property.
|
||||||
|
(remove #(= :layout-grid-cells %))))
|
||||||
|
|
||||||
(defn enabled-shape?
|
(defn enabled-shape?
|
||||||
[id container]
|
[id container]
|
||||||
@ -1646,19 +1647,22 @@
|
|||||||
(defn- generate-update-tokens
|
(defn- generate-update-tokens
|
||||||
[changes container dest-shape origin-shape touched omit-touched? valid-attrs]
|
[changes container dest-shape origin-shape touched omit-touched? valid-attrs]
|
||||||
;; valid-attrs is a set of attrs to consider on the update. If it is nil, it will consider all the attrs
|
;; valid-attrs is a set of attrs to consider on the update. If it is nil, it will consider all the attrs
|
||||||
(let [attrs (->> (seq (keys ctk/sync-attrs))
|
(let [attrs
|
||||||
;; We don't update the flex-child attrs
|
(->> (keys ctk/sync-attrs)
|
||||||
(remove #(= :layout-grid-cells %)))
|
;; We don't update the flex-child attrs
|
||||||
|
(remove #(= :layout-grid-cells %)))
|
||||||
|
|
||||||
applied-tokens (reduce (fn [applied-tokens attr]
|
applied-tokens
|
||||||
(let [attr-group (get ctk/sync-attrs attr)
|
(reduce (fn [applied-tokens attr]
|
||||||
token-attrs (cto/shape-attr->token-attrs attr)]
|
(let [sync-group (or (ctk/resolve-sync-group (:type origin-shape) attr)
|
||||||
(if (and (or (not omit-touched?) (not (touched attr-group)))
|
(ctk/resolve-sync-group (:type dest-shape) attr))
|
||||||
(or (empty? valid-attrs) (contains? valid-attrs attr)))
|
token-attrs (cto/shape-attr->token-attrs attr)]
|
||||||
(into applied-tokens token-attrs)
|
(if (and (or (not omit-touched?) (not (touched sync-group)))
|
||||||
applied-tokens)))
|
(or (empty? valid-attrs) (contains? valid-attrs attr)))
|
||||||
#{}
|
(into applied-tokens token-attrs)
|
||||||
attrs)]
|
applied-tokens)))
|
||||||
|
#{}
|
||||||
|
attrs)]
|
||||||
(cond-> changes
|
(cond-> changes
|
||||||
(seq applied-tokens)
|
(seq applied-tokens)
|
||||||
(update-tokens container dest-shape origin-shape applied-tokens))))
|
(update-tokens container dest-shape origin-shape applied-tokens))))
|
||||||
@ -1804,6 +1808,7 @@
|
|||||||
uoperations '()]
|
uoperations '()]
|
||||||
|
|
||||||
(let [attr (first attrs)]
|
(let [attr (first attrs)]
|
||||||
|
|
||||||
(if (nil? attr)
|
(if (nil? attr)
|
||||||
(cond-> changes
|
(cond-> changes
|
||||||
(seq roperations)
|
(seq roperations)
|
||||||
@ -1813,55 +1818,60 @@
|
|||||||
:always
|
:always
|
||||||
(generate-update-tokens container dest-shape origin-shape touched omit-touched? nil))
|
(generate-update-tokens container dest-shape origin-shape touched omit-touched? nil))
|
||||||
|
|
||||||
(let [attr-group (get ctk/sync-attrs attr)
|
(let [sync-group
|
||||||
|
(or (ctk/resolve-sync-group (:type origin-shape) attr)
|
||||||
|
(ctk/resolve-sync-group (:type dest-shape) attr))
|
||||||
|
|
||||||
;; position-data is a special case because can be affected by
|
;; position-data is a special case because can be affected by
|
||||||
;; :geometry-group and :content-group so, if the position-data
|
;; :geometry-group and :content-group so, if the
|
||||||
;; changes but the geometry is touched we need to reset the position-data
|
;; position-data changes but the geometry is touched
|
||||||
;; so it's calculated again
|
;; we need to reset the position-data so it's
|
||||||
reset-pos-data? (and (cfh/text-shape? origin-shape)
|
;; calculated again
|
||||||
(= attr :position-data)
|
reset-pos-data?
|
||||||
(not= (:position-data origin-shape) (:position-data dest-shape))
|
(and (cfh/text-shape? origin-shape)
|
||||||
(touched :geometry-group))
|
(= attr :position-data)
|
||||||
|
(not= (:position-data origin-shape) (:position-data dest-shape))
|
||||||
|
(touched :geometry-group))
|
||||||
|
|
||||||
;; On texts, when we want to omit the touched attrs, both text (the actual letters)
|
;; On texts, when we want to omit the touched attrs, both text (the actual letters)
|
||||||
;; and attrs (bold, font, etc) are in the same attr :content.
|
;; and attrs (bold, font, etc) are in the same attr :content.
|
||||||
;; If only one of them is touched, we want to adress this case and
|
;; If only one of them is touched, we want to adress this case and
|
||||||
;; only update the untouched one
|
;; only update the untouched one
|
||||||
text-content-change?
|
text-content-change?
|
||||||
(and
|
(and omit-touched?
|
||||||
omit-touched?
|
(cfh/text-shape? origin-shape)
|
||||||
(cfh/text-shape? origin-shape)
|
(= :content attr)
|
||||||
(= :content attr)
|
(touched sync-group))
|
||||||
(touched attr-group))
|
|
||||||
|
|
||||||
skip-operations?
|
skip-operations?
|
||||||
(or (= (get origin-shape attr) (get dest-shape attr))
|
(or (= (get origin-shape attr) (get dest-shape attr))
|
||||||
(and (touched attr-group)
|
(and (touched sync-group)
|
||||||
omit-touched?
|
omit-touched?
|
||||||
;; When it is a text-partial-change, we should generate operations
|
;; When it is a text-partial-change, we should generate operations
|
||||||
;; even when omit-touched? is true, but updating only the text or
|
;; even when omit-touched? is true, but updating only the text or
|
||||||
;; the attributes, omiting the other part
|
;; the attributes, omiting the other part
|
||||||
(not text-content-change?)))
|
(not text-content-change?)))
|
||||||
|
|
||||||
attr-val (when-not skip-operations?
|
attr-val
|
||||||
(cond
|
(when-not skip-operations?
|
||||||
;; If position data changes and the geometry group is touched
|
(cond
|
||||||
;; we need to put to nil so we can regenerate it
|
;; If position data changes and the geometry group is touched
|
||||||
reset-pos-data?
|
;; we need to put to nil so we can regenerate it
|
||||||
nil
|
reset-pos-data?
|
||||||
|
nil
|
||||||
|
|
||||||
text-content-change?
|
text-content-change?
|
||||||
(text-change-value (:content dest-shape)
|
(text-change-value (:content dest-shape)
|
||||||
(:content origin-shape)
|
(:content origin-shape)
|
||||||
touched)
|
touched)
|
||||||
|
|
||||||
:else
|
:else
|
||||||
(get origin-shape attr)))
|
(get origin-shape attr)))
|
||||||
|
|
||||||
;; If the final attr-value is the actual value, skip
|
;; If the final attr-value is the actual value, skip
|
||||||
skip-operations? (or skip-operations?
|
skip-operations?
|
||||||
(= attr-val (get dest-shape attr)))
|
(or skip-operations?
|
||||||
|
(= attr-val (get dest-shape attr)))
|
||||||
|
|
||||||
;; On a text-partial-change, we want to force a position-data reset
|
;; On a text-partial-change, we want to force a position-data reset
|
||||||
;; so it's calculated again
|
;; so it's calculated again
|
||||||
@ -2079,7 +2089,9 @@
|
|||||||
roperations [{:type :set-touched :touched (:touched previous-shape)}]
|
roperations [{:type :set-touched :touched (:touched previous-shape)}]
|
||||||
uoperations (list {:type :set-touched :touched (:touched current-shape)})]
|
uoperations (list {:type :set-touched :touched (:touched current-shape)})]
|
||||||
(if-let [attr (first attrs)]
|
(if-let [attr (first attrs)]
|
||||||
(let [attr-group (get ctk/sync-attrs attr)
|
(let [sync-group
|
||||||
|
(ctk/resolve-sync-group (:type previous-shape) attr)
|
||||||
|
|
||||||
skip-operations?
|
skip-operations?
|
||||||
(or
|
(or
|
||||||
;; For auto text, avoid copying geometry-driven attrs on switch.
|
;; For auto text, avoid copying geometry-driven attrs on switch.
|
||||||
@ -2096,7 +2108,7 @@
|
|||||||
(= (get previous-shape attr) (get origin-ref-shape attr))
|
(= (get previous-shape attr) (get origin-ref-shape attr))
|
||||||
|
|
||||||
;; If the attr is not touched, don't copy it
|
;; If the attr is not touched, don't copy it
|
||||||
(not (touched attr-group))
|
(not (touched sync-group))
|
||||||
|
|
||||||
;; If both variants (origin and destiny) don't have the same value
|
;; If both variants (origin and destiny) don't have the same value
|
||||||
;; for that attribute, don't copy it.
|
;; for that attribute, don't copy it.
|
||||||
@ -2120,12 +2132,11 @@
|
|||||||
;; If only one of them is touched, we want to adress this case and
|
;; If only one of them is touched, we want to adress this case and
|
||||||
;; only update the untouched one
|
;; only update the untouched one
|
||||||
text-change?
|
text-change?
|
||||||
(and
|
(and (not skip-operations?)
|
||||||
(not skip-operations?)
|
(cfh/text-shape? current-shape)
|
||||||
(cfh/text-shape? current-shape)
|
(cfh/text-shape? previous-shape)
|
||||||
(cfh/text-shape? previous-shape)
|
(= :content attr)
|
||||||
(= :content attr)
|
(touched sync-group))
|
||||||
(touched attr-group))
|
|
||||||
|
|
||||||
path-change?
|
path-change?
|
||||||
(and (= :path (:type current-shape))
|
(and (= :path (:type current-shape))
|
||||||
@ -2218,9 +2229,12 @@
|
|||||||
(->> attrs
|
(->> attrs
|
||||||
(reduce
|
(reduce
|
||||||
(fn [dest attr]
|
(fn [dest attr]
|
||||||
(let [attr-group (get ctk/sync-attrs attr)]
|
(let [sync-group
|
||||||
|
(or (ctk/resolve-sync-group (:type origin) attr)
|
||||||
|
(ctk/resolve-sync-group (:type dest) attr))]
|
||||||
(cond-> dest
|
(cond-> dest
|
||||||
(or (not (touched attr-group)) (not omit-touched?))
|
(or (not (touched sync-group))
|
||||||
|
(not omit-touched?))
|
||||||
(assoc attr (get origin attr)))))
|
(assoc attr (get origin attr)))))
|
||||||
dest))))
|
dest))))
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
(ns app.common.types.component
|
(ns app.common.types.component
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
|
[app.common.exceptions :as ex]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
[app.common.time :as-alias ct]
|
[app.common.time :as-alias ct]
|
||||||
[app.common.types.page :as ctp]
|
[app.common.types.page :as ctp]
|
||||||
@ -39,15 +40,17 @@
|
|||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
;; Attributes that may be synced in components, and the group they belong to.
|
;; Attributes that may be synced in components, and the group they belong to.
|
||||||
;; When one attribute is modified in a shape inside a component, the corresponding
|
;; When one attribute is modified in a shape inside a component, the
|
||||||
;; group is marked as :touched. Then, if the shape is synced with the remote shape
|
;; corresponding group is marked as :touched. Then, if the shape is synced with
|
||||||
;; in the main component, none of the attributes of the same group is changed.
|
;; the remote shape in the main component, none of the attributes of the same
|
||||||
|
;; group is changed.
|
||||||
|
|
||||||
(def sync-attrs
|
(def sync-attrs
|
||||||
{:name :name-group
|
{:name :name-group
|
||||||
:fills :fill-group
|
:fills :fill-group
|
||||||
:hide-fill-on-export :fill-group
|
:hide-fill-on-export :fill-group
|
||||||
:content :content-group
|
:content {:path :geometry-group
|
||||||
|
:text :content-group}
|
||||||
:position-data :content-group
|
:position-data :content-group
|
||||||
:hidden :visibility-group
|
:hidden :visibility-group
|
||||||
:blocked :modifiable-group
|
:blocked :modifiable-group
|
||||||
@ -143,6 +146,18 @@
|
|||||||
:layout-item-align-self
|
:layout-item-align-self
|
||||||
:interactions})
|
:interactions})
|
||||||
|
|
||||||
|
(defn resolve-sync-group
|
||||||
|
"Makes a by type resolution of the sync group. This is necessary
|
||||||
|
because we have several properties that has different group
|
||||||
|
depending on the shape type. Per example the attr `:content` is used
|
||||||
|
by path and text shapes and the sync groups are different for each
|
||||||
|
shape type."
|
||||||
|
[type attr]
|
||||||
|
(when-let [group (get sync-attrs attr)]
|
||||||
|
(if (map? group)
|
||||||
|
(get group type)
|
||||||
|
group)))
|
||||||
|
|
||||||
(defn component-attr?
|
(defn component-attr?
|
||||||
"Check if some attribute is one that is involved in component syncrhonization.
|
"Check if some attribute is one that is involved in component syncrhonization.
|
||||||
Note that design tokens also are involved, although they go by an alternate
|
Note that design tokens also are involved, although they go by an alternate
|
||||||
@ -150,7 +165,7 @@
|
|||||||
Also when detaching a nested copy it also needs to trigger a synchronization,
|
Also when detaching a nested copy it also needs to trigger a synchronization,
|
||||||
even though :shape-ref is not a synced attribute per se"
|
even though :shape-ref is not a synced attribute per se"
|
||||||
[attr]
|
[attr]
|
||||||
(or (get sync-attrs attr)
|
(or (contains? sync-attrs attr)
|
||||||
(= :shape-ref attr)
|
(= :shape-ref attr)
|
||||||
(= :applied-tokens attr)))
|
(= :applied-tokens attr)))
|
||||||
|
|
||||||
@ -356,15 +371,17 @@
|
|||||||
(or (not (instance-head? shape))
|
(or (not (instance-head? shape))
|
||||||
(not (in-component-copy? parent))))))
|
(not (in-component-copy? parent))))))
|
||||||
|
|
||||||
(defn all-touched-groups
|
(def ^:private all-touched-groups
|
||||||
[]
|
(reduce-kv (fn [acc _ v]
|
||||||
(into #{} (vals sync-attrs)))
|
(if (map? v)
|
||||||
|
(into acc (vals v))
|
||||||
|
(conj acc v)))
|
||||||
|
#{}
|
||||||
|
sync-attrs))
|
||||||
|
|
||||||
(defn valid-touched-group?
|
(defn valid-touched-group?
|
||||||
[group]
|
[group]
|
||||||
(try
|
(ex/ignoring
|
||||||
(or (contains? (all-touched-groups) group)
|
(or (contains? all-touched-groups group)
|
||||||
(and (swap-slot? group)
|
(and (swap-slot? group)
|
||||||
(some? (group->swap-slot group))))
|
(some? (group->swap-slot group))))))
|
||||||
(catch #?(:clj Throwable :cljs :default) _
|
|
||||||
false)))
|
|
||||||
|
|||||||
@ -554,14 +554,14 @@
|
|||||||
(let [old-applied-tokens (d/nilv (:applied-tokens shape) #{})
|
(let [old-applied-tokens (d/nilv (:applied-tokens shape) #{})
|
||||||
changed-token-attrs (filter #(not= (get old-applied-tokens %) (get new-applied-tokens %))
|
changed-token-attrs (filter #(not= (get old-applied-tokens %) (get new-applied-tokens %))
|
||||||
ctt/all-keys)
|
ctt/all-keys)
|
||||||
text-shape? (= (:type shape) :text)
|
shape-type (get shape :type)
|
||||||
|
text-shape? (= shape-type :text)
|
||||||
attrs-in-text-content? (some #(ctt/attrs-in-text-content %)
|
attrs-in-text-content? (some #(ctt/attrs-in-text-content %)
|
||||||
changed-token-attrs)
|
changed-token-attrs)
|
||||||
|
|
||||||
changed-groups (into #{}
|
changed-groups (into #{}
|
||||||
(comp (map ctt/token-attr->shape-attr)
|
(comp (map ctt/token-attr->shape-attr)
|
||||||
(map #(get ctk/sync-attrs %))
|
(keep #(ctk/resolve-sync-group shape-type %)))
|
||||||
(filter some?))
|
|
||||||
changed-token-attrs)
|
changed-token-attrs)
|
||||||
|
|
||||||
changed-groups (if (and text-shape?
|
changed-groups (if (and text-shape?
|
||||||
@ -577,8 +577,9 @@
|
|||||||
The returned shape will contain a metadata associated with it
|
The returned shape will contain a metadata associated with it
|
||||||
indicating if shape is touched or not."
|
indicating if shape is touched or not."
|
||||||
[shape attr val & {:keys [ignore-touched ignore-geometry]}]
|
[shape attr val & {:keys [ignore-touched ignore-geometry]}]
|
||||||
(let [group (get ctk/sync-attrs attr)
|
(let [type (get shape :type)
|
||||||
shape-val (get shape attr)
|
group (ctk/resolve-sync-group type attr)
|
||||||
|
shape-val (get shape attr)
|
||||||
|
|
||||||
ignore?
|
ignore?
|
||||||
(or ignore-touched
|
(or ignore-touched
|
||||||
@ -612,16 +613,20 @@
|
|||||||
(not equal?)
|
(not equal?)
|
||||||
(not (and ignore-geometry is-geometry?)))
|
(not (and ignore-geometry is-geometry?)))
|
||||||
|
|
||||||
content-diff-type (when (and (= (:type shape) :text) (= attr :content))
|
content-diff-type
|
||||||
(cttx/get-diff-type (:content shape) val))
|
(when (and (= type :text) (= attr :content))
|
||||||
|
(cttx/get-diff-type (:content shape) val))
|
||||||
|
|
||||||
token-groups (if (= attr :applied-tokens)
|
token-groups
|
||||||
(get-token-groups shape val)
|
(if (= attr :applied-tokens)
|
||||||
#{})
|
(get-token-groups shape val)
|
||||||
|
#{})
|
||||||
|
|
||||||
|
groups
|
||||||
|
(cond-> token-groups
|
||||||
|
(and group (not equal?))
|
||||||
|
(set/union #{group} content-diff-type))]
|
||||||
|
|
||||||
groups (cond-> token-groups
|
|
||||||
(and group (not equal?))
|
|
||||||
(set/union #{group} content-diff-type))]
|
|
||||||
(cond-> shape
|
(cond-> shape
|
||||||
;; Depending on the origin of the attribute change, we need or not to
|
;; Depending on the origin of the attribute change, we need or not to
|
||||||
;; set the "touched" flag for the group the attribute belongs to.
|
;; set the "touched" flag for the group the attribute belongs to.
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
;; --- Auxiliar Functions
|
;; --- Auxiliar Functions
|
||||||
|
|
||||||
(def valid-browsers
|
(def valid-browsers
|
||||||
#{:chrome :firefox :safari :safari-16 :safari-17 :edge :other})
|
#{:chrome :firefox :safari :safari-16 :safari-17 :safari-18 :safari-26 :edge :other})
|
||||||
|
|
||||||
(def valid-platforms
|
(def valid-platforms
|
||||||
#{:windows :linux :macos :other})
|
#{:windows :linux :macos :other})
|
||||||
@ -40,13 +40,17 @@
|
|||||||
check-edge? (fn [] (str/includes? user-agent "edg"))
|
check-edge? (fn [] (str/includes? user-agent "edg"))
|
||||||
check-safari? (fn [] (str/includes? user-agent "safari"))
|
check-safari? (fn [] (str/includes? user-agent "safari"))
|
||||||
check-safari-16? (fn [] (and (check-safari?) (str/includes? user-agent "version/16")))
|
check-safari-16? (fn [] (and (check-safari?) (str/includes? user-agent "version/16")))
|
||||||
check-safari-17? (fn [] (and (check-safari?) (str/includes? user-agent "version/17")))]
|
check-safari-17? (fn [] (and (check-safari?) (str/includes? user-agent "version/17")))
|
||||||
|
check-safari-18? (fn [] (and (check-safari?) (str/includes? user-agent "version/18")))
|
||||||
|
check-safari-26? (fn [] (and (check-safari?) (str/includes? user-agent "version/26")))]
|
||||||
(cond
|
(cond
|
||||||
^boolean (check-edge?) :edge
|
^boolean (check-edge?) :edge
|
||||||
^boolean (check-chrome?) :chrome
|
^boolean (check-chrome?) :chrome
|
||||||
^boolean (check-firefox?) :firefox
|
^boolean (check-firefox?) :firefox
|
||||||
^boolean (check-safari-16?) :safari-16
|
^boolean (check-safari-16?) :safari-16
|
||||||
^boolean (check-safari-17?) :safari-17
|
^boolean (check-safari-17?) :safari-17
|
||||||
|
^boolean (check-safari-18?) :safari-18
|
||||||
|
^boolean (check-safari-26?) :safari-26
|
||||||
^boolean (check-safari?) :safari
|
^boolean (check-safari?) :safari
|
||||||
:else :unknown)))
|
:else :unknown)))
|
||||||
|
|
||||||
@ -212,7 +216,7 @@
|
|||||||
(defn ^boolean check-browser? [candidate]
|
(defn ^boolean check-browser? [candidate]
|
||||||
(dm/assert! (contains? valid-browsers candidate))
|
(dm/assert! (contains? valid-browsers candidate))
|
||||||
(if (= candidate :safari)
|
(if (= candidate :safari)
|
||||||
(contains? #{:safari :safari-16 :safari-17} browser)
|
(contains? #{:safari :safari-16 :safari-17 :safari-18 :safari-26} browser)
|
||||||
(= candidate browser)))
|
(= candidate browser)))
|
||||||
|
|
||||||
(defn ^boolean check-platform? [candidate]
|
(defn ^boolean check-platform? [candidate]
|
||||||
|
|||||||
@ -63,7 +63,7 @@
|
|||||||
(ptk/reify ::initialize-rasterizer
|
(ptk/reify ::initialize-rasterizer
|
||||||
ptk/EffectEvent
|
ptk/EffectEvent
|
||||||
(effect [_ state _]
|
(effect [_ state _]
|
||||||
(when (feat/active-feature? state "render-wasm/v1")
|
(when-not (feat/active-feature? state "render-wasm/v1")
|
||||||
(thr/init!)))))
|
(thr/init!)))))
|
||||||
|
|
||||||
(defn initialize
|
(defn initialize
|
||||||
|
|||||||
@ -44,6 +44,7 @@
|
|||||||
[app.main.data.workspace.shapes :as dwsh]
|
[app.main.data.workspace.shapes :as dwsh]
|
||||||
[app.main.data.workspace.specialized-panel :as dwsp]
|
[app.main.data.workspace.specialized-panel :as dwsp]
|
||||||
[app.main.data.workspace.thumbnails :as dwt]
|
[app.main.data.workspace.thumbnails :as dwt]
|
||||||
|
[app.main.data.workspace.thumbnails-wasm :as dwt.wasm]
|
||||||
[app.main.data.workspace.transforms :as dwtr]
|
[app.main.data.workspace.transforms :as dwtr]
|
||||||
[app.main.data.workspace.undo :as dwu]
|
[app.main.data.workspace.undo :as dwu]
|
||||||
[app.main.data.workspace.wasm-text :as dwwt]
|
[app.main.data.workspace.wasm-text :as dwwt]
|
||||||
@ -945,7 +946,12 @@
|
|||||||
component (ctkl/get-component data component-id)
|
component (ctkl/get-component data component-id)
|
||||||
page-id (:main-instance-page component)
|
page-id (:main-instance-page component)
|
||||||
root-id (:main-instance-id component)]
|
root-id (:main-instance-id component)]
|
||||||
(dwt/update-thumbnail file-id page-id root-id tag "update-component-thumbnail-sync")))
|
(if (and (= tag "component")
|
||||||
|
(features/active-feature? state "render-wasm/v1"))
|
||||||
|
;; WASM: render immediately, UI only — server persist happens
|
||||||
|
;; on the debounced path (update-component-thumbnail)
|
||||||
|
(dwt.wasm/render-thumbnail file-id page-id root-id)
|
||||||
|
(dwt/update-thumbnail file-id page-id root-id tag "update-component-thumbnail-sync"))))
|
||||||
|
|
||||||
(defn update-component-sync
|
(defn update-component-sync
|
||||||
([shape-id file-id] (update-component-sync shape-id file-id nil))
|
([shape-id file-id] (update-component-sync shape-id file-id nil))
|
||||||
@ -964,9 +970,12 @@
|
|||||||
(dwu/start-undo-transaction undo-id)
|
(dwu/start-undo-transaction undo-id)
|
||||||
(update-component shape-id undo-group)
|
(update-component shape-id undo-group)
|
||||||
|
|
||||||
;; These two calls are necessary for properly sync thumbnails
|
;; These calls are necessary for properly sync thumbnails
|
||||||
;; when a main component does not live in the same page
|
;; when a main component does not live in the same page.
|
||||||
(update-component-thumbnail-sync state component-id file-id "frame")
|
;; When WASM is active, skip the "frame" tag (SVG-based) since
|
||||||
|
;; component previews are rendered locally via WASM.
|
||||||
|
(when-not (features/active-feature? state "render-wasm/v1")
|
||||||
|
(update-component-thumbnail-sync state component-id file-id "frame"))
|
||||||
(update-component-thumbnail-sync state component-id file-id "component")
|
(update-component-thumbnail-sync state component-id file-id "component")
|
||||||
|
|
||||||
(sync-file current-file-id file-id :components component-id undo-group)
|
(sync-file current-file-id file-id :components component-id undo-group)
|
||||||
@ -991,13 +1000,21 @@
|
|||||||
(dwu/commit-undo-transaction undo-id)))))))
|
(dwu/commit-undo-transaction undo-id)))))))
|
||||||
|
|
||||||
(defn update-component-thumbnail
|
(defn update-component-thumbnail
|
||||||
"Update the thumbnail of the component with the given id, in the
|
"Persist the thumbnail of the component to the server.
|
||||||
current file and in the imported libraries."
|
For WASM, the UI is already up-to-date from the immediate render in
|
||||||
|
update-component-thumbnail-sync, so this only persists.
|
||||||
|
For SVG, this does the full render + persist."
|
||||||
[component-id file-id]
|
[component-id file-id]
|
||||||
(ptk/reify ::update-component-thumbnail
|
(ptk/reify ::update-component-thumbnail
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state _]
|
(watch [_ state _]
|
||||||
(rx/of (update-component-thumbnail-sync state component-id file-id "component")))))
|
(if (features/active-feature? state "render-wasm/v1")
|
||||||
|
(let [data (dsh/lookup-file-data state file-id)
|
||||||
|
component (ctkl/get-component data component-id)
|
||||||
|
page-id (:main-instance-page component)
|
||||||
|
root-id (:main-instance-id component)]
|
||||||
|
(rx/of (dwt.wasm/persist-thumbnail file-id page-id root-id)))
|
||||||
|
(rx/of (update-component-thumbnail-sync state component-id file-id "component"))))))
|
||||||
|
|
||||||
(defn- find-shape-index
|
(defn- find-shape-index
|
||||||
[objects id shape-id]
|
[objects id shape-id]
|
||||||
@ -1373,9 +1390,18 @@
|
|||||||
(->> (rx/from changed-components)
|
(->> (rx/from changed-components)
|
||||||
(rx/map #(component-changed % (:id old-data) undo-group))))
|
(rx/map #(component-changed % (:id old-data) undo-group))))
|
||||||
;; even if save-undo? is false, we need to update the :modified-date of the component
|
;; even if save-undo? is false, we need to update the :modified-date of the component
|
||||||
;; (for example, for undos)
|
;; (for example, for undos). When WASM is active, also re-render the thumbnail
|
||||||
(->> (rx/from changed-components)
|
;; so undo/redo visually updates component previews.
|
||||||
(rx/map touch-component)))
|
(->> (mapcat (fn [component-id]
|
||||||
|
(if (features/active-feature? @st/state "render-wasm/v1")
|
||||||
|
(let [component (ctkl/get-component old-data component-id)]
|
||||||
|
[(touch-component component-id)
|
||||||
|
(dwt.wasm/render-thumbnail (:id old-data)
|
||||||
|
(:main-instance-page component)
|
||||||
|
(:main-instance-id component))])
|
||||||
|
[(touch-component component-id)]))
|
||||||
|
changed-components)
|
||||||
|
(rx/from)))
|
||||||
|
|
||||||
(rx/empty)))))
|
(rx/empty)))))
|
||||||
|
|
||||||
@ -1390,18 +1416,32 @@
|
|||||||
(rx/debounce 5000)
|
(rx/debounce 5000)
|
||||||
(rx/tap #(log/trc :hint "buffer initialized")))]
|
(rx/tap #(log/trc :hint "buffer initialized")))]
|
||||||
|
|
||||||
(when (contains? cf/flags :component-thumbnails)
|
(when (or (contains? cf/flags :component-thumbnails)
|
||||||
(->> (rx/merge
|
(features/active-feature? @st/state "render-wasm/v1"))
|
||||||
changes-s
|
(let [wasm? (features/active-feature? @st/state "render-wasm/v1")]
|
||||||
|
(->> (rx/merge
|
||||||
|
changes-s
|
||||||
|
|
||||||
(->> changes-s
|
;; WASM: render thumbnails immediately for instant UI feedback
|
||||||
(rx/map deref)
|
(if wasm?
|
||||||
(rx/buffer-until notifier-s)
|
(->> changes-s
|
||||||
(rx/mapcat #(into #{} %))
|
(rx/filter (ptk/type? ::component-changed))
|
||||||
(rx/map (fn [[component-id file-id]]
|
(rx/map deref)
|
||||||
(update-component-thumbnail component-id file-id)))))
|
(rx/map (fn [[component-id file-id]]
|
||||||
|
(update-component-thumbnail-sync @st/state component-id file-id "component"))))
|
||||||
|
(rx/empty))
|
||||||
|
|
||||||
(rx/take-until stopper-s)))))))
|
;; Persist thumbnails to the server in batches after user
|
||||||
|
;; becomes inactive for 5 seconds.
|
||||||
|
(->> changes-s
|
||||||
|
(rx/filter (ptk/type? ::component-changed))
|
||||||
|
(rx/map deref)
|
||||||
|
(rx/buffer-until notifier-s)
|
||||||
|
(rx/mapcat #(into #{} %))
|
||||||
|
(rx/map (fn [[component-id file-id]]
|
||||||
|
(update-component-thumbnail component-id file-id)))))
|
||||||
|
|
||||||
|
(rx/take-until stopper-s))))))))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Backend interactions
|
;; Backend interactions
|
||||||
|
|||||||
@ -118,7 +118,7 @@
|
|||||||
(rx/ignore))))
|
(rx/ignore))))
|
||||||
(rx/empty)))))))
|
(rx/empty)))))))
|
||||||
|
|
||||||
(defn- assoc-thumbnail
|
(defn assoc-thumbnail
|
||||||
[object-id uri]
|
[object-id uri]
|
||||||
(let [prev-uri* (volatile! nil)]
|
(let [prev-uri* (volatile! nil)]
|
||||||
(ptk/reify ::assoc-thumbnail
|
(ptk/reify ::assoc-thumbnail
|
||||||
|
|||||||
118
frontend/src/app/main/data/workspace/thumbnails_wasm.cljs
Normal file
118
frontend/src/app/main/data/workspace/thumbnails_wasm.cljs
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
;; 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
|
||||||
|
|
||||||
|
(ns app.main.data.workspace.thumbnails-wasm
|
||||||
|
"WASM-based component thumbnail rendering.
|
||||||
|
Renders component previews using the existing workspace WASM context
|
||||||
|
via render-shape-pixels (SurfaceId::Export), avoiding a separate
|
||||||
|
WASM module in the worker.
|
||||||
|
|
||||||
|
Two-phase design:
|
||||||
|
- render-thumbnail: immediate WASM render → UI update (no server)
|
||||||
|
- persist-thumbnail: pushes current data-uri to the server (debounced)"
|
||||||
|
(:require
|
||||||
|
[app.common.data.macros :as dm]
|
||||||
|
[app.common.logging :as l]
|
||||||
|
[app.common.thumbnails :as thc]
|
||||||
|
[app.common.time :as ct]
|
||||||
|
[app.main.data.workspace.thumbnails :as dwt]
|
||||||
|
[app.main.repo :as rp]
|
||||||
|
[app.render-wasm.api :as wasm.api]
|
||||||
|
[app.util.webapi :as wapi]
|
||||||
|
[beicon.v2.core :as rx]
|
||||||
|
[cuerdas.core :as str]
|
||||||
|
[potok.v2.core :as ptk]))
|
||||||
|
|
||||||
|
(l/set-level! :warn)
|
||||||
|
|
||||||
|
(defn- png-bytes->data-uri
|
||||||
|
"Converts a Uint8Array of PNG bytes to a data:image/png;base64 URI."
|
||||||
|
[png-bytes]
|
||||||
|
(let [blob (wapi/create-blob png-bytes "image/png")
|
||||||
|
reader (js/FileReader.)]
|
||||||
|
(js/Promise.
|
||||||
|
(fn [resolve reject]
|
||||||
|
(set! (.-onload reader)
|
||||||
|
(fn [] (resolve (.-result reader))))
|
||||||
|
(set! (.-onerror reader)
|
||||||
|
(fn [e] (reject e)))
|
||||||
|
(.readAsDataURL reader blob)))))
|
||||||
|
|
||||||
|
(defn- render-component-pixels
|
||||||
|
"Renders a component frame using the workspace WASM context.
|
||||||
|
Returns an observable that emits a data-uri string.
|
||||||
|
Deferred by one animation frame so that process-shape-changes!
|
||||||
|
has time to sync all child shapes to WASM memory first."
|
||||||
|
[frame-id]
|
||||||
|
(rx/create
|
||||||
|
(fn [subs]
|
||||||
|
(js/requestAnimationFrame
|
||||||
|
(fn [_]
|
||||||
|
(try
|
||||||
|
(let [png-bytes (wasm.api/render-shape-pixels frame-id 1)]
|
||||||
|
(if (or (nil? png-bytes) (zero? (.-length png-bytes)))
|
||||||
|
(do (js/console.error "[thumbnails] render-shape-pixels returned empty for" (str frame-id))
|
||||||
|
(rx/end! subs))
|
||||||
|
(.then (png-bytes->data-uri png-bytes)
|
||||||
|
(fn [data-uri]
|
||||||
|
(rx/push! subs data-uri)
|
||||||
|
(rx/end! subs))
|
||||||
|
(fn [err]
|
||||||
|
(rx/error! subs err)))))
|
||||||
|
(catch :default err
|
||||||
|
(rx/error! subs err)))))
|
||||||
|
nil)))
|
||||||
|
|
||||||
|
(defn render-thumbnail
|
||||||
|
"Renders a component thumbnail via WASM and updates the UI immediately.
|
||||||
|
Does NOT persist to the server — persistence is handled separately
|
||||||
|
by `persist-thumbnail` on a debounced schedule."
|
||||||
|
[file-id page-id frame-id]
|
||||||
|
(let [object-id (thc/fmt-object-id file-id page-id frame-id "component")]
|
||||||
|
(ptk/reify ::render-thumbnail
|
||||||
|
cljs.core/IDeref
|
||||||
|
(-deref [_] object-id)
|
||||||
|
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ _ stream]
|
||||||
|
(let [tp (ct/tpoint-ms)]
|
||||||
|
(->> (render-component-pixels frame-id)
|
||||||
|
(rx/map
|
||||||
|
(fn [data-uri]
|
||||||
|
(l/dbg :hint "component thumbnail rendered (wasm)"
|
||||||
|
:elapsed (dm/str (tp) "ms"))
|
||||||
|
(dwt/assoc-thumbnail object-id data-uri)))
|
||||||
|
|
||||||
|
(rx/catch (fn [err]
|
||||||
|
(js/console.error "[thumbnails] error rendering component thumbnail" err)
|
||||||
|
(rx/empty)))
|
||||||
|
|
||||||
|
(rx/take-until
|
||||||
|
(->> stream
|
||||||
|
(rx/filter (ptk/type? ::dwt/clear-thumbnail))
|
||||||
|
(rx/filter #(= (deref %) object-id))))))))))
|
||||||
|
|
||||||
|
(defn persist-thumbnail
|
||||||
|
"Persists the current component thumbnail data-uri to the server.
|
||||||
|
Expects that `render-thumbnail` has already been called so the
|
||||||
|
data-uri is present in app state. If not, this is a no-op."
|
||||||
|
[file-id page-id frame-id]
|
||||||
|
(let [object-id (thc/fmt-object-id file-id page-id frame-id "component")]
|
||||||
|
(ptk/reify ::persist-thumbnail
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(let [data-uri (dm/get-in state [:thumbnails object-id])]
|
||||||
|
(if (and (some? data-uri)
|
||||||
|
(str/starts-with? data-uri "data:"))
|
||||||
|
(let [blob (wapi/data-uri->blob data-uri)]
|
||||||
|
(->> (rp/cmd! :create-file-object-thumbnail
|
||||||
|
{:file-id file-id
|
||||||
|
:object-id object-id
|
||||||
|
:media blob
|
||||||
|
:tag "component"})
|
||||||
|
(rx/catch rx/empty)
|
||||||
|
(rx/ignore)))
|
||||||
|
(rx/empty)))))))
|
||||||
@ -68,14 +68,16 @@
|
|||||||
"false" false
|
"false" false
|
||||||
nil))
|
nil))
|
||||||
|
|
||||||
|
(def wasm-url-override-ref
|
||||||
|
(l/derived wasm-url-override st/state))
|
||||||
|
|
||||||
(defn active-feature?
|
(defn active-feature?
|
||||||
"Given a state and feature, check if feature is enabled."
|
"Given a state and feature, check if feature is enabled."
|
||||||
[state feature]
|
[state feature]
|
||||||
(assert (contains? cfeat/supported-features feature)
|
(assert (contains? cfeat/supported-features feature)
|
||||||
"feature not supported")
|
"feature not supported")
|
||||||
|
|
||||||
(let [wasm-override (when (= feature "render-wasm/v1")
|
(let [wasm-override (when (= feature "render-wasm/v1") (wasm-url-override state))]
|
||||||
(wasm-url-override state))]
|
|
||||||
(cond
|
(cond
|
||||||
(some? wasm-override)
|
(some? wasm-override)
|
||||||
wasm-override
|
wasm-override
|
||||||
@ -110,8 +112,15 @@
|
|||||||
(defn use-feature
|
(defn use-feature
|
||||||
"A react hook that checks if feature is currently enabled"
|
"A react hook that checks if feature is currently enabled"
|
||||||
[feature]
|
[feature]
|
||||||
(let [enabled-features (mf/deref features-ref)]
|
(let [enabled-features (mf/deref features-ref)
|
||||||
(contains? enabled-features feature)))
|
wasm-override (mf/deref wasm-url-override-ref)
|
||||||
|
wasm-override (when (= feature "render-wasm/v1") wasm-override)]
|
||||||
|
(cond
|
||||||
|
(some? wasm-override)
|
||||||
|
wasm-override
|
||||||
|
|
||||||
|
:else
|
||||||
|
(contains? enabled-features feature))))
|
||||||
|
|
||||||
(defn toggle-feature
|
(defn toggle-feature
|
||||||
"An event constructor for runtime feature toggle.
|
"An event constructor for runtime feature toggle.
|
||||||
|
|||||||
@ -205,6 +205,19 @@
|
|||||||
(dwt/dispose! instance)
|
(dwt/dispose! instance)
|
||||||
(st/emit! (dwt/update-editor nil)))))
|
(st/emit! (dwt/update-editor nil)))))
|
||||||
|
|
||||||
|
(defn vertical-align-editor-classes
|
||||||
|
"Returns `[align-top? align-center? align-bottom?]` for the text editor root
|
||||||
|
flex layout. When `render-wasm?` is true, the `foreignObject` is already
|
||||||
|
positioned using the same vertical offset as Skia (`content_rect`); applying
|
||||||
|
`justify-content` center/end here would double the offset and misalign the DOM
|
||||||
|
editor with the rendered text, caret, and selection."
|
||||||
|
[content render-wasm?]
|
||||||
|
(if render-wasm?
|
||||||
|
[true false false]
|
||||||
|
[(= (:vertical-align content "top") "top")
|
||||||
|
(= (:vertical-align content) "center")
|
||||||
|
(= (:vertical-align content) "bottom")]))
|
||||||
|
|
||||||
(defn get-color-from-content [content]
|
(defn get-color-from-content [content]
|
||||||
(let [fills (->> (tree-seq map? :children content)
|
(let [fills (->> (tree-seq map? :children content)
|
||||||
(mapcat :fills)
|
(mapcat :fills)
|
||||||
@ -225,7 +238,7 @@
|
|||||||
"Text editor (HTML)"
|
"Text editor (HTML)"
|
||||||
{::mf/wrap [mf/memo]
|
{::mf/wrap [mf/memo]
|
||||||
::mf/props :obj}
|
::mf/props :obj}
|
||||||
[{:keys [shape canvas-ref]}]
|
[{:keys [shape canvas-ref render-wasm?] :or {render-wasm? false}}]
|
||||||
(let [content (:content shape)
|
(let [content (:content shape)
|
||||||
shape-id (dm/get-prop shape :id)
|
shape-id (dm/get-prop shape :id)
|
||||||
fill-color (get-color-from-content content)
|
fill-color (get-color-from-content content)
|
||||||
@ -244,6 +257,9 @@
|
|||||||
text-color (or fill-color (get-default-text-color {:frame frame
|
text-color (or fill-color (get-default-text-color {:frame frame
|
||||||
:background-color background-color}) color/black)
|
:background-color background-color}) color/black)
|
||||||
|
|
||||||
|
[align-top? align-center? align-bottom?]
|
||||||
|
(vertical-align-editor-classes content render-wasm?)
|
||||||
|
|
||||||
fonts
|
fonts
|
||||||
(-> (mf/use-memo (mf/deps content) #(get-fonts content))
|
(-> (mf/use-memo (mf/deps content) #(get-fonts content))
|
||||||
(h/use-equal-memo))]
|
(h/use-equal-memo))]
|
||||||
@ -291,9 +307,9 @@
|
|||||||
:grow-type-fixed (= (:grow-type shape) :fixed)
|
:grow-type-fixed (= (:grow-type shape) :fixed)
|
||||||
:grow-type-auto-width (= (:grow-type shape) :auto-width)
|
:grow-type-auto-width (= (:grow-type shape) :auto-width)
|
||||||
:grow-type-auto-height (= (:grow-type shape) :auto-height)
|
:grow-type-auto-height (= (:grow-type shape) :auto-height)
|
||||||
:align-top (= (:vertical-align content "top") "top")
|
:align-top align-top?
|
||||||
:align-center (= (:vertical-align content) "center")
|
:align-center align-center?
|
||||||
:align-bottom (= (:vertical-align content) "bottom")))
|
:align-bottom align-bottom?))
|
||||||
:ref editor-ref
|
:ref editor-ref
|
||||||
:data-testid "text-editor-content"
|
:data-testid "text-editor-content"
|
||||||
:data-x (dm/get-prop shape :x)
|
:data-x (dm/get-prop shape :x)
|
||||||
@ -348,7 +364,7 @@
|
|||||||
;; NOTE: this teoretically breaks hooks rules, but in practice
|
;; NOTE: this teoretically breaks hooks rules, but in practice
|
||||||
;; it is imposible to really break it
|
;; it is imposible to really break it
|
||||||
maybe-zoom
|
maybe-zoom
|
||||||
(when (cf/check-browser? :safari-16)
|
(when (cf/check-browser? :safari)
|
||||||
(mf/deref refs/selected-zoom))
|
(mf/deref refs/selected-zoom))
|
||||||
|
|
||||||
shape (cond-> shape
|
shape (cond-> shape
|
||||||
@ -404,17 +420,23 @@
|
|||||||
;; Transform is necessary when there is a text overflow and the vertical
|
;; Transform is necessary when there is a text overflow and the vertical
|
||||||
;; aligment is center or bottom.
|
;; aligment is center or bottom.
|
||||||
(and (not render-wasm?)
|
(and (not render-wasm?)
|
||||||
(not (cf/check-browser? :safari)))
|
(not (cf/check-browser? :safari-16)))
|
||||||
(obj/merge!
|
(obj/merge!
|
||||||
#js {:transform (dm/fmt "translate(%px, %px)" (- (dm/get-prop shape :x) x) (- (dm/get-prop shape :y) y))})
|
#js {:transform (dm/fmt "translate(%px, %px)" (- (dm/get-prop shape :x) x) (- (dm/get-prop shape :y) y))})
|
||||||
|
|
||||||
(cf/check-browser? :safari-17)
|
(and (cf/check-browser? :safari) (not (cf/check-browser? :safari-16)))
|
||||||
(obj/merge!
|
(obj/merge!
|
||||||
#js {:height "100%"
|
#js {:height "100%"
|
||||||
:display "flex"
|
:display "flex"
|
||||||
:flexDirection "column"
|
:flexDirection "column"
|
||||||
:justifyContent (shape->justify shape)})
|
:justifyContent (shape->justify shape)})
|
||||||
|
|
||||||
|
(or (cf/check-browser? :safari-26) (cf/check-browser? :safari-18))
|
||||||
|
(obj/merge!
|
||||||
|
#js {:position "fixed"
|
||||||
|
:transform-origin "top left"
|
||||||
|
:transform (dm/fmt "scale(%)" maybe-zoom)})
|
||||||
|
|
||||||
(cf/check-browser? :safari-16)
|
(cf/check-browser? :safari-16)
|
||||||
(obj/merge!
|
(obj/merge!
|
||||||
#js {:position "fixed"
|
#js {:position "fixed"
|
||||||
@ -435,4 +457,5 @@
|
|||||||
[:div {:style style}
|
[:div {:style style}
|
||||||
[:& text-editor-html {:shape shape
|
[:& text-editor-html {:shape shape
|
||||||
:canvas-ref canvas-ref
|
:canvas-ref canvas-ref
|
||||||
|
:render-wasm? render-wasm?
|
||||||
:key (dm/str shape-id)}]]]]))
|
:key (dm/str shape-id)}]]]]))
|
||||||
|
|||||||
@ -24,6 +24,7 @@
|
|||||||
[app.main.data.workspace.libraries :as dwl]
|
[app.main.data.workspace.libraries :as dwl]
|
||||||
[app.main.data.workspace.undo :as dwu]
|
[app.main.data.workspace.undo :as dwu]
|
||||||
[app.main.data.workspace.variants :as dwv]
|
[app.main.data.workspace.variants :as dwv]
|
||||||
|
[app.main.features :as features]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.render :refer [component-svg component-svg-thumbnail]]
|
[app.main.render :refer [component-svg component-svg-thumbnail]]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
@ -298,7 +299,8 @@
|
|||||||
(inc retry))))]
|
(inc retry))))]
|
||||||
|
|
||||||
(if (and (some? thumbnail-uri)
|
(if (and (some? thumbnail-uri)
|
||||||
(contains? cf/flags :component-thumbnails))
|
(or (contains? cf/flags :component-thumbnails)
|
||||||
|
(features/active-feature? @st/state "render-wasm/v1")))
|
||||||
[:& component-svg-thumbnail
|
[:& component-svg-thumbnail
|
||||||
{:thumbnail-uri thumbnail-uri
|
{:thumbnail-uri thumbnail-uri
|
||||||
:class class
|
:class class
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user