🎉 Separate the content of the text of the rest of properties on variants

This commit is contained in:
Pablo Alba 2025-06-11 11:22:43 +02:00 committed by GitHub
parent 9761cba337
commit 925b6c02d6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 150 additions and 90 deletions

View file

@ -1665,45 +1665,26 @@
:shapes all-parents})])))) :shapes all-parents})]))))
(defn- text-partial-change-value
[touched-shape untouched-shape touched]
(cond
(touched :text-content-structure-same-attrs)
;; Keep the touched-shape structure and texts, update its attrs to make them like the untouched-shape
(cttx/copy-attrs-keys touched-shape (cttx/get-first-paragraph-text-attrs untouched-shape))
(touched :text-content-text)
;; Keep the texts touched in touched-shape copy the texts from dest over the attrs of untouched-shape
(cttx/copy-text-keys touched-shape untouched-shape)
(touched :text-content-attribute)
;; Keep the attrs touched in touched-shape copy the texts from untouched-shape over the attrs of touched-shape
(cttx/copy-text-keys untouched-shape touched-shape)))
(defn- add-update-attr-operations (defn- add-update-attr-operations
[attr dest-shape origin-shape roperations uoperations touched is-text-partial-change?] [attr dest-shape roperations uoperations attr-val]
(let [orig-value (get origin-shape attr) (let [roperation {:type :set
dest-value (get dest-shape attr)
;; position-data is a special case because can be affected by :geometry-group and :content-group
;; so, if the position-data changes but the geometry is touched we need to reset the position-data
;; so it's calculated again
reset-pos-data?
(and (cfh/text-shape? origin-shape)
(= attr :position-data)
(not= orig-value dest-value)
(touched :geometry-group))
;; We want to split the changes on the text itself and on its properties
text-value
(when is-text-partial-change?
(cond
(touched :text-content-structure-same-attrs)
;; Keep the dest structure and texts, update its attrs to make them like the origin
(cttx/copy-attrs-keys dest-value (cttx/get-first-paragraph-text-attrs orig-value))
(touched :text-content-text)
;; Keep the texts touched in dest: copy the texts from dest over the attrs of origin
(cttx/copy-text-keys dest-value orig-value)
(touched :text-content-attribute)
;; Keep the attrs touched in dest: copy the texts from origin over the attrs of dest
(cttx/copy-text-keys orig-value dest-value)))
val (cond
;; If position data changes and the geometry group is touched
;; we need to put to nil so we can regenerate it
reset-pos-data? nil
is-text-partial-change? text-value
:else orig-value)
roperation {:type :set
:attr attr :attr attr
:val val :val attr-val
:ignore-touched true} :ignore-touched true}
uoperation {:type :set uoperation {:type :set
:attr attr :attr attr
@ -1714,13 +1695,13 @@
(defn- is-text-partial-change? (defn- is-text-partial-change?
"Check if the attr update is a text partial change" "Check if the attr update is a text partial change"
[origin-shape dest-shape attr touched] [untouched-shape touched-shape]
(let [partial-text-keys [:text-content-attribute :text-content-text] (let [touched (get touched-shape :touched #{})
active-keys (filter touched partial-text-keys) partial-text-keys [:text-content-attribute :text-content-text]
orig-content (get origin-shape attr) active-keys (filter touched partial-text-keys)
orig-attrs (cttx/get-first-paragraph-text-attrs orig-content) untouched-content (:content untouched-shape)
untouched-attrs (cttx/get-first-paragraph-text-attrs untouched-content)
equal-orig-attrs? (cttx/equal-attrs? orig-content orig-attrs)] eq-untouched-attrs? (cttx/equal-attrs? untouched-content untouched-attrs)]
(and (and
(or (or
;; One and only one of the keys is pressent ;; One and only one of the keys is pressent
@ -1731,12 +1712,12 @@
(or (or
;; Both has the same structure ;; Both has the same structure
(cttx/equal-structure? (:content origin-shape) (:content dest-shape)) (cttx/equal-structure? untouched-content (:content touched-shape))
;; The origin and destiny have different structures, but each have the same attrs ;; The origin and destiny have different structures, but each have the same attrs
;; for all the items on its content tree ;; for all the items on its content tree
(and (and
equal-orig-attrs? eq-untouched-attrs?
(touched :text-content-structure-same-attrs)))))) (touched :text-content-structure-same-attrs))))))
(defn- update-attrs (defn- update-attrs
@ -1782,73 +1763,152 @@
(generate-update-tokens container dest-shape origin-shape touched omit-touched?)) (generate-update-tokens container dest-shape origin-shape touched omit-touched?))
(let [attr-group (get ctk/sync-attrs attr) (let [attr-group (get ctk/sync-attrs attr)
;; position-data is a special case because can be affected by
;; :geometry-group and :content-group so, if the position-data
;; changes but the geometry is touched we need to reset the position-data
;; so it's calculated again
reset-pos-data? (and (cfh/text-shape? origin-shape)
(= 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-partial-change? (when (and text-partial-change?
omit-touched? (when (and
(= :text (:type origin-shape)) omit-touched?
(= :content attr) (cfh/text-shape? origin-shape)
(touched attr-group)) (= :content attr)
(is-text-partial-change? origin-shape dest-shape attr touched)) (touched attr-group))
(is-text-partial-change? origin-shape dest-shape))
skip-operations? (or (= (get origin-shape attr) (get dest-shape attr)) skip-operations?
(and (touched attr-group) (or (= (get origin-shape attr) (get dest-shape attr))
omit-touched? (and (touched attr-group)
;; When it is a text-partial-change, we should generate operations omit-touched?
;; even when omit-touched? is true, but updating only the text or ;; When it is a text-partial-change, we should generate operations
;; the attributes, omiting the other part ;; even when omit-touched? is true, but updating only the text or
(not text-partial-change?))) ;; the attributes, omiting the other part
(not text-partial-change?)))
attr-val (when-not skip-operations?
(cond
;; If position data changes and the geometry group is touched
;; we need to put to nil so we can regenerate it
reset-pos-data?
nil
text-partial-change?
(text-partial-change-value (:content dest-shape)
(:content origin-shape)
touched)
:else
(get origin-shape attr)))
[roperations' uoperations'] [roperations' uoperations']
(if skip-operations? (if skip-operations?
[roperations uoperations] [roperations uoperations]
(add-update-attr-operations attr dest-shape origin-shape roperations uoperations touched text-partial-change?))] (add-update-attr-operations attr dest-shape roperations uoperations attr-val))]
(recur (next attrs) (recur (next attrs)
roperations' roperations'
uoperations'))))))) uoperations')))))))
(defn update-attrs-on-switch (defn update-attrs-on-switch
"Copy attributes that have changed in the origin shape to the dest shape. Used on variants switch" "Copy attributes that have changed in the shape previous to the switch
[changes dest-shape origin-shape dest-root origin-root origin-ref-shape container] to the current shape (post switch). Used only on variants switch"
;; NOTE: This function have similitudes but is very different to
;; update-attrs:
;; In components (update-attrs), the source shape is "clean", and the destination
;; shape may have touched elements that shouldn't be overwritten.
;; In variants (update-attrs-on-switch), the destination shape is "clean",
;; and it's the source shape that may have touched elements, and we only want
;; to copy those touched elements.
[changes current-shape previous-shape current-root prev-root origin-ref-shape container]
(let [;; We need to sync only the position relative to the origin of the component. (let [;; We need to sync only the position relative to the origin of the component.
;; (see update-attrs for a full explanation) ;; (see update-attrs for a full explanation)
origin-shape (reposition-shape origin-shape origin-root dest-root) previous-shape (reposition-shape previous-shape prev-root current-root)
touched (get dest-shape :touched #{}) touched (get previous-shape :touched #{})]
touched-origin (get origin-shape :touched #{})]
(loop [attrs updatable-attrs (loop [attrs updatable-attrs
roperations [{:type :set-touched :touched (:touched origin-shape)}] roperations [{:type :set-touched :touched (:touched previous-shape)}]
uoperations (list {:type :set-touched :touched (:touched dest-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 [attr-group (get ctk/sync-attrs attr)
skip-operations?
(or
;; If the attribute is not valid for the destiny, don't copy it
(not (cts/is-allowed-attr? attr (:type current-shape)))
;; If the values are already equal, don't copy them
(= (get previous-shape attr) (get current-shape attr))
;; If the referenced shape on the original component doesn't
;; have the same value, don't copy it
;; Exceptions: :points :selrect and :content can be different
(and
(not (contains? #{:points :selrect :content} attr))
(not= (get origin-ref-shape attr) (get current-shape attr)))
;; The :content attr cant't be copied to elements of different type
(and (= attr :content) (not= (:type previous-shape) (:type current-shape)))
;; If the attr is not touched, don't copy it
(not (touched attr-group)))
;; On texts, both text (the actual letters)
;; 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
;; only update the untouched one
text-partial-change?
(when (and
(not skip-operations?)
(cfh/text-shape? current-shape)
(cfh/text-shape? previous-shape)
(= :content attr)
(touched attr-group))
(is-text-partial-change? current-shape previous-shape))
;; position-data is a special case because can be affected by :geometry-group and :content-group
;; so, if the position-data changes but the geometry is touched we need to reset the position-data
;; so it's calculated again
reset-pos-data? (and
(not skip-operations?)
(cfh/text-shape? previous-shape)
(= attr :position-data)
(not= (:position-data previous-shape) (:position-data current-shape))
(touched :geometry-group))
attr-val (when-not skip-operations?
(cond
;; If position data changes and the geometry group is touched
;; we need to put to nil so we can regenerate it
reset-pos-data?
nil
text-partial-change?
(text-partial-change-value (:content previous-shape)
(:content current-shape)
touched)
:else
(get previous-shape attr)))
[roperations' uoperations'] [roperations' uoperations']
(if (or (if skip-operations?
;; If the attribute is not valid for the destiny, don't copy it
(not (cts/is-allowed-attr? attr (:type dest-shape)))
;; If the values are already equal, don't copy it
(= (get origin-shape attr) (get dest-shape attr))
;; If the referenced shape on the original component doesn't have the same value, don't copy it
;; Exceptions: :points :selrect and :content can be different
(and
(not (contains? #{:points :selrect :content} attr))
(not= (get origin-ref-shape attr) (get dest-shape attr)))
;; The :content attr cant't be copied to elements of different type
(and (= attr :content) (not= (:type origin-shape) (:type dest-shape)))
;; If the attr is not touched in the origin shape, don't copy it
(not (touched-origin attr-group)))
[roperations uoperations] [roperations uoperations]
(add-update-attr-operations attr dest-shape origin-shape roperations uoperations touched false))] (add-update-attr-operations attr current-shape roperations uoperations attr-val))]
(recur (next attrs) (recur (next attrs)
roperations' roperations'
uoperations')) uoperations'))
(cond-> changes (cond-> changes
(> (count roperations) 1) (> (count roperations) 1)
(add-update-attr-changes dest-shape container roperations uoperations) (add-update-attr-changes current-shape container roperations uoperations)
:always :always
(generate-update-tokens container dest-shape origin-shape touched false)))))) (generate-update-tokens container current-shape previous-shape touched false))))))
(defn- propagate-attrs (defn- propagate-attrs
"Helper that puts the origin attributes (attrs) into dest but only if "Helper that puts the origin attributes (attrs) into dest but only if

View file

@ -75,16 +75,16 @@
objects objects
(:id new-shape)) (:id new-shape))
new-shapes-map (into {} (map (juxt :shape-path identity) new-shapes-w-path)) new-shapes-map (into {} (map (juxt :shape-path identity) new-shapes-w-path))
orig-touched (filter (comp seq :touched) orig-shapes-w-path) orig-touched (filter (comp seq :touched) orig-shapes-w-path)
container (ctn/make-container page :page)] container (ctn/make-container page :page)]
(reduce (reduce
(fn [changes touched-shape] (fn [changes previous-shape]
(let [related-shape (get new-shapes-map (:shape-path touched-shape)) (let [current-shape (get new-shapes-map (:shape-path previous-shape))
orig-ref-shape (ctf/find-ref-shape nil container libraries touched-shape)] orig-ref-shape (ctf/find-ref-shape nil container libraries previous-shape)]
(if related-shape (if current-shape
(cll/update-attrs-on-switch (cll/update-attrs-on-switch
changes related-shape touched-shape new-shape original-shape orig-ref-shape container) changes current-shape previous-shape new-shape original-shape orig-ref-shape container)
changes))) changes)))
changes changes
orig-touched))) orig-touched)))