0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-15 17:21:17 -05:00

Enhance synchronization of a component with subcomponents

This commit is contained in:
Andrés Moya 2020-10-06 15:44:42 +02:00
parent 7c75b75f5b
commit 68ca44188c
8 changed files with 346 additions and 239 deletions

View file

@ -236,12 +236,8 @@
:width :size-group :width :size-group
:height :size-group :height :size-group
:proportion :size-group :proportion :size-group
:x :position-group
:y :position-group
:rx :radius-group :rx :radius-group
:ry :radius-group :ry :radius-group})
:points :points-group
:transform :transform-group})
(def color-sync-attrs [:fill-color (def color-sync-attrs [:fill-color
:stroke-color]) :stroke-color])
@ -899,14 +895,14 @@
group (get component-sync-attrs attr)] group (get component-sync-attrs attr)]
(cond-> shape (cond-> shape
(and shape-ref group (not ignore) (not= val (get shape attr)))
(update :touched #(conj (or % #{}) group))
(nil? val) (nil? val)
(dissoc attr) (dissoc attr)
(some? val) (some? val)
(assoc attr val) (assoc attr val))))
(and shape-ref group (not ignore))
(update :touched #(conj (or % #{}) group)))))
(defmethod process-operation :set-touched (defmethod process-operation :set-touched
[shape op] [shape op]

View file

@ -31,15 +31,15 @@
(update page :objects (update page :objects
#(into % (d/index-by :id objects-list)))) #(into % (d/index-by :id objects-list))))
(defn get-root-component (defn get-root-shape
"Get the root shape linked to the component for this shape, if any" "Get the root shape linked to a component for this shape, if any"
[id objects] [shape objects]
(let [obj (get objects id)] (if (:component-root? shape)
(if-let [component-id (:component-root? obj)] shape
id (if-let [parent-id (:parent-id shape)]
(if-let [parent-id (:parent-id obj)] (get-root-shape (get objects (:parent-id shape))
(get-root-component parent-id objects) objects)
nil)))) nil)))
(defn get-children (defn get-children
"Retrieve all children ids recursively for a given object" "Retrieve all children ids recursively for a given object"
@ -58,7 +58,7 @@
(defn get-object-with-children (defn get-object-with-children
"Retrieve a list with an object and all of its children" "Retrieve a list with an object and all of its children"
[id objects] [id objects]
(map #(get objects %) (concat [id] (get-children id objects)))) (map #(get objects %) (d/concat [id] (get-children id objects))))
(defn is-shape-grouped (defn is-shape-grouped
"Checks if a shape is inside a group" "Checks if a shape is inside a group"

View file

@ -1148,7 +1148,6 @@
(update [_ state] (update [_ state]
(let [page-id (:current-page-id state) (let [page-id (:current-page-id state)
objects (dwc/lookup-page-objects state page-id) objects (dwc/lookup-page-objects state page-id)
root-id (cph/get-root-component (:id shape) objects)
mdata {:position position mdata {:position position
:shape shape :shape shape

View file

@ -160,7 +160,10 @@
:val (:component-root? updated-shape)} :val (:component-root? updated-shape)}
{:type :set {:type :set
:attr :shape-ref :attr :shape-ref
:val (:shape-ref updated-shape)}]}) :val (:shape-ref updated-shape)}
{:type :set
:attr :touched
:val (:touched updated-shape)}]})
updated-shapes)) updated-shapes))
uchanges (conj uchanges uchanges (conj uchanges
@ -184,9 +187,13 @@
:val (:component-root? original-shape)} :val (:component-root? original-shape)}
{:type :set {:type :set
:attr :shape-ref :attr :shape-ref
:val (:shape-ref original-shape)}]})) :val (:shape-ref original-shape)}
{:type :set
:attr :touched
:val (:touched original-shape)}]}))
updated-shapes))] updated-shapes))]
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}) (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
(dws/select-shapes (d/ordered-set (:id group)))))))))) (dws/select-shapes (d/ordered-set (:id group))))))))))
@ -245,7 +252,8 @@
(dwc/calculate-frame-overlap all-frames $)) (dwc/calculate-frame-overlap all-frames $))
(assoc $ :parent-id (assoc $ :parent-id
(or (:parent-id $) (:frame-id $))) (or (:parent-id $) (:frame-id $)))
(assoc $ :shape-ref (:id original-shape))) (assoc $ :shape-ref (:id original-shape))
(dissoc $ :touched))
(nil? (:parent-id original-shape)) (nil? (:parent-id original-shape))
(assoc :component-id (:id original-shape) (assoc :component-id (:id original-shape)
@ -356,25 +364,35 @@
(ptk/reify ::reset-component (ptk/reify ::reset-component
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(js/console.info "##### RESET-COMPONENT of shape" (str id))
(let [page-id (:current-page-id state) (let [page-id (:current-page-id state)
page (get-in state [:workspace-data :pages-index page-id]) page (get-in state [:workspace-data :pages-index page-id])
objects (dwc/lookup-page-objects state page-id) objects (dwc/lookup-page-objects state page-id)
root-shape (get objects id) shape (get objects id)
file-id (get root-shape :component-file) file-id (get shape :component-file)
components [all-shapes component root-component]
(if (nil? file-id) (dwlh/resolve-shapes-and-components shape
(get-in state [:workspace-data :components]) objects
(get-in state [:workspace-libraries file-id :data :components])) state
true)
_ (js/console.info "shape" (:name shape) "<- component" (:name component))
_ (js/console.debug "all-shapes" (clj->js all-shapes))
_ (js/console.debug "component" (clj->js component))
_ (js/console.debug "root-component" (clj->js root-component))
[rchanges uchanges] [rchanges uchanges]
(dwlh/generate-sync-shape-and-children-components root-shape (dwlh/generate-sync-shape-and-children-components shape
objects all-shapes
components component
root-component
(:id page) (:id page)
nil nil
true)] true)]
(js/console.debug "rchanges" (clj->js rchanges))
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))) (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))
(defn update-component (defn update-component
@ -383,22 +401,32 @@
(ptk/reify ::update-component (ptk/reify ::update-component
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(js/console.info "##### UPDATE-COMPONENT of shape" (str id))
(let [page-id (:current-page-id state) (let [page-id (:current-page-id state)
objects (dwc/lookup-page-objects state page-id) objects (dwc/lookup-page-objects state page-id)
root-shape (get objects id) shape (get objects id)
file-id (get root-shape :component-file) file-id (get shape :component-file)
components [all-shapes component root-component]
(if (nil? file-id) (dwlh/resolve-shapes-and-components shape
(get-in state [:workspace-data :components]) objects
(get-in state [:workspace-libraries file-id :data :components])) state
true)
_ (js/console.info "shape" (:name shape) "-> component" (:name component))
_ (js/console.debug "all-shapes" (clj->js all-shapes))
_ (js/console.debug "component" (clj->js component))
_ (js/console.debug "root-component" (clj->js root-component))
[rchanges uchanges] [rchanges uchanges]
(dwlh/generate-sync-shape-inverse root-shape (dwlh/generate-sync-shape-inverse shape
objects all-shapes
components component
root-component
page-id)] page-id)]
(js/console.debug "rchanges" (clj->js rchanges))
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))) (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))
(declare sync-file-2nd-stage) (declare sync-file-2nd-stage)
@ -415,6 +443,7 @@
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(js/console.info "##### SYNC-FILE" (str (or file-id "local")))
(let [[rchanges1 uchanges1] (dwlh/generate-sync-file :components file-id state) (let [[rchanges1 uchanges1] (dwlh/generate-sync-file :components file-id state)
[rchanges2 uchanges2] (dwlh/generate-sync-library :components file-id state) [rchanges2 uchanges2] (dwlh/generate-sync-library :components file-id state)
[rchanges3 uchanges3] (dwlh/generate-sync-file :colors file-id state) [rchanges3 uchanges3] (dwlh/generate-sync-file :colors file-id state)
@ -423,6 +452,7 @@
[rchanges6 uchanges6] (dwlh/generate-sync-library :typographies file-id state) [rchanges6 uchanges6] (dwlh/generate-sync-library :typographies file-id state)
rchanges (d/concat rchanges1 rchanges2 rchanges3 rchanges4 rchanges5 rchanges6) rchanges (d/concat rchanges1 rchanges2 rchanges3 rchanges4 rchanges5 rchanges6)
uchanges (d/concat uchanges1 uchanges2 uchanges3 uchanges4 uchanges5 uchanges6)] uchanges (d/concat uchanges1 uchanges2 uchanges3 uchanges4 uchanges5 uchanges6)]
(js/console.debug "rchanges" (clj->js rchanges))
(rx/concat (rx/concat
(rx/of (dm/hide-tag :sync-dialog)) (rx/of (dm/hide-tag :sync-dialog))
(when rchanges (when rchanges
@ -448,11 +478,13 @@
(ptk/reify ::sync-file-2nd-stage (ptk/reify ::sync-file-2nd-stage
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(js/console.info "##### SYNC-FILE" (str (or file-id "local")) "(2nd stage)")
(let [[rchanges1 uchanges1] (dwlh/generate-sync-file :components nil state) (let [[rchanges1 uchanges1] (dwlh/generate-sync-file :components nil state)
[rchanges2 uchanges2] (dwlh/generate-sync-library :components file-id state) [rchanges2 uchanges2] (dwlh/generate-sync-library :components file-id state)
rchanges (d/concat rchanges1 rchanges2) rchanges (d/concat rchanges1 rchanges2)
uchanges (d/concat uchanges1 uchanges2)] uchanges (d/concat uchanges1 uchanges2)]
(when rchanges (when rchanges
(js/console.debug "rchanges" (clj->js rchanges))
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))) (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))))
(def ignore-sync (def ignore-sync

View file

@ -21,8 +21,10 @@
(declare generate-sync-container) (declare generate-sync-container)
(declare generate-sync-shape) (declare generate-sync-shape)
(declare has-asset-reference-fn)
(declare generate-sync-component-components) (declare get-assets)
(declare resolve-shapes-and-components)
(declare generate-sync-shape-and-children-components) (declare generate-sync-shape-and-children-components)
(declare generate-sync-shape-inverse) (declare generate-sync-shape-inverse)
(declare generate-sync-shape<-component) (declare generate-sync-shape<-component)
@ -50,7 +52,10 @@
(assoc :frame-id nil) (assoc :frame-id nil)
(nil? (:parent-id new-shape)) (nil? (:parent-id new-shape))
(assoc :component-root? true))) (dissoc :component-id
:component-file
:component-root?
:shape-ref)))
;; Make the original shape an instance of the new component. ;; Make the original shape an instance of the new component.
;; If one of the original shape children already was a component ;; If one of the original shape children already was a component
@ -58,7 +63,8 @@
update-original-shape (fn [original-shape new-shape] update-original-shape (fn [original-shape new-shape]
(cond-> original-shape (cond-> original-shape
true true
(assoc :shape-ref (:id new-shape)) (-> (assoc :shape-ref (:id new-shape))
(dissoc :touched))
(nil? (:parent-id new-shape)) (nil? (:parent-id new-shape))
(assoc :component-id (:id new-shape) (assoc :component-id (:id new-shape)
@ -96,7 +102,7 @@
(let [[page-rchanges page-uchanges] (let [[page-rchanges page-uchanges]
(generate-sync-container asset-type (generate-sync-container asset-type
library-id library-id
library-items state
page page
(:id page) (:id page)
nil)] nil)]
@ -123,7 +129,7 @@
(let [[comp-rchanges comp-uchanges] (let [[comp-rchanges comp-uchanges]
(generate-sync-container asset-type (generate-sync-container asset-type
library-id library-id
library-items state
local-component local-component
nil nil
(:id local-component))] (:id local-component))]
@ -132,7 +138,30 @@
(d/concat uchanges comp-uchanges))) (d/concat uchanges comp-uchanges)))
[rchanges uchanges]))))) [rchanges uchanges])))))
(defn has-asset-reference-fn (defn- generate-sync-container
"Generate changes to synchronize all shapes in a particular container
(a page or a component) that are linked to the given library."
[asset-type library-id state container page-id component-id]
(let [has-asset-reference? (has-asset-reference-fn asset-type library-id)
linked-shapes (cph/select-objects has-asset-reference? container)]
(loop [shapes (seq linked-shapes)
rchanges []
uchanges []]
(if-let [shape (first shapes)]
(let [[shape-rchanges shape-uchanges]
(generate-sync-shape asset-type
library-id
state
(get container :objects)
page-id
component-id
shape)]
(recur (next shapes)
(d/concat rchanges shape-rchanges)
(d/concat uchanges shape-uchanges)))
[rchanges uchanges]))))
(defn- has-asset-reference-fn
"Gets a function that checks if a shape uses some asset of the given type "Gets a function that checks if a shape uses some asset of the given type
in the given library." in the given library."
[asset-type library-id] [asset-type library-id]
@ -167,50 +196,28 @@
#(and (some? (:typography-ref-id %)) #(and (some? (:typography-ref-id %))
(= library-id (:typography-ref-file %))))))))) (= library-id (:typography-ref-file %)))))))))
(defn generate-sync-container (defmulti generate-sync-shape
"Generate changes to synchronize all shapes in a particular container "Generate changes to synchronize one shape, that use the given type
(a page or a component)." of asset of the given library."
[asset-type library-id library-items container page-id component-id] (fn [type _ _ _ _ _ _ _] type))
(let [has-asset-reference? (has-asset-reference-fn asset-type library-id)
linked-shapes (cph/select-objects has-asset-reference? container)]
(loop [shapes (seq linked-shapes)
rchanges []
uchanges []]
(if-let [shape (first shapes)]
(let [[shape-rchanges shape-uchanges]
(generate-sync-shape asset-type
library-id
library-items
(get container :objects)
page-id
component-id
shape)]
(recur (next shapes)
(d/concat rchanges shape-rchanges)
(d/concat uchanges shape-uchanges)))
[rchanges uchanges]))))
(defmulti generate-sync-shape (fn [type _ _ _ _ _ _ _] type))
(defmethod generate-sync-shape :components (defmethod generate-sync-shape :components
[_ library-id library-items objects page-id component-id shape] [_ library-id state objects page-id component-id shape]
(let [[all-shapes component root-component]
;; Synchronize a shape that is the root instance of a component, and all of its (resolve-shapes-and-components shape
;; children. All attributes of the component shape that have changed, and whose
;; group have not been touched in the linked shape, will be copied to the shape.
;; Any shape that is linked to a no-longer existent component shape will be
;; detached.
(let [root-shape shape
components library-items
reset-touched? false]
(generate-sync-shape-and-children-components root-shape
objects objects
components state
false)]
(generate-sync-shape-and-children-components shape
all-shapes
component
root-component
page-id page-id
component-id component-id
reset-touched?))) false)))
(defn generate-sync-text-shape [shape page-id component-id update-node] (defn- generate-sync-text-shape [shape page-id component-id update-node]
(let [old-content (:content shape) (let [old-content (:content shape)
new-content (ut/map-node update-node old-content) new-content (ut/map-node update-node old-content)
rchanges [(d/without-nils {:type :mod-obj rchanges [(d/without-nils {:type :mod-obj
@ -232,13 +239,14 @@
[rchanges lchanges]))) [rchanges lchanges])))
(defmethod generate-sync-shape :colors (defmethod generate-sync-shape :colors
[_ library-id library-items _ page-id component-id shape] [_ library-id state _ page-id component-id shape]
;; Synchronize a shape that uses some colors of the library. The value of the ;; Synchronize a shape that uses some colors of the library. The value of the
;; color in the library is copied to the shape. ;; color in the library is copied to the shape.
(let [colors (get-assets library-id :colors state)]
(if (= :text (:type shape)) (if (= :text (:type shape))
(let [update-node (fn [node] (let [update-node (fn [node]
(if-let [color (get library-items (:fill-color-ref-id node))] (if-let [color (get colors (:fill-color-ref-id node))]
(assoc node :fill-color (:value color)) (assoc node :fill-color (:value color))
node))] node))]
(generate-sync-text-shape shape page-id component-id update-node)) (generate-sync-text-shape shape page-id component-id update-node))
@ -265,7 +273,7 @@
(recur (next attrs) (recur (next attrs)
roperations roperations
uoperations) uoperations)
(let [color (get library-items (get shape attr-ref-id)) (let [color (get colors (get shape attr-ref-id))
roperation {:type :set roperation {:type :set
:attr attr :attr attr
:val (:value color) :val (:value color)
@ -276,15 +284,16 @@
:ignore-touched true}] :ignore-touched true}]
(recur (next attrs) (recur (next attrs)
(conj roperations roperation) (conj roperations roperation)
(conj uoperations uoperation)))))))))) (conj uoperations uoperation)))))))))))
(defmethod generate-sync-shape :typographies (defmethod generate-sync-shape :typographies
[_ library-id library-items _ page-id component-id shape] [_ library-id state _ page-id component-id shape]
;; Synchronize a shape that uses some typographies of the library. The attributes ;; Synchronize a shape that uses some typographies of the library. The attributes
;; of the typography are copied to the shape." ;; of the typography are copied to the shape."
(let [update-node (fn [node] (let [typographies (get-assets library-id :typographies state)
(if-let [typography (get library-items (:typography-ref-id node))] update-node (fn [node]
(if-let [typography (get typographies (:typography-ref-id node))]
(merge node (d/without-keys typography [:name :id])) (merge node (d/without-keys typography [:name :id]))
node))] node))]
(generate-sync-text-shape shape page-id component-id update-node))) (generate-sync-text-shape shape page-id component-id update-node)))
@ -292,16 +301,59 @@
;; ---- Component synchronization helpers ---- ;; ---- Component synchronization helpers ----
(defn- get-assets
[library-id asset-type state]
(if (nil? library-id)
(get-in state [:workspace-data asset-type])
(get-in state [:workspace-libraries library-id :data asset-type])))
(defn- get-component
[state file-id component-id]
(let [components (if (nil? file-id)
(get-in state [:workspace-data :components])
(get-in state [:workspace-libraries file-id :data :components]))]
(get components component-id)))
(defn resolve-shapes-and-components
"Get all shapes inside a component instance, and the component they are
linked with. If follow-indirection? is true, and the shape corresponding
to the root shape is also a component instance, follow the link and get
the final component."
[shape objects state follow-indirection?]
(loop [all-shapes (cph/get-object-with-children (:id shape) objects)
local-objects objects
local-shape shape]
(let [root-shape (cph/get-root-shape local-shape local-objects)
component (get-component state
(get root-shape :component-file)
(get root-shape :component-id))
component-shape (get-in component [:objects (:shape-ref local-shape)])]
(if (or (nil? (:component-id component-shape))
(not follow-indirection?))
[all-shapes component component-shape]
(let [resolve-indirection
(fn [shape]
(let [component-shape (get-in component [:objects (:shape-ref shape)])]
(-> shape
(assoc :shape-ref (:shape-ref component-shape))
(d/assoc-when :component-id (:component-id component-shape))
(d/assoc-when :component-file (:component-file component-shape)))))
new-shapes (map resolve-indirection all-shapes)]
(recur new-shapes
(:objects component)
component-shape))))))
(defn generate-sync-shape-and-children-components (defn generate-sync-shape-and-children-components
"Generate changes to synchronize one shape that is linked to a component, "Generate changes to synchronize one shape that the root of a component
and all its children. If reset-touched? is false, same considerations as instance, and all its children, from the given component.
in generate-sync-shape :components. If it's true, all attributes of the If reset? is false, all atributes of each component shape that have
component that have changed will be copied, and the 'touched' flags in changed, and whose group has not been touched in the instance shape will
the shapes will be cleared." be copied to this one.
[root-shape objects components page-id component-id reset-touched?] If reset? is true, all changed attributes will be copied and the 'touched'
(let [all-shapes (cph/get-object-with-children (:id root-shape) objects) flags in the instance shape will be cleared."
component (get components (:component-id root-shape)) [root-shape all-shapes component root-component page-id component-id reset?]
root-component (get-in component [:objects (:shape-ref root-shape)])]
(loop [shapes (seq all-shapes) (loop [shapes (seq all-shapes)
rchanges [] rchanges []
uchanges []] uchanges []]
@ -316,20 +368,20 @@
component component
page-id page-id
component-id component-id
reset-touched?)] reset?)]
(recur (next shapes) (recur (next shapes)
(d/concat rchanges shape-rchanges) (d/concat rchanges shape-rchanges)
(d/concat uchanges shape-uchanges)))))))) (d/concat uchanges shape-uchanges)))))))
(defn generate-sync-shape-inverse (defn- generate-sync-shape-inverse
"Generate changes to update the component a shape is linked to, from "Generate changes to update the component a shape is linked to, from
the values in the shape and all its children. It acts like the above the values in the shape and all its children.
function with reset-touched? as true. Also clears the 'touched' flags All atributes of each instance shape that have changed, will be copied
in the source shapes." to the component shape. Also clears the 'touched' flags in the source
[root-shape objects components page-id] shapes.
(let [all-shapes (cph/get-object-with-children (:id root-shape) objects) And if the component shapes are, in turn, instances of a second component,
component (get components (:component-id root-shape)) their 'touched' flags will be set accordingly."
root-component (get-in component [:objects (:shape-ref root-shape)])] [root-shape all-shapes component root-component page-id]
(loop [shapes (seq all-shapes) (loop [shapes (seq all-shapes)
rchanges [] rchanges []
uchanges []] uchanges []]
@ -345,12 +397,12 @@
page-id)] page-id)]
(recur (next shapes) (recur (next shapes)
(d/concat rchanges shape-rchanges) (d/concat rchanges shape-rchanges)
(d/concat uchanges shape-uchanges)))))))) (d/concat uchanges shape-uchanges)))))))
(defn generate-sync-shape<-component (defn- generate-sync-shape<-component
"Generate changes to synchronize one shape that is linked to other shape "Generate changes to synchronize one shape that is linked to other shape
inside a component. Same considerations as above about reset-touched?" inside a component. Same considerations as above about reset-touched?"
[shape root-shape root-component component page-id component-id reset-touched?] [shape root-shape root-component component page-id component-id reset?]
(if (nil? component) (if (nil? component)
(remove-component-and-ref shape page-id component-id) (remove-component-and-ref shape page-id component-id)
(let [component-shape (get (:objects component) (:shape-ref shape))] (let [component-shape (get (:objects component) (:shape-ref shape))]
@ -362,25 +414,32 @@
root-component root-component
page-id page-id
component-id component-id
reset-touched?))))) {:omit-touched? (not reset?)
:reset-touched? reset?
:set-touched? false})))))
(defn generate-sync-shape->component (defn- generate-sync-shape->component
"Generate changes to synchronize one shape inside a component, with other "Generate changes to synchronize one shape inside a component, with other
shape that is linked to it." shape that is linked to it."
[shape root-shape root-component component page-id] [shape root-shape root-component component page-id]
(js/console.log "component" (clj->js component))
(if (nil? component) (if (nil? component)
empty-changes empty-changes
(let [component-shape (get (:objects component) (:shape-ref shape))] (let [component-shape (get (:objects component) (:shape-ref shape))]
(js/console.log "component-shape" (clj->js component-shape))
(if (nil? component-shape) (if (nil? component-shape)
empty-changes empty-changes
(let [[rchanges1 uchanges1] (let [_(js/console.info "update" (:name shape) "->" (:name component-shape))
[rchanges1 uchanges1]
(update-attrs component-shape (update-attrs component-shape
shape shape
root-component root-component
root-shape root-shape
nil nil
(:id root-component) (:id root-component)
true) {:omit-touched? false
:reset-touched? false
:set-touched? true})
[rchanges2 uchanges2] [rchanges2 uchanges2]
(reset-touched shape (reset-touched shape
page-id page-id
@ -391,13 +450,16 @@
; ---- Operation generation helpers ---- ; ---- Operation generation helpers ----
(defn remove-component-and-ref (defn- remove-component-and-ref
[shape page-id component-id] [shape page-id component-id]
[[(d/without-nils {:type :mod-obj [[(d/without-nils {:type :mod-obj
:id (:id shape) :id (:id shape)
:page-id page-id :page-id page-id
:component-id component-id :component-id component-id
:operations [{:type :set :operations [{:type :set
:attr :component-root?
:val nil}
{:type :set
:attr :component-id :attr :component-id
:val nil} :val nil}
{:type :set {:type :set
@ -413,6 +475,9 @@
:page-id page-id :page-id page-id
:component-id component-id :component-id component-id
:operations [{:type :set :operations [{:type :set
:attr :component-root?
:val (:component-root? shape)}
{:type :set
:attr :component-id :attr :component-id
:val (:component-id shape)} :val (:component-id shape)}
{:type :set {:type :set
@ -424,7 +489,7 @@
{:type :set-touched {:type :set-touched
:touched (:touched shape)}]})]]) :touched (:touched shape)}]})]])
(defn remove-ref (defn- -remove-ref
[shape page-id component-id] [shape page-id component-id]
[[(d/without-nils {:type :mod-obj [[(d/without-nils {:type :mod-obj
:id (:id shape) :id (:id shape)
@ -445,7 +510,7 @@
{:type :set-touched {:type :set-touched
:touched (:touched shape)}]})]]) :touched (:touched shape)}]})]])
(defn reset-touched (defn- reset-touched
[shape page-id component-id] [shape page-id component-id]
[[(d/without-nils {:type :mod-obj [[(d/without-nils {:type :mod-obj
:id (:id shape) :id (:id shape)
@ -460,32 +525,42 @@
:operations [{:type :set-touched :operations [{:type :set-touched
:touched (:touched shape)}]})]]) :touched (:touched shape)}]})]])
(defn update-attrs (defn- update-attrs
"The main function that implements the sync algorithm." "The main function that implements the sync algorithm. Copy
[shape component-shape root-shape root-component page-id component-id reset-touched?] attributes that have changed in the origin shape to the dest shape.
If omit-touched? is true, attributes whose group has been touched
in the destination shape will be ignored.
If reset-touched? is true, the 'touched' flags will be cleared in
the dest shape.
If set-touched? is true, the corresponding 'touched' flags will be
set in dest shape if they are different than their current values."
[dest-shape origin-shape dest-root origin-root page-id component-id
{:keys [omit-touched? reset-touched? set-touched?] :as options}]
;; === Uncomment this to debug synchronization === ;; === Uncomment this to debug synchronization ===
;; (println "SYNC" ;; (println "SYNC"
;; "[C]" (:name component-shape) ;; "[C]" (:name origin-shape)
;; "->" ;; "->"
;; (if page-id "[W]" ["C"]) ;; (if page-id "[W]" ["C"])
;; (:name shape)) ;; (:name dest-shape)
;; (str options))
(let [; The position attributes need a special sync algorith, because we do (let [; The position attributes need a special sync algorith, because we do
; not synchronize the absolute position, but the position relative of ; not synchronize the absolute position, but the position relative of
; the container shape of the component. ; the container shape of the component.
new-pos (calc-new-pos shape component-shape root-shape root-component) new-pos (calc-new-pos dest-shape origin-shape dest-root origin-root)
pos-group (get cp/component-sync-attrs :x) touched (get dest-shape :touched #{})]
touched (get shape :touched #{})]
(loop [attrs (seq (keys (dissoc cp/component-sync-attrs :x :y))) (loop [attrs (seq (keys (dissoc cp/component-sync-attrs :x :y)))
roperations (if (or (not (touched pos-group)) reset-touched? true) roperations (if (or (not= (:x new-pos) (:x dest-shape))
[{:type :set :attr :x :val (:x new-pos)} ; ^ TODO: the position-group is being set (not= (:y new-pos) (:y dest-shape)))
{:type :set :attr :y :val (:y new-pos)}] ; | as touched somewhere. Investigate why. [{:type :set :attr :x :val (:x new-pos)}
{:type :set :attr :y :val (:y new-pos)}]
[]) [])
uoperations (if (or (not (touched pos-group)) reset-touched? true) uoperations (if (or (not= (:x new-pos) (:x dest-shape))
[{:type :set :attr :x :val (:x shape)} (not= (:y new-pos) (:y dest-shape)))
{:type :set :attr :y :val (:y shape)}] [{:type :set :attr :x :val (:x dest-shape)}
{:type :set :attr :y :val (:y dest-shape)}]
[])] [])]
(let [attr (first attrs)] (let [attr (first attrs)]
@ -499,51 +574,50 @@
uoperations (if reset-touched? uoperations (if reset-touched?
(conj uoperations (conj uoperations
{:type :set-touched {:type :set-touched
:touched (:touched shape)}) :touched (:touched dest-shape)})
uoperations) uoperations)
rchanges [(d/without-nils {:type :mod-obj rchanges [(d/without-nils {:type :mod-obj
:id (:id shape) :id (:id dest-shape)
:page-id page-id :page-id page-id
:component-id component-id :component-id component-id
:operations roperations})] :operations roperations})]
uchanges [(d/without-nils {:type :mod-obj uchanges [(d/without-nils {:type :mod-obj
:id (:id shape) :id (:id dest-shape)
:page-id page-id :page-id page-id
:component-id component-id :component-id component-id
:operations uoperations})]] :operations uoperations})]]
[rchanges uchanges]) [rchanges uchanges])
(if-not (contains? shape attr) (if-not (contains? dest-shape attr)
(recur (next attrs) (recur (next attrs)
roperations roperations
uoperations) uoperations)
(let [roperation {:type :set (let [roperation {:type :set
:attr attr :attr attr
:val (get component-shape attr) :val (get origin-shape attr)
:ignore-touched true} :ignore-touched (not set-touched?)}
uoperation {:type :set uoperation {:type :set
:attr attr :attr attr
:val (get shape attr) :val (get dest-shape attr)
:ignore-touched true} :ignore-touched (not set-touched?)}
attr-group (get cp/component-sync-attrs attr)] attr-group (get cp/component-sync-attrs attr)]
(if (or (not (touched attr-group)) reset-touched?) (if (and (touched attr-group) omit-touched?)
(recur (next attrs)
(conj roperations roperation)
(conj uoperations uoperation))
(recur (next attrs) (recur (next attrs)
roperations roperations
uoperations))))))))) uoperations)
(recur (next attrs)
(conj roperations roperation)
(conj uoperations uoperation))))))))))
(defn calc-new-pos (defn- calc-new-pos
[shape component-shape root-shape root-component] [dest-shape origin-shape dest-root origin-root]
(let [root-pos (gpt/point (:x root-shape) (:y root-shape)) (let [root-pos (gpt/point (:x dest-root) (:y dest-root))
root-component-pos (gpt/point (:x root-component) (:y root-component)) origin-root-pos (gpt/point (:x origin-root) (:y origin-root))
component-pos (gpt/point (:x component-shape) (:y component-shape)) origin-pos (gpt/point (:x origin-shape) (:y origin-shape))
delta (gpt/subtract component-pos root-component-pos) delta (gpt/subtract origin-pos origin-root-pos)
shape-pos (gpt/point (:x shape) (:y shape)) shape-pos (gpt/point (:x dest-shape) (:y dest-shape))
new-pos (gpt/add root-pos delta)] new-pos (gpt/add root-pos delta)]
new-pos)) new-pos))

View file

@ -106,8 +106,7 @@
(show-component [shape objects] (show-component [shape objects]
(if (nil? (:shape-ref shape)) (if (nil? (:shape-ref shape))
"" ""
(let [root-id (cph/get-root-component (:id shape) objects) (let [root-shape (cph/get-root-shape shape objects)
root-shape (when root-id (get objects root-id))
component-id (when root-shape (:component-id root-shape)) component-id (when root-shape (:component-id root-shape))
component-file-id (when root-shape (:component-file root-shape)) component-file-id (when root-shape (:component-file root-shape))
component-file (when component-file-id (get libraries component-file-id)) component-file (when component-file-id (get libraries component-file-id))
@ -118,7 +117,9 @@
component-shape (when (and component (:shape-ref shape)) component-shape (when (and component (:shape-ref shape))
(get-in component [:objects (:shape-ref shape)]))] (get-in component [:objects (:shape-ref shape)]))]
(str/format " %s--> %s%s%s" (str/format " %s--> %s%s%s"
(if (:component-root? shape) "#" "-") (cond (:component-root? shape) "#"
(:component-id shape) "@"
:else "-")
(when component-file (str/format "<%s> " (:name component-file))) (when component-file (str/format "<%s> " (:name component-file)))
(:name component-shape) (:name component-shape)
(if (or (:component-root? shape) (if (or (:component-root? shape)

View file

@ -20,6 +20,7 @@
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.libraries :as dwl]
[app.main.ui.hooks :refer [use-rxsub]] [app.main.ui.hooks :refer [use-rxsub]]
[app.main.ui.components.dropdown :refer [dropdown]])) [app.main.ui.components.dropdown :refer [dropdown]]))
@ -65,8 +66,10 @@
do-detach-component #(st/emit! (dwl/detach-component id)) do-detach-component #(st/emit! (dwl/detach-component id))
do-reset-component #(st/emit! (dwl/reset-component id)) do-reset-component #(st/emit! (dwl/reset-component id))
do-update-component #(do do-update-component #(do
(st/emit! dwc/start-undo-transaction)
(st/emit! (dwl/update-component id)) (st/emit! (dwl/update-component id))
(st/emit! (dwl/sync-file nil))) (st/emit! (dwl/sync-file nil))
(st/emit! dwc/commit-undo-transaction))
do-navigate-component-file #(st/emit! (dwl/nav-to-component-file do-navigate-component-file #(st/emit! (dwl/nav-to-component-file
(:component-file shape)))] (:component-file shape)))]
[:* [:*

View file

@ -89,7 +89,8 @@
:default-value (:name shape "")}] :default-value (:name shape "")}]
[:span.element-name [:span.element-name
{:on-double-click on-click} {:on-double-click on-click}
(:name shape "")]))) (:name shape "")
(when (seq (:touched shape)) " *")])))
(defn- make-collapsed-iref (defn- make-collapsed-iref
[id] [id]
@ -305,6 +306,7 @@
:component-id :component-id
:component-file :component-file
:shape-ref :shape-ref
:touched
:metadata])] :metadata])]
(persistent! (persistent!
(reduce-kv (fn [res id obj] (reduce-kv (fn [res id obj]