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

Merge pull request #354 from uxbox/task/732/update-subcomponent

Task/732/update subcomponent
This commit is contained in:
Andrey Antukh 2020-10-15 11:20:40 +02:00 committed by GitHub
commit 3e14393c97
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 504 additions and 324 deletions

View file

@ -46,6 +46,7 @@
(<= % max-safe-int)))
(s/def ::component-id uuid?)
(s/def ::component-file uuid?)
(s/def ::component-root? boolean?)
(s/def ::shape-ref uuid?)
(s/def ::safe-number
@ -122,7 +123,6 @@
(s/def :internal.shape/line-height ::safe-number)
(s/def :internal.shape/locked boolean?)
(s/def :internal.shape/page-id uuid?)
(s/def :internal.shape/component-id uuid?)
(s/def :internal.shape/proportion ::safe-number)
(s/def :internal.shape/proportion-lock boolean?)
(s/def :internal.shape/rx ::safe-number)
@ -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])
@ -255,6 +251,7 @@
(s/keys :opt-un [::id
::component-id
::component-file
::component-root?
::shape-ref])))
(s/def :internal.page/objects (s/map-of uuid? ::shape))
@ -891,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]

View file

@ -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-id 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 %) (cons id (get-children id objects))))
(defn is-shape-grouped
"Checks if a shape is inside a group"

View file

@ -1148,12 +1148,9 @@
(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)
root-shape (get objects root-id)
mdata {:position position
:shape shape
:root-shape root-shape
:selected (get-in state [:workspace-local :selected])}]
(-> state
(assoc-in [:workspace-local :context-menu] mdata))))

View file

