mirror of
https://github.com/penpot/penpot.git
synced 2025-03-13 16:21:57 -05:00
✨ Enhance synchronization of a component with subcomponents
This commit is contained in:
parent
7c75b75f5b
commit
68ca44188c
8 changed files with 346 additions and 239 deletions
|
@ -236,12 +236,8 @@
|
|||
:width :size-group
|
||||
:height :size-group
|
||||
:proportion :size-group
|
||||
:x :position-group
|
||||
:y :position-group
|
||||
:rx :radius-group
|
||||
:ry :radius-group
|
||||
:points :points-group
|
||||
:transform :transform-group})
|
||||
:ry :radius-group})
|
||||
|
||||
(def color-sync-attrs [:fill-color
|
||||
:stroke-color])
|
||||
|
@ -892,21 +888,21 @@
|
|||
|
||||
(defmethod process-operation :set
|
||||
[shape op]
|
||||
(let [attr (:attr op)
|
||||
val (:val op)
|
||||
ignore (:ignore-touched op)
|
||||
(let [attr (:attr op)
|
||||
val (:val op)
|
||||
ignore (:ignore-touched op)
|
||||
shape-ref (:shape-ref shape)
|
||||
group (get component-sync-attrs attr)]
|
||||
group (get component-sync-attrs attr)]
|
||||
|
||||
(cond-> shape
|
||||
(and shape-ref group (not ignore) (not= val (get shape attr)))
|
||||
(update :touched #(conj (or % #{}) group))
|
||||
|
||||
(nil? val)
|
||||
(dissoc attr)
|
||||
|
||||
(some? val)
|
||||
(assoc attr val)
|
||||
|
||||
(and shape-ref group (not ignore))
|
||||
(update :touched #(conj (or % #{}) group)))))
|
||||
(assoc attr val))))
|
||||
|
||||
(defmethod process-operation :set-touched
|
||||
[shape op]
|
||||
|
|
|
@ -31,15 +31,15 @@
|
|||
(update page :objects
|
||||
#(into % (d/index-by :id objects-list))))
|
||||
|
||||
(defn get-root-component
|
||||
"Get the root shape linked to the component for this shape, if any"
|
||||
[id objects]
|
||||
(let [obj (get objects id)]
|
||||
(if-let [component-id (:component-root? obj)]
|
||||
id
|
||||
(if-let [parent-id (:parent-id obj)]
|
||||
(get-root-component parent-id objects)
|
||||
nil))))
|
||||
(defn get-root-shape
|
||||
"Get the root shape linked to a component for this shape, if any"
|
||||
[shape objects]
|
||||
(if (:component-root? shape)
|
||||
shape
|
||||
(if-let [parent-id (:parent-id shape)]
|
||||
(get-root-shape (get objects (:parent-id shape))
|
||||
objects)
|
||||
nil)))
|
||||
|
||||
(defn get-children
|
||||
"Retrieve all children ids recursively for a given object"
|
||||
|
@ -58,7 +58,7 @@
|
|||
(defn get-object-with-children
|
||||
"Retrieve a list with an object and all of its children"
|
||||
[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
|
||||
"Checks if a shape is inside a group"
|
||||
|
|
|
@ -1148,7 +1148,6 @@
|
|||
(update [_ state]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
root-id (cph/get-root-component (:id shape) objects)
|
||||
|
||||
mdata {:position position
|
||||
:shape shape
|
||||
|
|
|
@ -160,7 +160,10 @@
|
|||
:val (:component-root? updated-shape)}
|
||||
{:type :set
|
||||
:attr :shape-ref
|
||||
:val (:shape-ref updated-shape)}]})
|
||||
:val (:shape-ref updated-shape)}
|
||||
{:type :set
|
||||
:attr :touched
|
||||
:val (:touched updated-shape)}]})
|
||||
updated-shapes))
|
||||
|
||||
uchanges (conj uchanges
|
||||
|
@ -184,9 +187,13 @@
|
|||
:val (:component-root? original-shape)}
|
||||
{:type :set
|
||||
:attr :shape-ref
|
||||
:val (:shape-ref original-shape)}]}))
|
||||
:val (:shape-ref original-shape)}
|
||||
{:type :set
|
||||
:attr :touched
|
||||
:val (:touched original-shape)}]}))
|
||||
updated-shapes))]
|
||||
|
||||
|
||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
|
||||
(dws/select-shapes (d/ordered-set (:id group))))))))))
|
||||
|
||||
|
@ -245,7 +252,8 @@
|
|||
(dwc/calculate-frame-overlap all-frames $))
|
||||
(assoc $ :parent-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))
|
||||
(assoc :component-id (:id original-shape)
|
||||
|
@ -356,25 +364,35 @@
|
|||
(ptk/reify ::reset-component
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (:current-page-id state)
|
||||
page (get-in state [:workspace-data :pages-index page-id])
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
root-shape (get objects id)
|
||||
file-id (get root-shape :component-file)
|
||||
(js/console.info "##### RESET-COMPONENT of shape" (str id))
|
||||
(let [page-id (:current-page-id state)
|
||||
page (get-in state [:workspace-data :pages-index page-id])
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
shape (get objects id)
|
||||
file-id (get shape :component-file)
|
||||
|
||||
components
|
||||
(if (nil? file-id)
|
||||
(get-in state [:workspace-data :components])
|
||||
(get-in state [:workspace-libraries file-id :data :components]))
|
||||
[all-shapes component root-component]
|
||||
(dwlh/resolve-shapes-and-components shape
|
||||
objects
|
||||
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]
|
||||
(dwlh/generate-sync-shape-and-children-components root-shape
|
||||
objects
|
||||
components
|
||||
(dwlh/generate-sync-shape-and-children-components shape
|
||||
all-shapes
|
||||
component
|
||||
root-component
|
||||
(:id page)
|
||||
nil
|
||||
true)]
|
||||
|
||||
(js/console.debug "rchanges" (clj->js rchanges))
|
||||
|
||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))
|
||||
|
||||
(defn update-component
|
||||
|
@ -383,22 +401,32 @@
|
|||
(ptk/reify ::update-component
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
root-shape (get objects id)
|
||||
file-id (get root-shape :component-file)
|
||||
(js/console.info "##### UPDATE-COMPONENT of shape" (str id))
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (dwc/lookup-page-objects state page-id)
|
||||
shape (get objects id)
|
||||
file-id (get shape :component-file)
|
||||
|
||||
components
|
||||
(if (nil? file-id)
|
||||
(get-in state [:workspace-data :components])
|
||||
(get-in state [:workspace-libraries file-id :data :components]))
|
||||
[all-shapes component root-component]
|
||||
(dwlh/resolve-shapes-and-components shape
|
||||
objects
|
||||
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]
|
||||
(dwlh/generate-sync-shape-inverse root-shape
|
||||
objects
|
||||
components
|
||||
(dwlh/generate-sync-shape-inverse shape
|
||||
all-shapes
|
||||
component
|
||||
root-component
|
||||
page-id)]
|
||||
|
||||
(js/console.debug "rchanges" (clj->js rchanges))
|
||||
|
||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))
|
||||
|
||||
(declare sync-file-2nd-stage)
|
||||
|
@ -415,6 +443,7 @@
|
|||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(js/console.info "##### SYNC-FILE" (str (or file-id "local")))
|
||||
(let [[rchanges1 uchanges1] (dwlh/generate-sync-file :components file-id state)
|
||||
[rchanges2 uchanges2] (dwlh/generate-sync-library :components 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)
|
||||
rchanges (d/concat rchanges1 rchanges2 rchanges3 rchanges4 rchanges5 rchanges6)
|
||||
uchanges (d/concat uchanges1 uchanges2 uchanges3 uchanges4 uchanges5 uchanges6)]
|
||||
(js/console.debug "rchanges" (clj->js rchanges))
|
||||
(rx/concat
|
||||
(rx/of (dm/hide-tag :sync-dialog))
|
||||
(when rchanges
|
||||
|
@ -448,11 +478,13 @@
|
|||
(ptk/reify ::sync-file-2nd-stage
|
||||
ptk/WatchEvent
|
||||
(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)
|
||||
[rchanges2 uchanges2] (dwlh/generate-sync-library :components file-id state)
|
||||
rchanges (d/concat rchanges1 rchanges2)
|
||||
uchanges (d/concat uchanges1 uchanges2)]
|
||||
(when rchanges
|
||||
(js/console.debug "rchanges" (clj->js rchanges))
|
||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))))
|
||||
|
||||
(def ignore-sync
|
||||
|
|
|
@ -21,8 +21,10 @@
|
|||
|
||||
(declare generate-sync-container)
|
||||
(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-inverse)
|
||||
(declare generate-sync-shape<-component)
|
||||
|
@ -50,7 +52,10 @@
|
|||
(assoc :frame-id nil)
|
||||
|
||||
(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.
|
||||
;; If one of the original shape children already was a component
|
||||
|
@ -58,7 +63,8 @@
|
|||
update-original-shape (fn [original-shape new-shape]
|
||||
(cond-> original-shape
|
||||
true
|
||||
(assoc :shape-ref (:id new-shape))
|
||||
(-> (assoc :shape-ref (:id new-shape))
|
||||
(dissoc :touched))
|
||||
|
||||
(nil? (:parent-id new-shape))
|
||||
(assoc :component-id (:id new-shape)
|
||||
|
@ -96,7 +102,7 @@
|
|||
(let [[page-rchanges page-uchanges]
|
||||
(generate-sync-container asset-type
|
||||
library-id
|
||||
library-items
|
||||
state
|
||||
page
|
||||
(:id page)
|
||||
nil)]
|
||||
|
@ -123,7 +129,7 @@
|
|||
(let [[comp-rchanges comp-uchanges]
|
||||
(generate-sync-container asset-type
|
||||
library-id
|
||||
library-items
|
||||
state
|
||||
local-component
|
||||
nil
|
||||
(:id local-component))]
|
||||
|
@ -132,7 +138,30 @@
|
|||
(d/concat uchanges comp-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
|
||||
in the given library."
|
||||
[asset-type library-id]
|
||||
|
@ -167,50 +196,28 @@
|
|||
#(and (some? (:typography-ref-id %))
|
||||
(= library-id (:typography-ref-file %)))))))))
|
||||
|
||||
(defn generate-sync-container
|
||||
"Generate changes to synchronize all shapes in a particular container
|
||||
(a page or a component)."
|
||||
[asset-type library-id library-items 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
|
||||
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))
|
||||
(defmulti generate-sync-shape
|
||||
"Generate changes to synchronize one shape, that use the given type
|
||||
of asset of the given library."
|
||||
(fn [type _ _ _ _ _ _ _] type))
|
||||
|
||||
(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]
|
||||
(resolve-shapes-and-components shape
|
||||
objects
|
||||
state
|
||||
false)]
|
||||
|
||||
;; Synchronize a shape that is the root instance of a component, and all of its
|
||||
;; 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
|
||||
components
|
||||
(generate-sync-shape-and-children-components shape
|
||||
all-shapes
|
||||
component
|
||||
root-component
|
||||
page-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)
|
||||
new-content (ut/map-node update-node old-content)
|
||||
rchanges [(d/without-nils {:type :mod-obj
|
||||
|
@ -232,59 +239,61 @@
|
|||
[rchanges lchanges])))
|
||||
|
||||
(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
|
||||
;; color in the library is copied to the shape.
|
||||
(if (= :text (:type shape))
|
||||
(let [update-node (fn [node]
|
||||
(if-let [color (get library-items (:fill-color-ref-id node))]
|
||||
(assoc node :fill-color (:value color))
|
||||
node))]
|
||||
(generate-sync-text-shape shape page-id component-id update-node))
|
||||
(loop [attrs (seq cp/color-sync-attrs)
|
||||
roperations []
|
||||
uoperations []]
|
||||
(let [attr (first attrs)]
|
||||
(if (nil? attr)
|
||||
(if (empty? roperations)
|
||||
empty-changes
|
||||
(let [rchanges [(d/without-nils {:type :mod-obj
|
||||
:page-id page-id
|
||||
:component-id component-id
|
||||
:id (:id shape)
|
||||
:operations roperations})]
|
||||
uchanges [(d/without-nils {:type :mod-obj
|
||||
:page-id page-id
|
||||
:component-id component-id
|
||||
:id (:id shape)
|
||||
:operations uoperations})]]
|
||||
[rchanges uchanges]))
|
||||
(let [attr-ref-id (keyword (str (name attr) "-ref-id"))]
|
||||
(if-not (contains? shape attr-ref-id)
|
||||
(recur (next attrs)
|
||||
roperations
|
||||
uoperations)
|
||||
(let [color (get library-items (get shape attr-ref-id))
|
||||
roperation {:type :set
|
||||
:attr attr
|
||||
:val (:value color)
|
||||
:ignore-touched true}
|
||||
uoperation {:type :set
|
||||
:attr attr
|
||||
:val (get shape attr)
|
||||
:ignore-touched true}]
|
||||
(let [colors (get-assets library-id :colors state)]
|
||||
(if (= :text (:type shape))
|
||||
(let [update-node (fn [node]
|
||||
(if-let [color (get colors (:fill-color-ref-id node))]
|
||||
(assoc node :fill-color (:value color))
|
||||
node))]
|
||||
(generate-sync-text-shape shape page-id component-id update-node))
|
||||
(loop [attrs (seq cp/color-sync-attrs)
|
||||
roperations []
|
||||
uoperations []]
|
||||
(let [attr (first attrs)]
|
||||
(if (nil? attr)
|
||||
(if (empty? roperations)
|
||||
empty-changes
|
||||
(let [rchanges [(d/without-nils {:type :mod-obj
|
||||
:page-id page-id
|
||||
:component-id component-id
|
||||
:id (:id shape)
|
||||
:operations roperations})]
|
||||
uchanges [(d/without-nils {:type :mod-obj
|
||||
:page-id page-id
|
||||
:component-id component-id
|
||||
:id (:id shape)
|
||||
:operations uoperations})]]
|
||||
[rchanges uchanges]))
|
||||
(let [attr-ref-id (keyword (str (name attr) "-ref-id"))]
|
||||
(if-not (contains? shape attr-ref-id)
|
||||
(recur (next attrs)
|
||||
(conj roperations roperation)
|
||||
(conj uoperations uoperation))))))))))
|
||||
roperations
|
||||
uoperations)
|
||||
(let [color (get colors (get shape attr-ref-id))
|
||||
roperation {:type :set
|
||||
:attr attr
|
||||
:val (:value color)
|
||||
:ignore-touched true}
|
||||
uoperation {:type :set
|
||||
:attr attr
|
||||
:val (get shape attr)
|
||||
:ignore-touched true}]
|
||||
(recur (next attrs)
|
||||
(conj roperations roperation)
|
||||
(conj uoperations uoperation)))))))))))
|
||||
|
||||
(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
|
||||
;; of the typography are copied to the shape."
|
||||
(let [update-node (fn [node]
|
||||
(if-let [typography (get library-items (:typography-ref-id node))]
|
||||
(let [typographies (get-assets library-id :typographies state)
|
||||
update-node (fn [node]
|
||||
(if-let [typography (get typographies (:typography-ref-id node))]
|
||||
(merge node (d/without-keys typography [:name :id]))
|
||||
node))]
|
||||
(generate-sync-text-shape shape page-id component-id update-node)))
|
||||
|
@ -292,65 +301,108 @@
|
|||
|
||||
;; ---- 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
|
||||
"Generate changes to synchronize one shape that is linked to a component,
|
||||
and all its children. If reset-touched? is false, same considerations as
|
||||
in generate-sync-shape :components. If it's true, all attributes of the
|
||||
component that have changed will be copied, and the 'touched' flags in
|
||||
the shapes will be cleared."
|
||||
[root-shape objects components page-id component-id reset-touched?]
|
||||
(let [all-shapes (cph/get-object-with-children (:id root-shape) objects)
|
||||
component (get components (:component-id root-shape))
|
||||
root-component (get-in component [:objects (:shape-ref root-shape)])]
|
||||
(loop [shapes (seq all-shapes)
|
||||
rchanges []
|
||||
uchanges []]
|
||||
(let [shape (first shapes)]
|
||||
(if (nil? shape)
|
||||
[rchanges uchanges]
|
||||
(let [[shape-rchanges shape-uchanges]
|
||||
(generate-sync-shape<-component
|
||||
shape
|
||||
root-shape
|
||||
root-component
|
||||
component
|
||||
page-id
|
||||
component-id
|
||||
reset-touched?)]
|
||||
(recur (next shapes)
|
||||
(d/concat rchanges shape-rchanges)
|
||||
(d/concat uchanges shape-uchanges))))))))
|
||||
"Generate changes to synchronize one shape that the root of a component
|
||||
instance, and all its children, from the given component.
|
||||
If reset? is false, all atributes of each component shape that have
|
||||
changed, and whose group has not been touched in the instance shape will
|
||||
be copied to this one.
|
||||
If reset? is true, all changed attributes will be copied and the 'touched'
|
||||
flags in the instance shape will be cleared."
|
||||
[root-shape all-shapes component root-component page-id component-id reset?]
|
||||
(loop [shapes (seq all-shapes)
|
||||
rchanges []
|
||||
uchanges []]
|
||||
(let [shape (first shapes)]
|
||||
(if (nil? shape)
|
||||
[rchanges uchanges]
|
||||
(let [[shape-rchanges shape-uchanges]
|
||||
(generate-sync-shape<-component
|
||||
shape
|
||||
root-shape
|
||||
root-component
|
||||
component
|
||||
page-id
|
||||
component-id
|
||||
reset?)]
|
||||
(recur (next shapes)
|
||||
(d/concat rchanges shape-rchanges)
|
||||
(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
|
||||
the values in the shape and all its children. It acts like the above
|
||||
function with reset-touched? as true. Also clears the 'touched' flags
|
||||
in the source shapes."
|
||||
[root-shape objects components page-id]
|
||||
(let [all-shapes (cph/get-object-with-children (:id root-shape) objects)
|
||||
component (get components (:component-id root-shape))
|
||||
root-component (get-in component [:objects (:shape-ref root-shape)])]
|
||||
(loop [shapes (seq all-shapes)
|
||||
rchanges []
|
||||
uchanges []]
|
||||
(let [shape (first shapes)]
|
||||
(if (nil? shape)
|
||||
[rchanges uchanges]
|
||||
(let [[shape-rchanges shape-uchanges]
|
||||
(generate-sync-shape->component
|
||||
shape
|
||||
root-shape
|
||||
root-component
|
||||
component
|
||||
page-id)]
|
||||
(recur (next shapes)
|
||||
(d/concat rchanges shape-rchanges)
|
||||
(d/concat uchanges shape-uchanges))))))))
|
||||
the values in the shape and all its children.
|
||||
All atributes of each instance shape that have changed, will be copied
|
||||
to the component shape. Also clears the 'touched' flags in the source
|
||||
shapes.
|
||||
And if the component shapes are, in turn, instances of a second component,
|
||||
their 'touched' flags will be set accordingly."
|
||||
[root-shape all-shapes component root-component page-id]
|
||||
(loop [shapes (seq all-shapes)
|
||||
rchanges []
|
||||
uchanges []]
|
||||
(let [shape (first shapes)]
|
||||
(if (nil? shape)
|
||||
[rchanges uchanges]
|
||||
(let [[shape-rchanges shape-uchanges]
|
||||
(generate-sync-shape->component
|
||||
shape
|
||||
root-shape
|
||||
root-component
|
||||
component
|
||||
page-id)]
|
||||
(recur (next shapes)
|
||||
(d/concat rchanges shape-rchanges)
|
||||
(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
|
||||
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)
|
||||
(remove-component-and-ref shape page-id component-id)
|
||||
(let [component-shape (get (:objects component) (:shape-ref shape))]
|
||||
|
@ -362,25 +414,32 @@
|
|||
root-component
|
||||
page-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
|
||||
shape that is linked to it."
|
||||
[shape root-shape root-component component page-id]
|
||||
(js/console.log "component" (clj->js component))
|
||||
(if (nil? component)
|
||||
empty-changes
|
||||
(let [component-shape (get (:objects component) (:shape-ref shape))]
|
||||
(js/console.log "component-shape" (clj->js component-shape))
|
||||
(if (nil? component-shape)
|
||||
empty-changes
|
||||
(let [[rchanges1 uchanges1]
|
||||
(let [_(js/console.info "update" (:name shape) "->" (:name component-shape))
|
||||
[rchanges1 uchanges1]
|
||||
(update-attrs component-shape
|
||||
shape
|
||||
root-component
|
||||
root-shape
|
||||
nil
|
||||
(:id root-component)
|
||||
true)
|
||||
{:omit-touched? false
|
||||
:reset-touched? false
|
||||
:set-touched? true})
|
||||
[rchanges2 uchanges2]
|
||||
(reset-touched shape
|
||||
page-id
|
||||
|
@ -391,13 +450,16 @@
|
|||
|
||||
; ---- Operation generation helpers ----
|
||||
|
||||
(defn remove-component-and-ref
|
||||
(defn- remove-component-and-ref
|
||||
[shape page-id component-id]
|
||||
[[(d/without-nils {:type :mod-obj
|
||||
:id (:id shape)
|
||||
:page-id page-id
|
||||
:component-id component-id
|
||||
:operations [{:type :set
|
||||
:attr :component-root?
|
||||
:val nil}
|
||||
{:type :set
|
||||
:attr :component-id
|
||||
:val nil}
|
||||
{:type :set
|
||||
|
@ -413,6 +475,9 @@
|
|||
:page-id page-id
|
||||
:component-id component-id
|
||||
:operations [{:type :set
|
||||
:attr :component-root?
|
||||
:val (:component-root? shape)}
|
||||
{:type :set
|
||||
:attr :component-id
|
||||
:val (:component-id shape)}
|
||||
{:type :set
|
||||
|
@ -424,7 +489,7 @@
|
|||
{:type :set-touched
|
||||
:touched (:touched shape)}]})]])
|
||||
|
||||
(defn remove-ref
|
||||
(defn- -remove-ref
|
||||
[shape page-id component-id]
|
||||
[[(d/without-nils {:type :mod-obj
|
||||
:id (:id shape)
|
||||
|
@ -445,7 +510,7 @@
|
|||
{:type :set-touched
|
||||
:touched (:touched shape)}]})]])
|
||||
|
||||
(defn reset-touched
|
||||
(defn- reset-touched
|
||||
[shape page-id component-id]
|
||||
[[(d/without-nils {:type :mod-obj
|
||||
:id (:id shape)
|
||||
|
@ -460,32 +525,42 @@
|
|||
:operations [{:type :set-touched
|
||||
:touched (:touched shape)}]})]])
|
||||
|
||||
(defn update-attrs
|
||||
"The main function that implements the sync algorithm."
|
||||
[shape component-shape root-shape root-component page-id component-id reset-touched?]
|
||||
(defn- update-attrs
|
||||
"The main function that implements the sync algorithm. Copy
|
||||
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 ===
|
||||
;; (println "SYNC"
|
||||
;; "[C]" (:name component-shape)
|
||||
;; "[C]" (:name origin-shape)
|
||||
;; "->"
|
||||
;; (if page-id "[W]" ["C"])
|
||||
;; (:name shape))
|
||||
;; (:name dest-shape)
|
||||
;; (str options))
|
||||
|
||||
(let [; The position attributes need a special sync algorith, because we do
|
||||
; not synchronize the absolute position, but the position relative of
|
||||
; the container shape of the component.
|
||||
new-pos (calc-new-pos shape component-shape root-shape root-component)
|
||||
pos-group (get cp/component-sync-attrs :x)
|
||||
touched (get shape :touched #{})]
|
||||
new-pos (calc-new-pos dest-shape origin-shape dest-root origin-root)
|
||||
touched (get dest-shape :touched #{})]
|
||||
|
||||
(loop [attrs (seq (keys (dissoc cp/component-sync-attrs :x :y)))
|
||||
roperations (if (or (not (touched pos-group)) reset-touched? true)
|
||||
[{:type :set :attr :x :val (:x new-pos)} ; ^ TODO: the position-group is being set
|
||||
{:type :set :attr :y :val (:y new-pos)}] ; | as touched somewhere. Investigate why.
|
||||
roperations (if (or (not= (:x new-pos) (:x dest-shape))
|
||||
(not= (:y new-pos) (:y dest-shape)))
|
||||
[{: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)
|
||||
[{:type :set :attr :x :val (:x shape)}
|
||||
{:type :set :attr :y :val (:y shape)}]
|
||||
uoperations (if (or (not= (:x new-pos) (:x dest-shape))
|
||||
(not= (:y new-pos) (:y dest-shape)))
|
||||
[{:type :set :attr :x :val (:x dest-shape)}
|
||||
{:type :set :attr :y :val (:y dest-shape)}]
|
||||
[])]
|
||||
|
||||
(let [attr (first attrs)]
|
||||
|
@ -499,51 +574,50 @@
|
|||
uoperations (if reset-touched?
|
||||
(conj uoperations
|
||||
{:type :set-touched
|
||||
:touched (:touched shape)})
|
||||
:touched (:touched dest-shape)})
|
||||
uoperations)
|
||||
|
||||
rchanges [(d/without-nils {:type :mod-obj
|
||||
:id (:id shape)
|
||||
:id (:id dest-shape)
|
||||
:page-id page-id
|
||||
:component-id component-id
|
||||
:operations roperations})]
|
||||
uchanges [(d/without-nils {:type :mod-obj
|
||||
:id (:id shape)
|
||||
:id (:id dest-shape)
|
||||
:page-id page-id
|
||||
:component-id component-id
|
||||
:operations uoperations})]]
|
||||
[rchanges uchanges])
|
||||
|
||||
(if-not (contains? shape attr)
|
||||
(if-not (contains? dest-shape attr)
|
||||
(recur (next attrs)
|
||||
roperations
|
||||
uoperations)
|
||||
(let [roperation {:type :set
|
||||
:attr attr
|
||||
:val (get component-shape attr)
|
||||
:ignore-touched true}
|
||||
:val (get origin-shape attr)
|
||||
:ignore-touched (not set-touched?)}
|
||||
uoperation {:type :set
|
||||
:attr attr
|
||||
:val (get shape attr)
|
||||
:ignore-touched true}
|
||||
:val (get dest-shape attr)
|
||||
:ignore-touched (not set-touched?)}
|
||||
|
||||
attr-group (get cp/component-sync-attrs attr)]
|
||||
(if (or (not (touched attr-group)) reset-touched?)
|
||||
(recur (next attrs)
|
||||
(conj roperations roperation)
|
||||
(conj uoperations uoperation))
|
||||
(if (and (touched attr-group) omit-touched?)
|
||||
(recur (next attrs)
|
||||
roperations
|
||||
uoperations)))))))))
|
||||
uoperations)
|
||||
(recur (next attrs)
|
||||
(conj roperations roperation)
|
||||
(conj uoperations uoperation))))))))))
|
||||
|
||||
(defn calc-new-pos
|
||||
[shape component-shape root-shape root-component]
|
||||
(let [root-pos (gpt/point (:x root-shape) (:y root-shape))
|
||||
root-component-pos (gpt/point (:x root-component) (:y root-component))
|
||||
component-pos (gpt/point (:x component-shape) (:y component-shape))
|
||||
delta (gpt/subtract component-pos root-component-pos)
|
||||
shape-pos (gpt/point (:x shape) (:y shape))
|
||||
new-pos (gpt/add root-pos delta)]
|
||||
(defn- calc-new-pos
|
||||
[dest-shape origin-shape dest-root origin-root]
|
||||
(let [root-pos (gpt/point (:x dest-root) (:y dest-root))
|
||||
origin-root-pos (gpt/point (:x origin-root) (:y origin-root))
|
||||
origin-pos (gpt/point (:x origin-shape) (:y origin-shape))
|
||||
delta (gpt/subtract origin-pos origin-root-pos)
|
||||
shape-pos (gpt/point (:x dest-shape) (:y dest-shape))
|
||||
new-pos (gpt/add root-pos delta)]
|
||||
new-pos))
|
||||
|
||||
|
||||
|
|
|
@ -106,8 +106,7 @@
|
|||
(show-component [shape objects]
|
||||
(if (nil? (:shape-ref shape))
|
||||
""
|
||||
(let [root-id (cph/get-root-component (:id shape) objects)
|
||||
root-shape (when root-id (get objects root-id))
|
||||
(let [root-shape (cph/get-root-shape shape objects)
|
||||
component-id (when root-shape (:component-id root-shape))
|
||||
component-file-id (when root-shape (:component-file root-shape))
|
||||
component-file (when component-file-id (get libraries component-file-id))
|
||||
|
@ -118,7 +117,9 @@
|
|||
component-shape (when (and component (:shape-ref shape))
|
||||
(get-in component [:objects (:shape-ref shape)]))]
|
||||
(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)))
|
||||
(:name component-shape)
|
||||
(if (or (:component-root? shape)
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.ui.hooks :refer [use-rxsub]]
|
||||
[app.main.ui.components.dropdown :refer [dropdown]]))
|
||||
|
@ -65,8 +66,10 @@
|
|||
do-detach-component #(st/emit! (dwl/detach-component id))
|
||||
do-reset-component #(st/emit! (dwl/reset-component id))
|
||||
do-update-component #(do
|
||||
(st/emit! dwc/start-undo-transaction)
|
||||
(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
|
||||
(:component-file shape)))]
|
||||
[:*
|
||||
|
|
|
@ -89,7 +89,8 @@
|
|||
:default-value (:name shape "")}]
|
||||
[:span.element-name
|
||||
{:on-double-click on-click}
|
||||
(:name shape "")])))
|
||||
(:name shape "")
|
||||
(when (seq (:touched shape)) " *")])))
|
||||
|
||||
(defn- make-collapsed-iref
|
||||
[id]
|
||||
|
@ -305,6 +306,7 @@
|
|||
:component-id
|
||||
:component-file
|
||||
:shape-ref
|
||||
:touched
|
||||
:metadata])]
|
||||
(persistent!
|
||||
(reduce-kv (fn [res id obj]
|
||||
|
|
Loading…
Add table
Reference in a new issue