@ -155,9 +155,15 @@
{:type :set
:attr :component-file
:val nil}
{:type :set
:attr :component-root?
: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
@ -176,11 +182,18 @@
{:type :set
:attr :component-file
:val (:component-file original-shape)}
{:type :set
:attr :component-root?
: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))))))))))
@ -239,10 +252,12 @@
(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))
(assoc :component-id (:id original-shape)
:component-root? true)
(and (nil? (:parent-id original-shape)) (some? file-id))
(assoc :component-file file-id)
@ -251,7 +266,7 @@
(dissoc :component-file)
(some? (:parent-id original-shape))
(dissoc :component-id :component-file))))
(dissoc :component-root?))))
[new-shape new-shapes _]
(cph/clone-object component-shape
@ -285,9 +300,7 @@
(watch [_ state stream]
(let [page-id (:current-page-id state)
objects (dwc/lookup-page-objects state page-id)
root-id (cph/get-root-component id objects)
shapes (cph/get-object-with-children root-id objects)
shapes (cph/get-object-with-children id objects)
rchanges (map (fn [obj]
{:type :mod-obj
@ -351,26 +364,38 @@
(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-id (cph/get-root-component id objects)
root-shape (get objects id)
file-id (get root-shape :component-file)
;; ===== Uncomment this to debug =====
;; (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)
;; ===== Uncomment this to debug =====
;; _ (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)]
;; ===== Uncomment this to debug =====
;; (js/console.debug "rchanges" (clj->js rchanges))
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))
(defn update-component
@ -379,60 +404,34 @@
(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-id (cph/get-root-component id objects)
root-shape (get objects id)
;; ===== Uncomment this to debug =====
;; (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)
component-id (get root-shape :component-id)
component-objs (dwc/lookup-component-objects state component-id)
component-obj (get component-objs component-id)
[all-shapes component root-component]
(dwlh/resolve-shapes-and-components shape
objects
state
true)
;; Clone again the original shape and its children, maintaing
;; the ids of the cloned shapes. If the original shape has some
;; new child shapes, the cloned ones will have new generated ids.
update-new-shape (fn [new-shape original-shape]
(cond-> new-shape
true
(assoc :frame-id nil)
;; ===== Uncomment this to debug =====
;; _ (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))
(= (:component-id original-shape) component-id)
(dissoc :component-id)
[rchanges uchanges]
(dwlh/generate-sync-shape-inverse shape
all-shapes
component
root-component
page-id)]
(some? (:shape-ref original-shape))
(assoc :id (:shape-ref original-shape))))
touch-shape (fn [original-shape _]
(into {} original-shape))
[new-shape new-shapes original-shapes]
(cph/clone-object root-shape nil objects update-new-shape touch-shape)
rchanges (d/concat
[{:type :mod-component
:id component-id
:name (:name new-shape)
:shapes new-shapes}]
(map (fn [shape]
{:type :mod-obj
:page-id page-id
:id (:id shape)
:operations [{:type :set-touched
:touched nil}]})
original-shapes))
uchanges (d/concat
[{:type :mod-component
:id component-id
:name (:name component-obj)
:shapes (vals component-objs)}]
(map (fn [shape]
{:type :mod-obj
:page-id page-id
:id (:id shape)
:operations [{:type :set-touched
:touched (:touched shape)}]})
original-shapes))]
;; ===== Uncomment this to debug =====
;; (js/console.debug "rchanges" (clj->js rchanges))
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))
@ -450,6 +449,8 @@
ptk/WatchEvent
(watch [_ state stream]
;; ===== Uncomment this to debug =====
;; (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)
@ -458,6 +459,8 @@
[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)]
;; ===== Uncomment this to debug =====
;; (js/console.debug "rchanges" (clj->js rchanges))
(rx/concat
(rx/of (dm/hide-tag :sync-dialog))
(when rchanges
@ -483,8 +486,15 @@
(ptk/reify ::sync-file-2nd-stage
ptk/WatchEvent
(watch [_ state stream]
(let [[rchanges uchanges] (dwlh/generate-sync-file :components nil state)]
;; ===== Uncomment this to debug =====
;; (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
;; ===== Uncomment this to debug =====
;; (js/console.debug "rchanges" (clj->js rchanges))
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))))
(def ignore-sync

View file

@ -21,15 +21,62 @@
(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-components)
(declare generate-sync-shape-inverse)
(declare generate-sync-shape<-component)
(declare generate-sync-shape->component)
(declare remove-component-and-ref)
(declare remove-ref)
(declare reset-touched)
(declare update-attrs)
(declare calc-new-pos)
;; ---- Create a new component ----
(defn make-component-shape
"Clone the shape and all children. Generate new ids and detach
from parent and frame. Update the original shapes to have links
to the new ones."
[shape objects]
(assert (nil? (:component-id shape)))
(assert (nil? (:component-file shape)))
(assert (nil? (:shape-ref shape)))
(let [update-new-shape (fn [new-shape original-shape]
(cond-> new-shape
true
(assoc :frame-id nil)
(nil? (:parent-id new-shape))
(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
;; instance, the 'instanceness' is copied into the new component.
update-original-shape (fn [original-shape new-shape]
(cond-> original-shape
true
(-> (assoc :shape-ref (:id new-shape))
(dissoc :touched))
(nil? (:parent-id new-shape))
(assoc :component-id (:id new-shape)
:component-file nil
:component-root? true)
(some? (:parent-id new-shape))
(dissoc :component-root?)))]
(cph/clone-object shape nil objects update-new-shape update-original-shape)))
;; ---- General library synchronization functions ----
(defn generate-sync-file
@ -55,7 +102,7 @@
(let [[page-rchanges page-uchanges]
(generate-sync-container asset-type
library-id
library-items
state
page
(:id page)
nil)]
@ -82,7 +129,7 @@
(let [[comp-rchanges comp-uchanges]
(generate-sync-container asset-type
library-id
library-items
state
local-component
nil
(:id local-component))]
@ -91,13 +138,36 @@
(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]
(case asset-type
:components
(fn [shape] (and (some? (:component-id shape))
(fn [shape] (and (:component-root? shape)
(= (:component-file shape) library-id)))
:colors
@ -126,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
@ -191,128 +239,170 @@
[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)))
;; ---- Create a new component ----
(defn make-component-shape
"Clone the shape and all children. Generate new ids and detach
from parent and frame. Update the original shapes to have links
to the new ones."
[shape objects]
(let [update-new-shape (fn [new-shape original-shape]
(assoc new-shape :frame-id nil))
;; If one of the original shape children already was a component
;; instance, the 'instanceness' is copied into the new component,
;; and the original shape now points to the new component.
update-original-shape (fn [original-shape new-shape]
(cond-> original-shape
true
(assoc :shape-ref (:id new-shape))
(nil? (:parent-id new-shape))
(assoc :component-id (:id new-shape)
:component-file nil)
(some? (:parent-id new-shape))
(assoc :component-id nil
:component-file nil)))]
(cph/clone-object shape nil objects update-new-shape update-original-shape)))
;; ---- Component synchronization helpers ----
(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-components
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))))))))
(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 generate-sync-shape-components
(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 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
"Generate changes to update the component a shape is linked to, from
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
"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))]
@ -324,15 +414,55 @@
root-component
page-id
component-id
reset-touched?)))))
{:omit-touched? (not reset?)
:reset-touched? reset?
:set-touched? false})))))
(defn remove-component-and-ref
(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]
;; ===== Uncomment this to debug =====
;; (js/console.log "component" (clj->js component))
(if (nil? component)
empty-changes
(let [component-shape (get (:objects component) (:shape-ref shape))]
;; ===== Uncomment this to debug =====
;; (js/console.log "component-shape" (clj->js component-shape))
(if (nil? component-shape)
empty-changes
(let [;; ===== Uncomment this to debug =====
;; _(js/console.info "update" (:name shape) "->" (:name component-shape))
[rchanges1 uchanges1]
(update-attrs component-shape
shape
root-component
root-shape
nil
(:id root-component)
{:omit-touched? false
:reset-touched? false
:set-touched? true})
[rchanges2 uchanges2]
(reset-touched shape
page-id
nil)]
[(d/concat rchanges1 rchanges2)
(d/concat uchanges2 uchanges2)])))))
; ---- Operation generation helpers ----
(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
@ -348,6 +478,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
@ -359,7 +492,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)
@ -380,32 +513,57 @@
{: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- reset-touched
[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-touched
:touched nil}]})]
[(d/without-nils {:type :mod-obj
:id (:id shape)
:page-id page-id
:component-id component-id
:operations [{:type :set-touched
:touched (:touched shape)}]})]])
(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)]
@ -419,51 +577,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))

View file

@ -89,7 +89,8 @@
(letfn [(show-shape [shape-id level objects]
(let [shape (get objects shape-id)]
(println (str/pad (str (str/repeat " " level)
(:name shape))
(:name shape)
(when (seq (:touched shape)) "*")
{:length 20
:type :right})
(show-component shape objects))
@ -102,24 +103,36 @@
(show-shape shape-id (inc level) objects))))))
(show-component [shape objects]
(let [root-id (cph/get-root-component (:id shape) objects)
root-shape (when root-id (get objects root-id))
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))
shape-ref (:shape-ref shape)
component (when component-id
(if component-file
(get-in component-file [:data :components component-id])
(get components component-id)))
component-shape (when (and component shape-ref)
(get-in component [:objects shape-ref]))]
(if component-shape
(str/format " %s--> %s%s"
(if (:component-id shape) "#" "-")
(if (nil? (:shape-ref shape))
""
(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))
component (when component-id
(if component-file
(get-in component-file [:data :components component-id])
(get components component-id)))
component-shape (when (and component (:shape-ref shape))
(get-in component [:objects (:shape-ref shape)]))]
(str/format " %s--> %s%s%s"
(cond (:component-root? shape) "#"
(:component-id shape) "@"
:else "-")
(when component-file (str/format "<%s> " (:name component-file)))
(:name component-shape))
"")))]
(:name component-shape)
(if (or (:component-root? shape)
(nil? (:component-id shape)))
""
(let [component-id (:component-id shape)
component-file-id (:component-file shape)
component-file (when component-file-id (get libraries component-file-id))
component (if component-file
(get-in component-file [:data :components component-id])
(get components component-id))]
(str/format " (%s%s)"
(when component-file (str/format "<%s> " (:name component-file)))
(:name component))))))))]
(println "[Workspace]")
(show-shape (:id root) 0 objects)

View file

@ -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]]))
@ -46,7 +47,6 @@
[{:keys [mdata] :as props}]
(let [{:keys [id] :as shape} (:shape mdata)
selected (:selected mdata)
root-shape (:root-shape mdata)
do-duplicate #(st/emit! dw/duplicate-selected)
do-delete #(st/emit! dw/delete-selected)
@ -66,10 +66,12 @@
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 root-shape)))]
(:component-file shape)))]
[:*
[:& menu-entry {:title "Copy"
:shortcut "Ctrl + c"
@ -117,28 +119,30 @@
[:& menu-entry {:title "Lock"
:on-click do-lock-shape}])
[:& menu-separator]
(if (nil? (:shape-ref shape))
[:& menu-entry {:title "Create component"
:shortcut "Ctrl + K"
:on-click do-add-component}]
(when (nil? (:shape-ref shape))
[:*
[:& menu-entry {:title "Detach instance"
:on-click do-detach-component}]
[:& menu-entry {:title "Reset overrides"
:on-click do-reset-component}]
(if (nil? (:component-file root-shape))
[:& menu-entry {:title "Update master component"
:on-click do-update-component}]
[:& menu-entry {:title "Go to master component file"
:on-click do-navigate-component-file}])])
[:& menu-separator]
[:& menu-entry {:title "Create component"
:shortcut "Ctrl + K"
:on-click do-add-component}]])
(when (:component-id shape)
[:*
[:& menu-separator]
[:& menu-entry {:title "Detach instance"
:on-click do-detach-component}]
[:& menu-entry {:title "Reset overrides"
:on-click do-reset-component}]
(if (nil? (:component-file shape))
[:& menu-entry {:title "Update master component"
:on-click do-update-component}]
[:& menu-entry {:title "Go to master component file"
:on-click do-navigate-component-file}])])
[:& menu-separator]
[:& menu-entry {:title "Delete"
:shortcut "Supr"
:on-click do-delete}]
]))
:on-click do-delete}]]))
(mf/defc viewport-context-menu
[{:keys [mdata] :as props}]

View file

@ -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]