diff --git a/backend/src/app/rpc/commands/files.clj b/backend/src/app/rpc/commands/files.clj index d8dc77086..a7a6961f3 100644 --- a/backend/src/app/rpc/commands/files.clj +++ b/backend/src/app/rpc/commands/files.clj @@ -13,6 +13,7 @@ [app.common.pages.helpers :as cph] [app.common.pages.migrations :as pmg] [app.common.spec :as us] + [app.common.types.components-list :as ctkl] [app.common.types.file :as ctf] [app.common.types.shape-tree :as ctt] [app.config :as cf] @@ -200,6 +201,16 @@ ::db/check-deleted? false})] (blob/decode (:content row)))) +(defn load-all-pointers! + [data] + (doseq [[_id page] (:pages-index data)] + (when (pmap/pointer-map? page) + (pmap/load! page))) + (doseq [[_id component] (:components data)] + (when (pmap/pointer-map? component) + (pmap/load! component))) + data) + (defn persist-pointers! [conn file-id] (doseq [[id item] @pmap/*tracked*] @@ -485,17 +496,23 @@ (defn get-team-shared-files [conn team-id] (letfn [(assets-sample [assets limit] - (let [sorted-assets (->> (vals assets) - (sort-by #(str/lower (:name %))))] - {:count (count sorted-assets) - :sample (into [] (take limit sorted-assets))})) + (let [sorted-assets (->> (vals assets) + (sort-by #(str/lower (:name %))))] + {:count (count sorted-assets) + :sample (into [] (take limit sorted-assets))})) (library-summary [{:keys [id data] :as file}] (binding [pmap/*load-fn* (partial load-pointer conn id)] - {:components (assets-sample (:components data) 4) - :media (assets-sample (:media data) 3) - :colors (assets-sample (:colors data) 3) - :typographies (assets-sample (:typographies data) 3)}))] + (let [load-objects (fn [component] + (binding [pmap/*load-fn* (partial load-pointer conn id)] + (ctf/load-component-objects data component))) + components-sample (-> (assets-sample (ctkl/components data) 4) + (update :sample + #(map load-objects %)))] + {:components components-sample + :media (assets-sample (:media data) 3) + :colors (assets-sample (:colors data) 3) + :typographies (assets-sample (:typographies data) 3)})))] (->> (db/exec! conn [sql:team-shared-files team-id]) (into #{} (comp @@ -552,7 +569,10 @@ (map (fn [{:keys [id] :as row}] (binding [pmap/*load-fn* (partial load-pointer conn id)] (-> row - (update :data dissoc :pages-index) + ;; TODO: re-enable this dissoc and replace call + ;; with other that gets files individually + ;; See task https://tree.taiga.io/project/penpot/task/4904 + ;; (update :data dissoc :pages-index) (handle-file-features client-features))))) (vec))) @@ -836,18 +856,23 @@ (let [library (db/get-by-id conn :file id)] (when (:is-shared library) (let [ldata (-> library decode-row pmg/migrate-file :data)] + (binding [pmap/*load-fn* (partial load-pointer conn id)] + (load-all-pointers! ldata)) (->> (db/query conn :file-library-rel {:library-file-id id}) (map :file-id) (keep #(db/get-by-id conn :file % ::db/check-deleted? false)) (map decode-row) (map pmg/migrate-file) (run! (fn [{:keys [id data revn] :as file}] - (let [data (ctf/absorb-assets data ldata)] - (db/update! conn :file - {:revn (inc revn) - :data (blob/encode data) - :modified-at (dt/now)} - {:id id}))))))))) + (binding [pmap/*tracked* (atom {}) + pmap/*load-fn* (partial load-pointer conn id)] + (let [data (ctf/absorb-assets data ldata)] + (db/update! conn :file + {:revn (inc revn) + :data (blob/encode data) + :modified-at (dt/now)} + {:id id})) + (persist-pointers! conn id))))))))) (s/def ::set-file-shared (s/keys :req [::rpc/profile-id] diff --git a/backend/src/app/tasks/file_gc.clj b/backend/src/app/tasks/file_gc.clj index f673213b3..81464453f 100644 --- a/backend/src/app/tasks/file_gc.clj +++ b/backend/src/app/tasks/file_gc.clj @@ -13,6 +13,7 @@ [app.common.data :as d] [app.common.logging :as l] [app.common.pages.migrations :as pmg] + [app.common.types.components-list :as ctkl] [app.common.types.file :as ctf] [app.common.types.shape-tree :as ctt] [app.config :as cf] @@ -204,36 +205,31 @@ (filter #(ctf/used-in? file-data library-id % :component)) components)) - find-used-components + find-unused-components (fn [components files-data] - ; Find what components are used in any of the files. + ; Find what components are NOT used in any of the files. (loop [files-data files-data - components components - used-components #{}] + components components] (let [file-data (first files-data)] (if (or (nil? file-data) (empty? components)) - used-components + components (let [used-components-file (find-used-components-file components file-data)] (recur (rest files-data) - (into #{} (remove used-components-file) components) - (into used-components used-components-file))))))) + (into #{} (remove used-components-file) components))))))) - deleted-components (set (vals (:deleted-components library-data))) - saved-components (find-used-components deleted-components - (cons library-data - (retrieve-client-files conn library-id))) - new-deleted-components (d/index-by :id (vec saved-components)) - - total (- (count deleted-components) - (count saved-components))] + deleted-components (set (ctkl/deleted-components-seq library-data)) + unused-components (find-unused-components deleted-components + (cons library-data + (retrieve-client-files conn library-id))) + total (count unused-components)] (when-not (zero? total) (l/debug :hint "clean deleted components" :total total) - (let [new-data (-> library-data - (assoc :deleted-components new-deleted-components) - (blob/encode))] + (let [new-data (reduce #(ctkl/delete-component %1 (:id %2)) + library-data + unused-components)] (db/update! conn :file - {:data new-data} + {:data (blob/encode new-data)} {:id library-id}))))) (def ^:private sql:get-unused-fragments diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index bf2ea3ced..6007e5a6d 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -255,6 +255,11 @@ (subvec v 0 index) (subvec v (inc index)))) +(defn without-obj + "Clear collection from specified obj and without nil values." + [coll o] + (into [] (filter #(not= % o)) coll)) + (defn zip [col1 col2] (map vector col1 col2)) @@ -477,6 +482,17 @@ (->> (apply c/iteration args) (concat-all))) +(defn insert-at-index + "Insert a list of elements at the given index of a previous list. + Replace all existing elems." + [elems index new-elems] + (let [[before after] (split-at index elems) + p? (set new-elems)] + (concat-vec [] + (remove p? before) + new-elems + (remove p? after)))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Data Parsing / Conversion diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index 2b7e2fc3f..53b8b2e37 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -8,6 +8,7 @@ "A version parsing helper." (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.exceptions :as ex] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] @@ -664,9 +665,10 @@ [_ shapes] (ctn/make-component-instance page component - (:id file) + (:data file) (gpt/point main-instance-x main-instance-y) + true {:main-instance? true :force-id main-instance-id})] (as-> file $ @@ -701,12 +703,15 @@ component (ctkl/get-component (:data file) component-id) ;; main-instance-id (:main-instance-id component) + components-v2 (dm/get-in file [:options :components-v2]) + [shape shapes] (ctn/make-component-instance page component (:id file) (gpt/point x y) + components-v2 #_{:main-instance? true :force-id main-instance-id})] diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc index 2e58f5440..77ad253fb 100644 --- a/common/src/app/common/pages/changes.cljc +++ b/common/src/app/common/pages/changes.cljc @@ -26,15 +26,6 @@ [app.common.types.shape-tree :as ctst] [app.common.types.typographies-list :as ctyl])) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Specific helpers -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn- without-obj - "Clear collection from specified obj and without nil values." - [coll o] - (into [] (filter #(not= % o)) coll)) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Page Transformation Changes ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -111,27 +102,9 @@ (defmethod process-change :del-obj [data {:keys [page-id component-id id ignore-touched]}] - (letfn [(delete-from-parent [parent] - (let [parent (update parent :shapes without-obj id)] - (cond-> parent - (and (:shape-ref parent) - (not ignore-touched)) - (-> (update :touched cph/set-touched-group :shapes-group) - (dissoc :remote-synced?))))) - - (delete-from-objects [objects] - (if-let [target (get objects id)] - (let [parent-id (or (:parent-id target) - (:frame-id target)) - children (cph/get-children id objects)] - (-> (reduce dissoc objects children) - (dissoc id) - (d/update-when parent-id delete-from-parent))) - objects))] - - (if page-id - (d/update-in-when data [:pages-index page-id :objects] delete-from-objects) - (d/update-in-when data [:components component-id :objects] delete-from-objects)))) + (if page-id + (d/update-in-when data [:pages-index page-id] ctst/delete-shape id ignore-touched) + (d/update-in-when data [:components component-id] ctst/delete-shape id ignore-touched))) ;; reg-objects operation "regenerates" the geometry and selrect of the parent groups (defmethod process-change :reg-objects @@ -197,7 +170,7 @@ (insert-items [prev-shapes index shapes] (let [prev-shapes (or prev-shapes [])] (if index - (cph/insert-at-index prev-shapes index shapes) + (d/insert-at-index prev-shapes index shapes) (cph/append-at-the-end prev-shapes shapes)))) (add-to-parent [parent index shapes] @@ -226,7 +199,7 @@ (not ignore-touched))] (-> objects - (d/update-in-when [pid :shapes] without-obj sid) + (d/update-in-when [pid :shapes] d/without-obj sid) (d/update-in-when [pid :shapes] d/vec-without-nils) (cond-> component? (d/update-when pid #(-> % (update :touched cph/set-touched-group :shapes-group) @@ -301,7 +274,7 @@ (defmethod process-change :mov-page [data {:keys [id index]}] - (update data :pages cph/insert-at-index index [id])) + (update data :pages d/insert-at-index index [id])) (defmethod process-change :add-color [data {:keys [color]}] diff --git a/common/src/app/common/pages/changes_builder.cljc b/common/src/app/common/pages/changes_builder.cljc index 3cf5c6100..313370c86 100644 --- a/common/src/app/common/pages/changes_builder.cljc +++ b/common/src/app/common/pages/changes_builder.cljc @@ -39,6 +39,12 @@ [changes stack-undo?] (assoc changes :stack-undo? stack-undo?)) +(defn set-undo-group + [changes undo-group] + (cond-> changes + (some? undo-group) + (assoc :undo-group undo-group))) + (defn with-page [changes page] (vary-meta changes assoc @@ -80,7 +86,8 @@ [changes1 changes2] {:redo-changes (d/concat-vec (:redo-changes changes1) (:redo-changes changes2)) :undo-changes (d/concat-vec (:undo-changes changes1) (:undo-changes changes2)) - :origin (:origin changes1)}) + :origin (:origin changes1) + :undo-group (:undo-group changes1)}) ; TODO: remove this when not needed (defn- assert-page-id @@ -598,19 +605,21 @@ (update :redo-changes (fn [redo-changes] (-> redo-changes - (conj {:type :add-component - :id id - :path path - :name name - :main-instance-id main-instance-id - :main-instance-page main-instance-page - :shapes new-shapes}) + (conj (cond-> {:type :add-component + :id id + :path path + :name name + :main-instance-id main-instance-id + :main-instance-page main-instance-page} + (some? new-shapes) ;; this will be null in components-v2 + (assoc :shapes new-shapes))) (into (map mk-change) updated-shapes)))) (update :undo-changes (fn [undo-changes] (-> undo-changes (d/preconj {:type :del-component - :id id}) + :id id + :skip-undelete? true}) (into (comp (map :id) (map lookupf) (map mk-change)) @@ -629,7 +638,7 @@ :id id :name (:name new-component) :path (:path new-component) - :objects (:objects new-component)}) + :objects (:objects new-component)}) ;; this won't exist in components-v2 (update :undo-changes d/preconj {:type :mod-component :id id :name (:name prev-component) @@ -638,28 +647,13 @@ changes))) (defn delete-component - [changes id components-v2] + [changes id] (assert-library changes) - (let [library-data (::library-data (meta changes)) - prev-component (get-in library-data [:components id])] - (-> changes - (update :redo-changes conj {:type :del-component - :id id}) - (update :undo-changes - (fn [undo-changes] - (cond-> undo-changes - components-v2 - (d/preconj {:type :purge-component - :id id}) - - :always - (d/preconj {:type :add-component - :id id - :name (:name prev-component) - :path (:path prev-component) - :main-instance-id (:main-instance-id prev-component) - :main-instance-page (:main-instance-page prev-component) - :shapes (vals (:objects prev-component))}))))))) + (-> changes + (update :redo-changes conj {:type :del-component + :id id}) + (update :undo-changes d/preconj {:type :restore-component + :id id}))) (defn restore-component [changes id] diff --git a/common/src/app/common/pages/changes_spec.cljc b/common/src/app/common/pages/changes_spec.cljc index 1cd63c7e2..f43832c51 100644 --- a/common/src/app/common/pages/changes_spec.cljc +++ b/common/src/app/common/pages/changes_spec.cljc @@ -153,8 +153,8 @@ (s/coll-of ::cts/shape)) (defmethod change-spec :add-component [_] - (s/keys :req-un [::id ::name :internal.changes.add-component/shapes] - :opt-un [::path])) + (s/keys :req-un [::id ::name] + :opt-un [::path :internal.changes.add-component/shapes])) (defmethod change-spec :mod-component [_] (s/keys :req-un [::id] diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index 589d2249b..7f90b3277 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -9,6 +9,8 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.spec :as us] + [app.common.types.components-list :as ctkl] + [app.common.types.pages-list :as ctpl] [app.common.types.shape.layout :as ctl] [app.common.uuid :as uuid] [cuerdas.core :as str])) @@ -270,25 +272,6 @@ [shape group] ((or (:touched shape) #{}) group)) -(defn get-component - "Retrieve a component from libraries, if no library-id is provided, we - iterate over all libraries and find the component on it." - ([libraries component-id] - (some #(-> % :data :components (get component-id)) (vals libraries))) - ([libraries library-id component-id] - (get-in libraries [library-id :data :components component-id]))) - -(defn get-component-shape - "Get the parent shape linked to a component for this shape, if any" - [objects shape] - (if-not (:shape-ref shape) - nil - (if (:component-id shape) - shape - (if-let [parent-id (:parent-id shape)] - (get-component-shape objects (get objects parent-id)) - nil)))) - (defn get-root-shape "Get the root shape linked to a component for this shape, if any." [objects shape] @@ -319,8 +302,8 @@ (us/assert uuid? id) (-> (if (= type :page) - (get-in file [:pages-index id]) - (get-in file [:components id])) + (ctpl/get-page file id) + (ctkl/get-component file id)) (assoc :type type))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -340,15 +323,6 @@ (update page :objects #(into % (d/index-by :id objects-list)))) -(defn insert-at-index - [objects index ids] - (let [[before after] (split-at index objects) - p? (set ids)] - (d/concat-vec [] - (remove p? before) - ids - (remove p? after)))) - (defn append-at-the-end [prev-ids ids] (reduce (fn [acc id] diff --git a/common/src/app/common/types/component.cljc b/common/src/app/common/types/component.cljc index 7177a52f9..d963f441b 100644 --- a/common/src/app/common/types/component.cljc +++ b/common/src/app/common/types/component.cljc @@ -36,7 +36,9 @@ (defn get-component-root [component] - (get-in component [:objects (:id component)])) + (if (some? (:main-instance-id component)) + (get-in component [:objects (:main-instance-id component)]) + (get-in component [:objects (:id component)]))) (defn uses-library-components? "Check if the shape uses any component in the given library." @@ -45,12 +47,12 @@ (= (:component-file shape) library-id))) (defn in-component-instance? - "Check if the shape is inside a component instance." + "Check if the shape is inside a component non-main instance." [shape] (some? (:shape-ref shape))) (defn in-component-instance-not-root? - "Check if the shape is inside a component instance and + "Check if the shape is inside a component non-main instance and is not the root shape." [shape] (and (some? (:shape-ref shape)) @@ -66,5 +68,3 @@ :remote-synced? :shape-ref :touched)) - - diff --git a/common/src/app/common/types/components_list.cljc b/common/src/app/common/types/components_list.cljc index 137990c8a..1a9b40f0b 100644 --- a/common/src/app/common/types/components_list.cljc +++ b/common/src/app/common/types/components_list.cljc @@ -10,9 +10,18 @@ [app.common.data.macros :as dm] [app.common.files.features :as feat])) +(defn components + [file-data] + (d/removem (fn [[_ component]] (:deleted component)) + (:components file-data))) + (defn components-seq [file-data] - (vals (:components file-data))) + (remove :deleted (vals (:components file-data)))) + +(defn deleted-components-seq + [file-data] + (filter :deleted (vals (:components file-data)))) (defn add-component [file-data {:keys [id name path main-instance-id main-instance-page shapes]}] @@ -23,11 +32,13 @@ (assoc-in [:components id] {:id id :name name - :path path - :objects (->> shapes - (d/index-by :id) - (wrap-object-fn))}) + :path path}) + (not components-v2) + (assoc-in [:components id :objects] + (->> shapes + (d/index-by :id) + (wrap-object-fn))) components-v2 (update-in [:components id] assoc :main-instance-id main-instance-id @@ -51,7 +62,15 @@ (defn get-component [file-data component-id] - (get-in file-data [:components component-id])) + (let [component (get-in file-data [:components component-id])] + (when-not (:deleted component) + component))) + +(defn get-deleted-component + [file-data component-id] + (let [component (get-in file-data [:components component-id])] + (when (:deleted component) + component))) (defn update-component [file-data component-id f] @@ -61,3 +80,10 @@ [file-data component-id] (update file-data :components dissoc component-id)) +(defn mark-component-deleted + [file-data component-id] + (assoc-in file-data [:components component-id :deleted] true)) + +(defn mark-component-undeleted + [file-data component-id] + (d/dissoc-in file-data [:components component-id :deleted])) diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index 7c9950a8b..3c230e15c 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -6,11 +6,12 @@ (ns app.common.types.container (:require - [app.common.data.macros :as dm] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.pages.common :as common] [app.common.spec :as us] + [app.common.types.components-list :as ctkl] + [app.common.types.pages-list :as ctpl] [app.common.types.shape-tree :as ctst] [clojure.spec.alpha :as s])) @@ -42,8 +43,8 @@ (us/assert uuid? id) (-> (if (= type :page) - (dm/get-in file [:pages-index id]) - (dm/get-in file [:components id])) + (ctpl/get-page file id) + (ctkl/get-component file id)) (assoc :type type))) (defn get-shape @@ -62,6 +63,17 @@ [container shape-id f] (update-in container [:objects shape-id] f)) +(defn get-component-shape + "Get the parent shape linked to a component for this shape, if any" + [objects shape] + (if-not (:shape-ref shape) + nil + (if (:component-id shape) + shape + (if-let [parent-id (:parent-id shape)] + (get-component-shape objects (get objects parent-id)) + nil)))) + (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 @@ -117,15 +129,22 @@ [new-root-shape (map remap-frame-id new-shapes) updated-shapes])) (defn make-component-instance - "Clone the shapes of the component, generating new names and ids, and linking + "Generate a new instance of the component inside the given container. + + Clone the shapes of the component, generating new names and ids, and linking each new shape to the corresponding one of the component. Place the new instance coordinates in the given position." - ([container component component-file-id position] - (make-component-instance container component component-file-id position {})) + ([container component library-data position components-v2] + (make-component-instance container component library-data position components-v2 {})) - ([container component component-file-id position + ([container component library-data position components-v2 {:keys [main-instance? force-id] :or {main-instance? false force-id nil}}] - (let [component-shape (get-shape component (:id component)) + (let [component-page (when components-v2 + (ctpl/get-page library-data (:main-instance-page component))) + component-shape (if components-v2 + (-> (get-shape component-page (:main-instance-id component)) + (assoc :parent-id nil)) + (get-shape component (:id component))) orig-pos (gpt/point (:x component-shape) (:y component-shape)) delta (gpt/subtract position orig-pos) @@ -147,20 +166,20 @@ (vswap! frame-ids-map assoc (:id original-shape) (:id new-shape))) (cond-> new-shape - true + :always (-> (gsh/move delta) - (dissoc :touched)) + (dissoc :touched :main-instance?)) - (nil? (:shape-ref original-shape)) + (and (not main-instance?) (nil? (:shape-ref original-shape))) (assoc :shape-ref (:id original-shape)) (nil? (:parent-id original-shape)) - (assoc :component-id (:id original-shape) - :component-file component-file-id + (assoc :component-id (:id component) + :component-file (:id library-data) :component-root? true :name new-name) - (and (nil? (:parent-id original-shape)) main-instance?) + (and (nil? (:parent-id original-shape)) main-instance? components-v2) (assoc :main-instance? true) (some? (:parent-id original-shape)) @@ -169,7 +188,7 @@ [new-shape new-shapes _] (ctst/clone-object component-shape nil - (get component :objects) + (if components-v2 (:objects component-page) (:objects component)) update-new-shape (fn [object _] object) force-id) @@ -177,10 +196,10 @@ ;; If frame-id points to a shape inside the component, remap it to the ;; corresponding new frame shape. If not, set it to the destination frame. ;; Also fix empty parent-id. - remap-frame-id (fn [shape] - (as-> shape $ - (update $ :frame-id #(get @frame-ids-map % frame-id)) - (update $ :parent-id #(or % (:frame-id $)))))] + remap-frame-id (fn [shape] + (as-> shape $ + (update $ :frame-id #(get @frame-ids-map % frame-id)) + (update $ :parent-id #(or % (:frame-id $)))))] [new-shape (map remap-frame-id new-shapes)]))) diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index 8bc075b1e..4045043a0 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -6,27 +6,27 @@ (ns app.common.types.file (:require - [app.common.data :as d] - [app.common.data.macros :as dm] - [app.common.files.features :as ffeat] - [app.common.geom.point :as gpt] - [app.common.geom.shapes :as gsh] - [app.common.pages.common :refer [file-version]] - [app.common.pages.helpers :as cph] - [app.common.types.color :as ctc] - [app.common.types.colors-list :as ctcl] - [app.common.types.component :as ctk] - [app.common.types.components-list :as ctkl] - [app.common.types.container :as ctn] - [app.common.types.file.media-object :as ctfm] - [app.common.types.page :as ctp] - [app.common.types.pages-list :as ctpl] - [app.common.types.shape-tree :as ctst] - [app.common.types.typographies-list :as ctyl] - [app.common.types.typography :as cty] - [app.common.uuid :as uuid] - [clojure.spec.alpha :as s] - [cuerdas.core :as str])) + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.files.features :as ffeat] + [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] + [app.common.pages.common :refer [file-version]] + [app.common.pages.helpers :as cph] + [app.common.types.color :as ctc] + [app.common.types.colors-list :as ctcl] + [app.common.types.component :as ctk] + [app.common.types.components-list :as ctkl] + [app.common.types.container :as ctn] + [app.common.types.file.media-object :as ctfm] + [app.common.types.page :as ctp] + [app.common.types.pages-list :as ctpl] + [app.common.types.shape-tree :as ctst] + [app.common.types.typographies-list :as ctyl] + [app.common.types.typography :as cty] + [app.common.uuid :as uuid] + [clojure.spec.alpha :as s] + [cuerdas.core :as str])) ;; Specs @@ -116,56 +116,100 @@ ([libraries library-id component-id] (ctkl/get-component (dm/get-in libraries [library-id :data]) component-id))) -(defn delete-component - "Delete a component and store it to be able to be recovered later. +(defn get-component-library + "Retrieve the library the component belongs to." + [libraries instance-root] + (get libraries (:component-file instance-root))) - Remember also the position of the main instance." +(defn get-component-page + "Retrieve the page where the main instance of the component resides." + [file-data component] + (ctpl/get-page file-data (:main-instance-page component))) + +(defn get-component-container + "Retrieve the container that holds the component shapes (the page in components-v2 + or the component itself in v1)" + [file-data component] + (let [components-v2 (dm/get-in file-data [:options :components-v2])] + (if (and components-v2 (not (:deleted component))) + (let [component-page (get-component-page file-data component)] + (cph/make-container component-page :page)) + (cph/make-container component :component)))) + +(defn get-component-root + "Retrieve the root shape of the component." + [file-data component] + (let [components-v2 (dm/get-in file-data [:options :components-v2])] + (if (and components-v2 (not (:deleted component))) + (-> file-data + (get-component-page component) + (ctn/get-shape (:main-instance-id component))) + (ctk/get-component-root component)))) + +(defn get-component-shape + "Retrieve one shape in the component by id." + [file-data component shape-id] + (let [components-v2 (dm/get-in file-data [:options :components-v2])] + (if (and components-v2 (not (:deleted component))) + (let [component-page (get-component-page file-data component)] + (ctn/get-shape component-page shape-id)) + (dm/get-in component [:objects shape-id])))) + +(defn get-ref-shape + "Retrieve the shape in the component that is referenced by the + instance shape." + [file-data component shape] + (when (:shape-ref shape) + (get-component-shape file-data component (:shape-ref shape)))) + +(defn get-component-shapes + "Retrieve all shapes of the component" + [file-data component] + (let [components-v2 (dm/get-in file-data [:options :components-v2])] + (if components-v2 + (let [instance-page (get-component-page file-data component)] + (cph/get-children-with-self (:objects instance-page) (:main-instance-id component))) + (vals (:objects component))))) + +(defn load-component-objects + "Add an :objects property to the component, with only the shapes that belong to it" + [file-data component] + (let [components-v2 (dm/get-in file-data [:options :components-v2])] + (if (and components-v2 component (nil? (:objects component))) ;; This operation may be called twice, e.g. in an idempotent change + (let [component-page (get-component-page file-data component) + page-objects (:objects component-page) + objects (->> (cons (:main-instance-id component) + (cph/get-children-ids page-objects (:main-instance-id component))) + (map #(get page-objects %)) + (d/index-by :id))] + (assoc component :objects objects)) + component))) + +(defn delete-component + "Mark a component as deleted and store the main instance shapes iside it, to + be able to be recovered later." ([file-data component-id] (delete-component file-data component-id false)) ([file-data component-id skip-undelete?] - (let [components-v2 (dm/get-in file-data [:options :components-v2]) - - add-to-deleted-components - (fn [file-data] - (let [component (ctkl/get-component file-data component-id)] - (if (some? component) - (let [page (ctpl/get-page file-data (:main-instance-page component)) - main-instance (ctn/get-shape page (:main-instance-id component)) - component (assoc component - :main-instance-x (:x main-instance) ; An instance root is always a group, - :main-instance-y (:y main-instance))] ; so it will have :x and :y - (when (nil? main-instance) - (throw (ex-info "Cannot delete the main instance before the component" {:component-id component-id}))) - (assoc-in file-data [:deleted-components component-id] component)) - file-data)))] - - (cond-> file-data - (and components-v2 (not skip-undelete?)) - (add-to-deleted-components) - - :always - (ctkl/delete-component component-id))))) - -(defn get-deleted-component - "Retrieve a component that has been deleted but still is in the safe store." - [file-data component-id] - (dm/get-in file-data [:deleted-components component-id])) + (let [components-v2 (dm/get-in file-data [:options :components-v2])] + (if (or (not components-v2) skip-undelete?) + (ctkl/delete-component file-data component-id) + (-> file-data + (ctkl/update-component component-id (partial load-component-objects file-data)) + (ctkl/mark-component-deleted component-id)))))) (defn restore-component - "Recover a deleted component and put it again in place." + "Recover a deleted component and all its shapes and put all this again in place." [file-data component-id] - (let [component (-> (dm/get-in file-data [:deleted-components component-id]) - (dissoc :main-instance-x :main-instance-y))] - (cond-> file-data - (some? component) - (-> (assoc-in [:components component-id] component) - (d/dissoc-in [:deleted-components component-id]))))) + (-> file-data + (ctkl/update-component component-id #(dissoc % :objects)) + (ctkl/mark-component-undeleted component-id))) (defn purge-component "Remove permanently a component." [file-data component-id] - (d/dissoc-in file-data [:deleted-components component-id])) + (ctkl/delete-component file-data component-id)) (defmulti uses-asset? "Checks if a shape uses the given asset." @@ -243,8 +287,9 @@ [(ctpl/add-page file-data library-page) (:id library-page) (gpt/point 0 0)])))) (defn migrate-to-components-v2 - "If there is any component in the file library, add a new 'Library backup' and generate - main instances for all components there. Mark the file with the :components-v2 option." + "If there is any component in the file library, add a new 'Library backup', generate + main instances for all components there and remove shapes from library components. + Mark the file with the :components-v2 option." [file-data] (let [components (ctkl/components-seq file-data)] (if (or (empty? components) @@ -262,8 +307,9 @@ [new-shape new-shapes] (ctn/make-component-instance page component - (:id file-data) + file-data position + false {:main-instance? true}) add-shapes @@ -281,9 +327,10 @@ update-component (fn [component] - (assoc component - :main-instance-id (:id new-shape) - :main-instance-page page-id))] + (-> component + (assoc :main-instance-id (:id new-shape) + :main-instance-page page-id) + (dissoc :objects)))] (-> file-data (ctpl/update-page page-id add-shapes) @@ -292,9 +339,9 @@ add-instance-grid (fn [file-data components] (let [position-seq (ctst/generate-shape-grid - (map ctk/get-component-root components) - start-pos - grid-gap)] + (map (partial get-component-root file-data) components) + start-pos + grid-gap)] (loop [file-data file-data components-seq (seq components) position-seq position-seq] @@ -311,7 +358,7 @@ (assoc-in [:options :components-v2] true)))))) (defn- absorb-components - [file-data used-components] + [file-data used-components library-data] (let [grid-gap 50 ; Search for the library page. If not exists, create it. @@ -326,10 +373,17 @@ [main-instance-shape main-instance-shapes] (ctn/make-component-instance page component - (:id file-data) + library-data position + (dm/get-in file-data [:options :components-v2]) {:main-instance? true}) + main-instance-shapes + (map #(cond-> % + (some? (:component-file %)) + (assoc :component-file (:id file-data))) + main-instance-shapes) + ; Add all shapes of the main instance to the library page add-main-instance-shapes (fn [page] @@ -353,7 +407,7 @@ :path (:path component) :main-instance-id (:id main-instance-shape) :main-instance-page page-id - :shapes (vals (:objects component))})) + :shapes (get-component-shapes library-data component)})) ; Change all existing instances to point to the local file remap-instances @@ -378,9 +432,9 @@ add-component-grid (fn [data used-components] (let [position-seq (ctst/generate-shape-grid - (map #(ctk/get-component-root (first %)) used-components) - start-pos - grid-gap)] + (map #(get-component-root library-data (first %)) used-components) + start-pos + grid-gap)] (loop [data data components-seq (seq used-components) position-seq position-seq] @@ -410,9 +464,9 @@ remap-shape)) % shapes)))] - (as-> file-data $ - (ctcl/add-color $ color) - (reduce remap-shapes $ usages))))] + (as-> file-data $ + (ctcl/add-color $ color) + (reduce remap-shapes $ usages))))] (reduce absorb-color file-data @@ -434,9 +488,9 @@ remap-shape)) % shapes)))] - (as-> file-data $ - (ctyl/add-typography $ typography) - (reduce remap-shapes $ usages))))] + (as-> file-data $ + (ctyl/add-typography $ typography) + (reduce remap-shapes $ usages))))] (reduce absorb-typography file-data @@ -452,7 +506,7 @@ (cond-> file-data (d/not-empty? used-components) - (absorb-components used-components) + (absorb-components used-components library-data) (d/not-empty? used-colors) (absorb-colors used-colors) @@ -473,73 +527,86 @@ ([file-data page-id libraries show-ids show-touched] (let [page (ctpl/get-page file-data page-id) objects (:objects page) - components (:components file-data) + components (ctkl/components file-data) root (d/seek #(nil? (:parent-id %)) (vals objects))] (letfn [(show-shape [shape-id level objects] - (let [shape (get objects shape-id)] - (println (str/pad (str (str/repeat " " level) - (:name shape) - (when (seq (:touched shape)) "*") - (when show-ids (str/format " <%s>" (:id shape)))) - {:length 20 - :type :right}) - (show-component shape objects)) - (when show-touched - (when (seq (:touched shape)) - (println (str (str/repeat " " level) - " " - (str (:touched shape))))) - (when (:remote-synced? shape) - (println (str (str/repeat " " level) - " (remote-synced)")))) - (when (:shapes shape) - (dorun (for [shape-id (:shapes shape)] - (show-shape shape-id (inc level) objects)))))) + (let [shape (get objects shape-id)] + (println (str/pad (str (str/repeat " " level) + (when (:main-instance? shape) "{") + (:name shape) + (when (:main-instance? shape) "}") + (when (seq (:touched shape)) "*") + (when show-ids (str/format " <%s>" (:id shape)))) + {:length 20 + :type :right}) + (show-component-info shape objects)) + (when show-touched + (when (seq (:touched shape)) + (println (str (str/repeat " " level) + " " + (str (:touched shape))))) + (when (:remote-synced? shape) + (println (str (str/repeat " " level) + " (remote-synced)")))) + (when (:shapes shape) + (dorun (for [shape-id (:shapes shape)] + (show-shape shape-id (inc level) objects)))))) - (show-component [shape objects] - (if (nil? (:shape-ref shape)) - "" - (let [root-shape (cph/get-component-shape objects shape) - 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 nil)) - component (when component-id - (if component-file - (dm/get-in component-file [:data :components component-id]) - (get components component-id))) - component-shape (when (and component (:shape-ref shape)) - (dm/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))) - (or (:name component-shape) "?") - (if (or (:component-root? shape) - (nil? (:component-id shape)) - true) - "" - (let [component-id (:component-id shape) - component-file-id (:component-file shape) - component-file (when component-file-id (get libraries component-file-id nil)) - component (if component-file - (dm/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))))))))] + (show-component-info [shape objects] + (if (nil? (:shape-ref shape)) + (if (:component-root? shape) " #" "") + (let [root-shape (ctn/get-component-shape objects shape) + 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 nil)) + component (when component-id + (if component-file + (ctkl/get-component (:data component-file) component-id) + (get components component-id))) + component-shape (when component + (if component-file + (get-ref-shape (:data component-file) component shape) + (get-ref-shape file-data component shape)))] - (println "[Page]") + (str/format " %s--> %s%s%s" + (cond (:component-root? shape) "#" + (:component-id shape) "@" + :else "-") + (when component-file (str/format "<%s> " (:name component-file))) + (or (:name component-shape) "?") + (if (or (:component-root? shape) + (nil? (:component-id shape)) + true) + "" + (let [component-id (:component-id shape) + component-file-id (:component-file shape) + component-file (when component-file-id (get libraries component-file-id nil)) + component (if component-file + (ctkl/get-component (:data component-file) component-id) + (get components component-id))] + (str/format " (%s%s)" + (when component-file (str/format "<%s> " (:name component-file))) + (:name component)))))))) + + (show-component-instance [component] + (let [page (get-component-page file-data component) + root (get-component-root file-data component)] + (if-not show-ids + (println (str " [" (:name page) "] / " (:name root))) + (do + (println (str " " (:name page) (str/format " <%s>" (:id page)))) + (println (str " " (:name root) (str/format " <%s>" (:id root))))))))] + + (println (str "[Page: " (:name page) "]")) (show-shape (:id root) 0 objects) (dorun (for [component (vals components)] (do (println) - (println (str/format "[%s]" (:name component)) - (when show-ids - (str/format " (main: %s/%s)" - (:main-instance-page component) - (:main-instance-id component)))) - (show-shape (:id component) 0 (:objects component))))))))) + (println (str/format "[%s]" (:name component))) + (when (:objects component) + (show-shape (:id component) 0 (:objects component))) + (when (:main-instance-page component) + (show-component-instance component))))))))) diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc index 6c3b3acde..5333f4488 100644 --- a/common/src/app/common/types/modifiers.cljc +++ b/common/src/app/common/types/modifiers.cljc @@ -709,7 +709,7 @@ (update shape :shapes (fn [shapes] (if (vector? shapes) - (cph/insert-at-index shapes index value) + (d/insert-at-index shapes index value) (d/concat-vec shapes value)))) (update shape :shapes d/concat-vec value))) diff --git a/common/src/app/common/types/pages_list.cljc b/common/src/app/common/types/pages_list.cljc index 777b6c072..4821d098e 100644 --- a/common/src/app/common/types/pages_list.cljc +++ b/common/src/app/common/types/pages_list.cljc @@ -6,8 +6,8 @@ (ns app.common.types.pages-list (:require - [app.common.data.macros :as dm] - [app.common.pages.helpers :as cph])) + [app.common.data :as d] + [app.common.data.macros :as dm])) (defn get-page [file-data id] @@ -23,7 +23,7 @@ (cond exists? pages (nil? index) (conj pages id) - :else (cph/insert-at-index pages index [id]))))) + :else (d/insert-at-index pages index [id]))))) (update :pages-index assoc id (dissoc page :index)))) (defn pages-seq diff --git a/common/src/app/common/types/shape_tree.cljc b/common/src/app/common/types/shape_tree.cljc index 5f281a890..4d2414325 100644 --- a/common/src/app/common/types/shape_tree.cljc +++ b/common/src/app/common/types/shape_tree.cljc @@ -36,7 +36,7 @@ (conj shapes id) :else - (cph/insert-at-index shapes index [id])))) + (d/insert-at-index shapes index [id])))) update-parent (fn [parent] @@ -49,28 +49,63 @@ (-> (update :touched cph/set-touched-group :shapes-group) (dissoc :remote-synced?))))) - ;; TODO: this looks wrong, why we allow nil values? update-objects (fn [objects parent-id] - (if (and (or (nil? parent-id) (contains? objects parent-id)) - (or (nil? frame-id) (contains? objects frame-id))) + (let [parent-id (if (contains? objects parent-id) + parent-id + uuid/zero) + frame-id (if (contains? objects frame-id) + frame-id + uuid/zero)] (-> objects (assoc id (-> shape (assoc :frame-id frame-id) (assoc :parent-id parent-id) (assoc :id id))) - (update parent-id update-parent)) - objects)) + (update parent-id update-parent)))) parent-id (or parent-id frame-id)] (update container :objects update-objects parent-id))) +(defn get-shape + "Get a shape identified by id" + [container id] + (-> container :objects (get id))) + (defn set-shape "Replace a shape in the tree with a new one" [container shape] (assoc-in container [:objects (:id shape)] shape)) +(defn delete-shape + "Remove a shape and all its children from the tree. + + Remove it also from its parent, and marks it as touched + if needed, unless ignore-touched is true." + ([container shape-id] + (delete-shape container shape-id false)) + + ([container shape-id ignore-touched] + (letfn [(delete-from-parent [parent] + (let [parent (update parent :shapes d/without-obj shape-id)] + (cond-> parent + (and (:shape-ref parent) (not ignore-touched)) + (-> (update :touched cph/set-touched-group :shapes-group) + (dissoc :remote-synced?))))) + + (delete-from-objects [objects] + (if-let [target (get objects shape-id)] + (let [parent-id (or (:parent-id target) + (:frame-id target)) + children-ids (cph/get-children-ids objects shape-id)] + (-> (reduce dissoc objects children-ids) + (dissoc shape-id) + (d/update-when parent-id delete-from-parent))) + objects))] + + (update container :objects delete-from-objects)))) + (defn get-frames "Retrieves all frame objects as vector" [objects] @@ -359,4 +394,3 @@ (iterate next-pos (with-meta start-pos {:counter 0})))) - diff --git a/common/test/common_tests/data_test.cljc b/common/test/common_tests/data_test.cljc index 0c0521003..10a3f830f 100644 --- a/common/test/common_tests/data_test.cljc +++ b/common/test/common_tests/data_test.cljc @@ -65,3 +65,41 @@ (t/is (= 0 (d/check-num [] 0))) (t/is (= 0 (d/check-num :foo 0))) (t/is (= 0 (d/check-num nil 0)))) + +(t/deftest insert-at-index + ;; insert different object + (t/is (= (d/insert-at-index [:a :b] 1 [:c :d]) + [:a :c :d :b])) + + ;; insert on the start + (t/is (= (d/insert-at-index [:a :b] 0 [:c]) + [:c :a :b])) + + ;; insert on the end 1 + (t/is (= (d/insert-at-index [:a :b] 2 [:c]) + [:a :b :c])) + + ;; insert on the end with not existing index + (t/is (= (d/insert-at-index [:a :b] 10 [:c]) + [:a :b :c])) + + ;; insert existing in a contiguous index + (t/is (= (d/insert-at-index [:a :b] 1 [:a]) + [:a :b])) + + ;; insert existing in the same index + (t/is (= (d/insert-at-index [:a :b] 0 [:a]) + [:a :b])) + + ;; insert existing in other index case 1 + (t/is (= (d/insert-at-index [:a :b :c] 2 [:a]) + [:b :a :c])) + + ;; insert existing in other index case 2 + (t/is (= (d/insert-at-index [:a :b :c :d] 0 [:d]) + [:d :a :b :c])) + + ;; insert existing in other index case 3 + (t/is (= (d/insert-at-index [:a :b :c :d] 1 [:a]) + [:a :b :c :d]))) + diff --git a/common/test/common_tests/helpers/components.cljc b/common/test/common_tests/helpers/components.cljc index b96e0fea6..4cfbdd547 100644 --- a/common/test/common_tests/helpers/components.cljc +++ b/common/test/common_tests/helpers/components.cljc @@ -9,7 +9,8 @@ [clojure.test :as t] [app.common.pages.helpers :as cph] [app.common.types.component :as ctk] - [app.common.types.container :as ctn])) + [app.common.types.container :as ctn] + [app.common.types.file :as ctf])) ;; ---- Helpers to manage libraries and synchronization @@ -81,7 +82,7 @@ [page root-inst-id libraries] (let [root-inst (ctn/get-shape page root-inst-id) - component (cph/get-component libraries (:component-id root-inst)) + component (ctf/get-component libraries (:component-id root-inst)) shapes-inst (cph/get-children-with-self (:objects page) root-inst-id) shapes-main (cph/get-children-with-self (:objects component) (:shape-ref root-inst)) @@ -90,10 +91,10 @@ main-exists? (fn [shape] (let [component-shape - (cph/get-component-shape (:objects page) shape) + (ctn/get-component-shape (:objects page) shape) component - (cph/get-component libraries (:component-id component-shape)) + (ctf/get-component libraries (:component-id component-shape)) main-shape (ctn/get-shape component (:shape-ref shape))] @@ -117,7 +118,7 @@ [page root-inst-id libraries] (let [root-inst (ctn/get-shape page root-inst-id) - component (cph/get-component libraries (:component-id root-inst)) + component (ctf/get-component libraries (:component-id root-inst)) shapes-inst (cph/get-children-with-self (:objects page) root-inst-id) shapes-main (cph/get-children-with-self (:objects component) (:shape-ref root-inst)) @@ -126,10 +127,10 @@ main-exists? (fn [shape] (let [component-shape - (cph/get-component-shape (:objects page) shape) + (ctn/get-component-shape (:objects page) shape) component - (cph/get-component libraries (:component-id component-shape)) + (ctf/get-component libraries (:component-id component-shape)) main-shape (ctn/get-shape component (:shape-ref shape))] @@ -144,7 +145,7 @@ (defn resolve-component "Get the component with the given id and all its shapes." [page component-id libraries] - (let [component (cph/get-component libraries component-id) + (let [component (ctf/get-component libraries component-id) root-main (ctk/get-component-root component) shapes-main (cph/get-children-with-self (:objects component) (:id root-main))] diff --git a/common/test/common_tests/helpers/files.cljc b/common/test/common_tests/helpers/files.cljc index 66fe15034..3c20d584f 100644 --- a/common/test/common_tests/helpers/files.cljc +++ b/common/test/common_tests/helpers/files.cljc @@ -102,8 +102,9 @@ (let [[instance-shape instance-shapes] (ctn/make-component-instance (ctpl/get-page file-data page-id) (ctkl/get-component (:data library) component-id) - (:id library) - (gpt/point 0 0))] + (:data library) + (gpt/point 0 0) + true)] (swap! idmap assoc label (:id instance-shape)) (-> file-data diff --git a/common/test/common_tests/pages_helpers_test.cljc b/common/test/common_tests/pages_helpers_test.cljc index 6079b0b84..76a4f4a22 100644 --- a/common/test/common_tests/pages_helpers_test.cljc +++ b/common/test/common_tests/pages_helpers_test.cljc @@ -10,50 +10,10 @@ [clojure.pprint :refer [pprint]] [app.common.pages.helpers :as cph])) -(t/deftest insert-at-index - ;; insert different object - (t/is (= (cph/insert-at-index [:a :b] 1 [:c :d]) - [:a :c :d :b])) - - ;; insert on the start - (t/is (= (cph/insert-at-index [:a :b] 0 [:c]) - [:c :a :b])) - - ;; insert on the end 1 - (t/is (= (cph/insert-at-index [:a :b] 2 [:c]) - [:a :b :c])) - - ;; insert on the end with not existing index - (t/is (= (cph/insert-at-index [:a :b] 10 [:c]) - [:a :b :c])) - - ;; insert existing in a contiguous index - (t/is (= (cph/insert-at-index [:a :b] 1 [:a]) - [:a :b])) - - ;; insert existing in the same index - (t/is (= (cph/insert-at-index [:a :b] 0 [:a]) - [:a :b])) - - ;; insert existing in other index case 1 - (t/is (= (cph/insert-at-index [:a :b :c] 2 [:a]) - [:b :a :c])) - - ;; insert existing in other index case 2 - (t/is (= (cph/insert-at-index [:a :b :c :d] 0 [:d]) - [:d :a :b :c])) - - ;; insert existing in other index case 3 - (t/is (= (cph/insert-at-index [:a :b :c :d] 1 [:a]) - [:a :b :c :d])) - - ) - - (t/deftest parse-path-name (t/is (= ["foo" "bar"] (cph/parse-path-name "foo/bar"))) (t/is (= ["" "foo"] (cph/parse-path-name "foo"))) (t/is (= ["" "foo"] (cph/parse-path-name "/foo"))) (t/is (= ["" ""] (cph/parse-path-name ""))) - (t/is (= ["" ""] (cph/parse-path-name nil))) - ) + (t/is (= ["" ""] (cph/parse-path-name nil)))) + diff --git a/frontend/src/app/libs/file_builder.cljs b/frontend/src/app/libs/file_builder.cljs index f2793f42a..f30014832 100644 --- a/frontend/src/app/libs/file_builder.cljs +++ b/frontend/src/app/libs/file_builder.cljs @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.file-builder :as fb] [app.common.media :as cm] + [app.common.types.components-list :as ctkl] [app.common.uuid :as uuid] [app.util.dom :as dom] [app.util.json :as json] @@ -108,7 +109,7 @@ components-stream (->> files-stream (rx/flat-map vals) - (rx/filter #(d/not-empty? (get-in % [:data :components]))) + (rx/filter #(d/not-empty? (ctkl/components-seq (:data %)))) (rx/flat-map e/parse-library-components)) pages-stream diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 279440ed0..f4dc5c1a6 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -233,6 +233,19 @@ (->> (filter (comp t/pointer? val) data) (resolve-pointers id) (rx/map #(update file :data merge %))))) + (rx/mapcat + (fn [{:keys [id data] :as file}] + ;; Resolve all pages of each library, if needed + (->> (rx/from (seq (:pages-index data))) + (rx/merge-map + (fn [[_ page :as kp]] + (if (t/pointer? page) + (resolve-pointer id kp) + (rx/of kp)))) + (rx/reduce conj {}) + (rx/map + (fn [pages-index] + (assoc-in file [:data :pages-index] pages-index)))))) (rx/reduce conj []) (rx/map libraries-fetched)))))))) @@ -808,8 +821,8 @@ (not (:component-root? shape))) parent (get objects parent-id) - component-shape (cph/get-component-shape objects shape) - component-shape-parent (cph/get-component-shape objects parent) + component-shape (ctn/get-component-shape objects shape) + component-shape-parent (ctn/get-component-shape objects parent) detach? (and instance-part? (not= (:id component-shape) (:id component-shape-parent))) @@ -1638,7 +1651,7 @@ all-objects (merge (:objects page) paste-objects) - changes (-> (dws/prepare-duplicate-changes all-objects page selected delta it) + changes (-> (dws/prepare-duplicate-changes all-objects page selected delta it nil) (pcb/amend-changes (partial process-rchange media-idx)) (pcb/amend-changes (partial change-add-obj-index paste-objects selected index))) diff --git a/frontend/src/app/main/data/workspace/changes.cljs b/frontend/src/app/main/data/workspace/changes.cljs index 771b873ce..359922cc0 100644 --- a/frontend/src/app/main/data/workspace/changes.cljs +++ b/frontend/src/app/main/data/workspace/changes.cljs @@ -34,22 +34,20 @@ (declare commit-changes) - -(defn- add-group-id +(defn- add-undo-group [changes state] - (let [undo (:workspace-undo state) - items (:items undo) - index (or (:index undo) (dec (count items))) - prev-item (when-not (or (empty? items) (= index -1)) - (get items index)) - group-id (:group-id prev-item) - add-group-id? (and - (not (nil? group-id)) - (= (get-in changes [:redo-changes 0 :type]) :mod-obj) - (= (get-in prev-item [:redo-changes 0 :type]) :add-obj)) ;; This is a copy-and-move with mouse+alt - ] - (cond-> changes add-group-id? (assoc :group-id group-id)))) + (let [undo (:workspace-undo state) + items (:items undo) + index (or (:index undo) (dec (count items))) + prev-item (when-not (or (empty? items) (= index -1)) + (get items index)) + undo-group (:undo-group prev-item) + add-undo-group? (and + (not (nil? undo-group)) + (= (get-in changes [:redo-changes 0 :type]) :mod-obj) + (= (get-in prev-item [:redo-changes 0 :type]) :add-obj))] ;; This is a copy-and-move with mouse+alt + (cond-> changes add-undo-group? (assoc :undo-group undo-group)))) (def commit-changes? (ptk/type? ::commit-changes)) @@ -82,7 +80,7 @@ (pcb/set-stack-undo? stack-undo?) (pcb/with-objects objects)) ids) - changes (add-group-id changes state)] + changes (add-undo-group changes state)] (rx/concat (if (seq (:redo-changes changes)) (let [changes (cond-> changes reg-objects? (pcb/resize-parents ids)) @@ -165,15 +163,24 @@ changes))) (defn commit-changes + "Schedules a list of changes to execute now, and add the corresponding undo changes to + the undo stack. + + Options: + - save-undo?: if set to false, do not add undo changes. + - undo-group: if some consecutive changes (or even transactions) share the same + undo-group, they will be undone or redone in a single step + " [{:keys [redo-changes undo-changes - origin save-undo? file-id group-id stack-undo?] - :or {save-undo? true stack-undo? false}}] + origin save-undo? file-id undo-group stack-undo?] + :or {save-undo? true stack-undo? false undo-group (uuid/next)}}] (log/debug :msg "commit-changes" + :js/undo-group (str undo-group) :js/redo-changes redo-changes :js/undo-changes undo-changes) - (let [error (volatile! nil) + (let [error (volatile! nil) page-id (:current-page-id @st/state) - frames (changed-frames redo-changes (wsh/lookup-page-objects @st/state))] + frames (changed-frames redo-changes (wsh/lookup-page-objects @st/state))] (ptk/reify ::commit-changes cljs.core/IDeref (-deref [_] @@ -184,8 +191,8 @@ :page-id page-id :frames frames :save-undo? save-undo? - :stack-undo? stack-undo? - :group-id group-id}) + :undo-group undo-group + :stack-undo? stack-undo?}) ptk/UpdateEvent (update [_ state] @@ -215,7 +222,7 @@ add-page-id (fn [{:keys [id type page] :as change}] (cond-> change - (page-change? type) + (and (page-change? type) (nil? (:page-id change))) (assoc :page-id (or id (:id page))))) changes-by-pages @@ -234,5 +241,5 @@ (when (and save-undo? (seq undo-changes)) (let [entry {:undo-changes undo-changes :redo-changes redo-changes - :group-id group-id}] + :undo-group undo-group}] (rx/of (dwu/append-undo entry stack-undo?))))))))))) diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs index f519f163c..11fef1fd5 100644 --- a/frontend/src/app/main/data/workspace/common.cljs +++ b/frontend/src/app/main/data/workspace/common.cljs @@ -63,16 +63,16 @@ (when-not (or (empty? items) (= index -1)) (let [item (get items index) changes (:undo-changes item) - group-id (:group-id item) + undo-group (:undo-group item) find-first-group-idx (fn ffgidx[index] (let [item (get items index)] - (if (= (:group-id item) group-id) + (if (= (:undo-group item) undo-group) (ffgidx (dec index)) (inc index)))) - undo-group-index (when group-id + undo-group-index (when undo-group (find-first-group-idx index))] - (if group-id + (if undo-group (rx/of (undo-to-index (dec undo-group-index))) (rx/of (dwu/materialize-undo changes (dec index)) (dch/commit-changes {:redo-changes changes @@ -94,16 +94,16 @@ (when-not (or (empty? items) (= index (dec (count items)))) (let [item (get items (inc index)) changes (:redo-changes item) - group-id (:group-id item) + undo-group (:undo-group item) find-last-group-idx (fn flgidx [index] (let [item (get items index)] - (if (= (:group-id item) group-id) + (if (= (:undo-group item) undo-group) (flgidx (inc index)) (dec index)))) - redo-group-index (when group-id + redo-group-index (when undo-group (find-last-group-idx (inc index)))] - (if group-id + (if undo-group (rx/of (undo-to-index redo-group-index)) (rx/of (dwu/materialize-undo changes (inc index)) (dch/commit-changes {:redo-changes changes diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 6f23a0487..fa2df7b68 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -17,11 +17,11 @@ [app.common.pages.helpers :as cph] [app.common.spec :as us] [app.common.types.color :as ctc] + [app.common.types.component :as ctk] [app.common.types.components-list :as ctkl] [app.common.types.container :as ctn] [app.common.types.file :as ctf] [app.common.types.file.media-object :as ctfm] - [app.common.types.pages-list :as ctpl] [app.common.types.typography :as ctt] [app.common.uuid :as uuid] [app.main.data.dashboard :as dd] @@ -317,7 +317,7 @@ shapes (dwg/shapes-for-grouping objects selected)] (when-not (empty? shapes) (let [[group _ changes] - (dwlh/generate-add-component it shapes objects page-id file-id components-v2)] + (dwlh/generate-add-component it shapes objects page-id file-id components-v2 dwg/prepare-create-group)] (when-not (empty? (:redo-changes changes)) (rx/of (dch/commit-changes changes) (dws/select-shapes (d/ordered-set (:id group))))))))))) @@ -346,8 +346,9 @@ ptk/WatchEvent (watch [it state _] (when (and (some? new-name) (not= "" new-name)) - (let [data (get state :workspace-data) - [path name] (cph/parse-path-name new-name) + (let [data (get state :workspace-data) + [path name] (cph/parse-path-name new-name) + components-v2 (features/active-feature? state :components-v2) update-fn (fn [component] @@ -355,12 +356,15 @@ ;; because there are small possibilities of race ;; conditions with component deletion. (when component - (-> component - (assoc :path path) - (assoc :name name) - (update :objects + (cond-> component + :always + (assoc :path path + :name name) + + (not components-v2) + (update :objects ;; Give the same name to the root shape - #(assoc-in % [id :name] name))))) + #(assoc-in % [id :name] name))))) changes (-> (pcb/empty-changes it) (pcb/with-library-data data) @@ -370,30 +374,33 @@ (defn duplicate-component "Create a new component copied from the one with the given id." - [{:keys [id] :as params}] + [library-id component-id] (ptk/reify ::duplicate-component ptk/WatchEvent (watch [it state _] (let [libraries (wsh/get-libraries state) - component (cph/get-component libraries id) + library (get libraries library-id) + component (ctkl/get-component (:data library) component-id) new-name (:name component) components-v2 (features/active-feature? state :components-v2) main-instance-page (when components-v2 - (wsh/lookup-page state (:main-instance-page component))) - main-instance-shape (when components-v2 - (ctn/get-shape main-instance-page (:main-instance-id component))) + (ctf/get-component-page (:data library) component)) - [new-component-shape new-component-shapes + new-component (assoc component :id (uuid/next)) + + [new-component-shape new-component-shapes ; <- null in components-v2 new-main-instance-shape new-main-instance-shapes] - (dwlh/duplicate-component component main-instance-page main-instance-shape) + (dwlh/duplicate-component new-component (:data library)) changes (-> (pcb/empty-changes it nil) (pcb/with-page main-instance-page) (pcb/with-objects (:objects main-instance-page)) (pcb/add-objects new-main-instance-shapes {:ignore-touched true}) - (pcb/add-component (:id new-component-shape) + (pcb/add-component (if components-v2 + (:id new-component) + (:id new-component-shape)) (:path component) new-name new-component-shapes @@ -413,12 +420,12 @@ (let [data (get state :workspace-data)] (if (features/active-feature? state :components-v2) (let [component (ctkl/get-component data id) - page (ctpl/get-page data (:main-instance-page component)) - shape (ctn/get-shape page (:main-instance-id component))] - (rx/of (dwsh/delete-shapes (:id page) #{(:id shape)}))) + page (ctf/get-component-page data component) + shape (ctf/get-component-root data component)] + (rx/of (dwsh/delete-shapes (:id page) #{(:id shape)}))) ;; Deleting main root triggers component delete (let [changes (-> (pcb/empty-changes it) (pcb/with-library-data data) - (pcb/delete-component id false))] + (pcb/delete-component id))] (rx/of (dch/commit-changes changes)))))))) (defn restore-component @@ -429,34 +436,18 @@ (ptk/reify ::restore-component ptk/WatchEvent (watch [it state _] - (let [file-data (wsh/get-file state library-id) - component (ctf/get-deleted-component file-data component-id) - page (ctpl/get-page file-data (:main-instance-page component)) - - ; Make a new main instance, with the same id of the original - [_main-instance shapes] - (ctn/make-component-instance page - component - (:id file-data) - (gpt/point (:main-instance-x component) - (:main-instance-y component)) - {:main-instance? true - :force-id (:main-instance-id component)}) - - changes (-> (pcb/empty-changes it) - (pcb/with-library-data file-data) - (pcb/with-page page)) - - changes (reduce #(pcb/add-object %1 %2 {:ignore-touched true}) - changes - shapes) - - ; restore-component change needs to be done after add main instance - ; because when undo changes, the orden is inverse - changes (pcb/restore-component changes component-id)] - - (rx/of (dch/commit-changes (assoc changes :file-id library-id))))))) - + (let [library-data (wsh/get-file state library-id) + component (ctkl/get-deleted-component library-data component-id) + page (ctf/get-component-page library-data component) + shapes (cph/get-children-with-self (:objects component) (:main-instance-id component)) + changes (-> (pcb/empty-changes it) + (pcb/with-library-data library-data) + (pcb/with-page page)) + changes (reduce #(pcb/add-object %1 %2 {:ignore-touched true}) + changes + shapes) + changes (pcb/restore-component changes component-id)] + (rx/of (dch/commit-changes changes)))))) (defn instantiate-component "Create a new shape in the current page, from the component with the given id @@ -471,8 +462,10 @@ (let [page (wsh/lookup-page state) libraries (wsh/get-libraries state) + changes (pcb/empty-changes it (:id page)) + [new-shape changes] - (dwlh/generate-instantiate-component it + (dwlh/generate-instantiate-component changes file-id component-id position @@ -594,76 +587,79 @@ NOTE: It's possible that the component to update is defined in an external library file, so this function may cause to modify a file different of that the one we are currently editing." - [id] - (us/assert ::us/uuid id) - (ptk/reify ::update-component - ptk/WatchEvent - (watch [it state _] - (log/info :msg "UPDATE-COMPONENT of shape" :id (str id)) - (let [page-id (get state :current-page-id) + ([id] (update-component id nil)) + ([id undo-group] + (us/assert ::us/uuid id) + (ptk/reify ::update-component + ptk/WatchEvent + (watch [it state _] + (log/info :msg "UPDATE-COMPONENT of shape" :id (str id) :undo-group undo-group) + (let [page-id (get state :current-page-id) + local-file (wsh/get-local-file state) + container (cph/get-container local-file :page page-id) + shape (ctn/get-shape container id)] - local-file (wsh/get-local-file state) - libraries (wsh/get-libraries state) + (when (ctk/in-component-instance? shape) + (let [libraries (wsh/get-libraries state) - container (cph/get-container local-file :page page-id) - shape (ctn/get-shape container id) + changes + (-> (pcb/empty-changes it) + (pcb/set-undo-group undo-group) + (pcb/with-container container) + (dwlh/generate-sync-shape-inverse libraries container id)) - changes - (-> (pcb/empty-changes it) - (pcb/with-container container) - (dwlh/generate-sync-shape-inverse libraries container id)) + file-id (:component-file shape) + file (wsh/get-file state file-id) - file-id (:component-file shape) - file (wsh/get-file state file-id) + xf-filter (comp + (filter :local-change?) + (map #(dissoc % :local-change?))) - xf-filter (comp - (filter :local-change?) - (map #(dissoc % :local-change?))) + local-changes (-> changes + (update :redo-changes #(into [] xf-filter %)) + (update :undo-changes #(into [] xf-filter %))) - local-changes (-> changes - (update :redo-changes #(into [] xf-filter %)) - (update :undo-changes #(into [] xf-filter %))) + xf-remove (comp + (remove :local-change?) + (map #(dissoc % :local-change?))) - xf-remove (comp - (remove :local-change?) - (map #(dissoc % :local-change?))) + nonlocal-changes (-> changes + (update :redo-changes #(into [] xf-remove %)) + (update :undo-changes #(into [] xf-remove %)))] - nonlocal-changes (-> changes - (update :redo-changes #(into [] xf-remove %)) - (update :undo-changes #(into [] xf-remove %)))] + (log/debug :msg "UPDATE-COMPONENT finished" + :js/local-changes (log-changes + (:redo-changes local-changes) + file) + :js/nonlocal-changes (log-changes + (:redo-changes nonlocal-changes) + file)) - (log/debug :msg "UPDATE-COMPONENT finished" - :js/local-changes (log-changes - (:redo-changes local-changes) - file) - :js/nonlocal-changes (log-changes - (:redo-changes nonlocal-changes) - file)) - - (rx/of - (when (seq (:redo-changes local-changes)) - (dch/commit-changes (assoc local-changes - :file-id (:id local-file)))) - (when (seq (:redo-changes nonlocal-changes)) - (dch/commit-changes (assoc nonlocal-changes - :file-id file-id)))))))) + (rx/of + (when (seq (:redo-changes local-changes)) + (dch/commit-changes (assoc local-changes + :file-id (:id local-file)))) + (when (seq (:redo-changes nonlocal-changes)) + (dch/commit-changes (assoc nonlocal-changes + :file-id file-id))))))))))) (defn update-component-sync - [shape-id file-id] - (ptk/reify ::update-component-sync - ptk/WatchEvent - (watch [_ state _] - (let [current-file-id (:current-file-id state) - page (wsh/lookup-page state) - shape (ctn/get-shape page shape-id) - undo-id (js/Symbol)] - (rx/of - (dwu/start-undo-transaction undo-id) - (update-component shape-id) - (sync-file current-file-id file-id :components (:component-id shape)) - (when (not= current-file-id file-id) - (sync-file file-id file-id :components (:component-id shape))) - (dwu/commit-undo-transaction undo-id)))))) + ([shape-id file-id] (update-component-sync shape-id file-id nil)) + ([shape-id file-id undo-group] + (ptk/reify ::update-component-sync + ptk/WatchEvent + (watch [_ state _] + (let [current-file-id (:current-file-id state) + page (wsh/lookup-page state) + shape (ctn/get-shape page shape-id) + undo-id (js/Symbol)] + (rx/of + (dwu/start-undo-transaction undo-id) + (update-component shape-id undo-group) + (sync-file current-file-id file-id :components (:component-id shape) undo-group) + (when (not= current-file-id file-id) + (sync-file file-id file-id :components (:component-id shape) undo-group)) + (dwu/commit-undo-transaction undo-id))))))) (defn update-component-in-bulk [shapes file-id] @@ -673,7 +669,7 @@ (let [undo-id (js/Symbol)] (rx/concat (rx/of (dwu/start-undo-transaction undo-id)) - (rx/map #(update-component-sync (:id %) file-id) (rx/from shapes)) + (rx/map #(update-component-sync (:id %) file-id (uuid/next)) (rx/from shapes)) (rx/of (dwu/commit-undo-transaction undo-id))))))) (declare sync-file-2nd-stage) @@ -690,6 +686,8 @@ ([file-id library-id] (sync-file file-id library-id nil nil)) ([file-id library-id asset-type asset-id] + (sync-file file-id library-id asset-type asset-id nil)) + ([file-id library-id asset-type asset-id undo-group] (us/assert ::us/uuid file-id) (us/assert ::us/uuid library-id) (us/assert (s/nilable #{:colors :components :typographies}) asset-type) @@ -709,7 +707,8 @@ :file (dwlh/pretty-file file-id state) :library (dwlh/pretty-file library-id state) :asset-type asset-type - :asset-id asset-id) + :asset-id asset-id + :undo-group undo-group) (let [file (wsh/get-file state file-id) sync-components? (or (nil? asset-type) (= asset-type :components)) @@ -718,7 +717,8 @@ library-changes (reduce pcb/concat-changes - (pcb/empty-changes it) + (-> (pcb/empty-changes it) + (pcb/set-undo-group undo-group)) [(when sync-components? (dwlh/generate-sync-library it file-id :components asset-id library-id state)) (when sync-colors? @@ -727,7 +727,8 @@ (dwlh/generate-sync-library it file-id :typographies asset-id library-id state))]) file-changes (reduce pcb/concat-changes - (pcb/empty-changes it) + (-> (pcb/empty-changes it) + (pcb/set-undo-group undo-group)) [(when sync-components? (dwlh/generate-sync-file it file-id :components asset-id library-id state)) (when sync-colors? @@ -758,7 +759,7 @@ :library-id library-id}))) (when (and (seq (:redo-changes library-changes)) sync-components?) - (rx/of (sync-file-2nd-stage file-id library-id asset-id)))))))))) + (rx/of (sync-file-2nd-stage file-id library-id asset-id undo-group)))))))))) (defn- sync-file-2nd-stage "If some components have been modified, we need to launch another synchronization @@ -769,7 +770,7 @@ ;; recursively. But for this not to cause an infinite loop, we need to ;; implement updated-at at component level, to detect what components have ;; not changed, and then not to apply sync and terminate the loop. - [file-id library-id asset-id] + [file-id library-id asset-id undo-group] (us/assert ::us/uuid file-id) (us/assert ::us/uuid library-id) (us/assert (s/nilable ::us/uuid) asset-id) @@ -782,7 +783,8 @@ (let [file (wsh/get-file state file-id) changes (reduce pcb/concat-changes - (pcb/empty-changes it) + (-> (pcb/empty-changes it) + (pcb/set-undo-group undo-group)) [(dwlh/generate-sync-file it file-id :components asset-id library-id state) (dwlh/generate-sync-library it file-id :components asset-id library-id state)])] @@ -856,7 +858,7 @@ check-changes (fn [[event data]] - (let [{:keys [changes save-undo?]} (deref event) + (let [{:keys [changes save-undo? undo-group]} (deref event) components-changed (reduce #(into %1 (ch/components-changed data %2)) #{} changes)] @@ -864,7 +866,7 @@ (log/info :msg "DETECTED COMPONENTS CHANGED" :ids (map str components-changed)) (run! st/emit! - (map #(update-component-sync % (:id data)) + (map #(update-component-sync % (:id data) undo-group) components-changed)))))] (when components-v2 diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index 261b3ba5b..c7c2ca3c4 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -7,6 +7,7 @@ (ns app.main.data.workspace.libraries-helpers (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.logging :as log] @@ -17,11 +18,12 @@ [app.common.text :as txt] [app.common.types.color :as ctc] [app.common.types.component :as ctk] + [app.common.types.components-list :as ctkl] [app.common.types.container :as ctn] [app.common.types.file :as ctf] [app.common.types.shape-tree :as ctst] [app.common.types.typography :as cty] - [app.main.data.workspace.groups :as dwg] + [app.common.uuid :as uuid] [app.main.data.workspace.state-helpers :as wsh] [cljs.spec.alpha :as s] [clojure.set :as set])) @@ -63,7 +65,7 @@ "If there is exactly one id, and it's a group or a frame, and not already a component, use it as root. Otherwise, create a group that contains all ids. Then, make a component with it, and link all shapes to their corresponding one in the component." - [it shapes objects page-id file-id components-v2] + [it shapes objects page-id file-id components-v2 prepare-create-group] (let [[group changes] (if (and (= (count shapes) 1) (or (= (:type (first shapes)) :group) @@ -74,67 +76,91 @@ (let [group-name (if (= 1 (count shapes)) (:name (first shapes)) "Component 1")] - (dwg/prepare-create-group it - objects - page-id - shapes - group-name - (not (ctk/instance-root? (first shapes)))))) + (prepare-create-group it ; This function needs to be passed as argument + objects ; to avoid a circular dependence + page-id + shapes + group-name + (not (ctk/instance-root? (first shapes)))))) name (:name group) [path name] (cph/parse-path-name name) - [new-shape new-shapes updated-shapes] - (ctn/make-component-shape group objects file-id components-v2) + [root-shape new-shapes updated-shapes] + (if-not components-v2 + (ctn/make-component-shape group objects file-id components-v2) + (let [new-id (uuid/next)] + [(assoc group :id new-id) + nil + [(assoc group + :component-id new-id + :component-file file-id + :component-root? true + :main-instance? true)]])) changes (-> changes - (pcb/add-component (:id new-shape) + (pcb/add-component (:id root-shape) path name new-shapes updated-shapes (:id group) - page-id))] - [group new-shape changes])) + page-id))] + [group (:id root-shape) changes])) (defn duplicate-component "Clone the root shape of the component and all children. Generate new ids from all of them." - [component main-instance-page main-instance-shape] - (let [position (gpt/add (gpt/point (:x main-instance-shape) (:y main-instance-shape)) - (gpt/point (+ (:width main-instance-shape) 50) 0)) + [component library-data] + (let [components-v2 (dm/get-in library-data [:options :components-v2])] + (if components-v2 - component-root (ctk/get-component-root component) + (let [main-instance-page (ctf/get-component-page library-data component) + main-instance-shape (ctf/get-component-root library-data component) - [new-component-shape new-component-shapes _] - (ctst/clone-object component-root - nil - (get component :objects) - identity) + position (gpt/add (gpt/point (:x main-instance-shape) (:y main-instance-shape)) + (gpt/point (+ (:width main-instance-shape) 50) 0)) + component-instance-extra-data (if components-v2 {:main-instance? true} {}) - [new-instance-shape new-instance-shapes] - (when (and (some? main-instance-page) (some? main-instance-shape)) - (ctn/make-component-instance main-instance-page - {:id (:id new-component-shape) - :name (:name new-component-shape) - :objects (d/index-by :id new-component-shapes)} - (:component-file main-instance-shape) - position))] + [new-instance-shape new-instance-shapes] + (when (and (some? main-instance-page) (some? main-instance-shape)) + (ctn/make-component-instance main-instance-page + component + library-data + position + true + component-instance-extra-data))] - [new-component-shape new-component-shapes - new-instance-shape new-instance-shapes])) + [nil nil new-instance-shape new-instance-shapes]) + + (let [component-root (d/seek #(nil? (:parent-id %)) (vals (:objects component))) + + [new-component-shape new-component-shapes _] + (ctst/clone-object component-root + nil + (get component :objects) + identity)] + + [new-component-shape new-component-shapes nil nil])))) (defn generate-instantiate-component "Generate changes to create a new instance from a component." - [it file-id component-id position page libraries] - (let [component (cph/get-component libraries file-id component-id) + [changes file-id component-id position page libraries] + (let [component (ctf/get-component libraries file-id component-id) + library (get libraries file-id) + + components-v2 (dm/get-in library [:data :options :components-v2]) [new-shape new-shapes] - (ctn/make-component-instance page component file-id position) + (ctn/make-component-instance page + component + (:data library) + position + components-v2) changes (reduce #(pcb/add-object %1 %2 {:ignore-touched true}) - (pcb/empty-changes it (:id page)) + changes new-shapes)] [new-shape changes])) @@ -217,7 +243,7 @@ (let [file (wsh/get-file state file-id) components-v2 (get-in file [:options :components-v2])] - (loop [local-components (vals (get file :components)) + (loop [local-components (ctkl/components-seq file) changes (pcb/empty-changes it)] (if-let [local-component (first local-components)] (recur (next local-components) @@ -447,47 +473,47 @@ ;; but it's not touched. (defn generate-sync-shape-direct - "Generate changes to synchronize one shape that the root of a component + "Generate changes to synchronize one shape that is the root of a component instance, and all its children, from the given component." [changes libraries container shape-id reset? components-v2] (log/debug :msg "Sync shape direct" :shape (str shape-id) :reset? reset?) - (let [shape-inst (ctn/get-shape container shape-id) - component (cph/get-component libraries - (:component-file shape-inst) - (:component-id shape-inst)) - component (or component - (and reset? - (ctf/get-deleted-component - (get-in libraries [(:component-file shape-inst) :data]) - (:component-id shape-inst)))) - shape-main (when component - (ctn/get-shape component (:shape-ref shape-inst))) + (let [shape-inst (ctn/get-shape container shape-id)] + (if (ctk/in-component-instance? shape-inst) + (let [library (dm/get-in libraries [(:component-file shape-inst) :data]) + component (or (ctkl/get-component library (:component-id shape-inst)) + (and reset? + (ctkl/get-deleted-component library (:component-id shape-inst)))) - initial-root? (:component-root? shape-inst) + shape-main (when component + (ctf/get-ref-shape library component shape-inst)) - root-inst shape-inst - root-main (when component - (ctk/get-component-root component))] + initial-root? (:component-root? shape-inst) - (if component - (generate-sync-shape-direct-recursive changes - container - shape-inst - component - shape-main - root-inst - root-main - reset? - initial-root? - components-v2) + root-inst shape-inst + root-main (when component + (ctf/get-component-root library component))] + + (if component + (generate-sync-shape-direct-recursive changes + container + shape-inst + component + library + shape-main + root-inst + root-main + reset? + initial-root? + components-v2) ; If the component is not found, because the master component has been ; deleted or the library unlinked, do nothing in v2 or detach in v1. - (if components-v2 - changes - (generate-detach-instance changes container shape-id))))) + (if components-v2 + changes + (generate-detach-instance changes container shape-id)))) + changes))) (defn- generate-sync-shape-direct-recursive - [changes container shape-inst component shape-main root-inst root-main reset? initial-root? components-v2] + [changes container shape-inst component library shape-main root-inst root-main reset? initial-root? components-v2] (log/debug :msg "Sync shape direct recursive" :shape (str (:name shape-inst)) :component (:name component)) @@ -522,20 +548,22 @@ set-remote-synced? (change-remote-synced shape-inst container true)) - children-inst (mapv #(ctn/get-shape container %) - (:shapes shape-inst)) - children-main (mapv #(ctn/get-shape component %) - (:shapes shape-main)) + component-container (ctf/get-component-container library component) + + children-inst (mapv #(ctn/get-shape container %) + (:shapes shape-inst)) + children-main (mapv #(ctn/get-shape component-container %) + (:shapes shape-main)) only-inst (fn [changes child-inst] (if-not (and omit-touched? - (contains? (:touched shape-inst) - :shapes-group)) - (remove-shape changes - child-inst - container - omit-touched?) - changes)) + (contains? (:touched shape-inst) + :shapes-group)) + (remove-shape changes + child-inst + container + omit-touched?) + changes)) only-main (fn [changes child-main] (if-not (and omit-touched? @@ -545,7 +573,7 @@ child-main (d/index-of children-main child-main) - component + component-container container root-inst root-main @@ -558,6 +586,7 @@ container child-inst component + library child-main root-inst root-main @@ -567,12 +596,12 @@ moved (fn [changes child-inst child-main] (move-shape - changes - child-inst - (d/index-of children-inst child-inst) - (d/index-of children-main child-main) - container - omit-touched?))] + changes + child-inst + (d/index-of children-inst child-inst) + (d/index-of children-main child-main) + container + omit-touched?))] (compare-children changes children-inst @@ -588,22 +617,22 @@ the values in the shape and all its children." [changes libraries container shape-id] (log/debug :msg "Sync shape inverse" :shape (str shape-id)) - (let [shape-inst (ctn/get-shape container shape-id) - component (cph/get-component libraries - (:component-file shape-inst) - (:component-id shape-inst)) - shape-main (ctn/get-shape component (:shape-ref shape-inst)) + (let [shape-inst (ctn/get-shape container shape-id) + library (dm/get-in libraries [(:component-file shape-inst) :data]) + component (ctkl/get-component library (:component-id shape-inst)) + shape-main (ctf/get-ref-shape library component shape-inst) - initial-root? (:component-root? shape-inst) + initial-root? (:component-root? shape-inst) - root-inst shape-inst - root-main (ctk/get-component-root component)] + root-inst shape-inst + root-main (ctf/get-component-root library component)] (if component (generate-sync-shape-inverse-recursive changes container shape-inst component + library shape-main root-inst root-main @@ -611,7 +640,7 @@ changes))) (defn- generate-sync-shape-inverse-recursive - [changes container shape-inst component shape-main root-inst root-main initial-root?] + [changes container shape-inst component library shape-main root-inst root-main initial-root?] (log/trace :msg "Sync shape inverse recursive" :shape (str (:name shape-inst)) :component (:name component)) @@ -619,7 +648,7 @@ (if (nil? shape-main) ;; This should not occur, but protect against it in any case changes - (let [component-container (cph/make-container component :component) + (let [component-container (ctf/get-component-container library component) omit-touched? false set-remote-synced? (not initial-root?) @@ -650,7 +679,7 @@ children-inst (mapv #(ctn/get-shape container %) (:shapes shape-inst)) - children-main (mapv #(ctn/get-shape component %) + children-main (mapv #(ctn/get-shape component-container %) (:shapes shape-main)) only-inst (fn [changes child-inst] @@ -659,6 +688,7 @@ (d/index-of children-inst child-inst) component + component-container container root-inst root-main)) @@ -674,6 +704,7 @@ container child-inst component + library child-main root-inst root-main @@ -681,12 +712,12 @@ moved (fn [changes child-inst child-main] (move-shape - changes - child-main - (d/index-of children-main child-main) - (d/index-of children-inst child-inst) - component-container - false)) + changes + child-main + (d/index-of children-main child-main) + (d/index-of children-inst child-inst) + component-container + false)) changes (compare-children changes @@ -763,9 +794,9 @@ (moved-cb child-inst' child-main))))))))))) (defn- add-shape-to-instance - [changes component-shape index component container root-instance root-main omit-touched? set-remote-synced?] + [changes component-shape index component-page container root-instance root-main omit-touched? set-remote-synced?] (log/info :msg (str "ADD [P] " (:name component-shape))) - (let [component-parent-shape (ctn/get-shape component (:parent-id component-shape)) + (let [component-parent-shape (ctn/get-shape component-page (:parent-id component-shape)) parent-shape (d/seek #(ctk/is-main-of? component-parent-shape %) (cph/get-children-with-self (:objects container) (:id root-instance))) @@ -793,7 +824,7 @@ [_ new-shapes _] (ctst/clone-object component-shape (:id parent-shape) - (get component :objects) + (get component-page :objects) update-new-shape update-original-shape) @@ -831,14 +862,14 @@ changes'))) (defn- add-shape-to-main - [changes shape index component page root-instance root-main] + [changes shape index component component-container page root-instance root-main] (log/info :msg (str "ADD [C] " (:name shape))) (let [parent-shape (ctn/get-shape page (:parent-id shape)) component-parent-shape (d/seek #(ctk/is-main-of? % parent-shape) - (cph/get-children-with-self (:objects component) + (cph/get-children-with-self (:objects component-container) (:id root-main))) all-parents (into [(:id component-parent-shape)] - (cph/get-parent-ids (:objects component) + (cph/get-parent-ids (:objects component-container) (:id component-parent-shape))) update-new-shape (fn [new-shape _original-shape] @@ -854,20 +885,24 @@ [_new-shape new-shapes updated-shapes] (ctst/clone-object shape - (:id component-parent-shape) - (get page :objects) - update-new-shape - update-original-shape) + (:id component-parent-shape) + (get page :objects) + update-new-shape + update-original-shape) add-obj-change (fn [changes shape'] (update changes :redo-changes conj - {:type :add-obj - :id (:id shape') - :component-id (:id component) - :parent-id (:parent-id shape') - :index index - :ignore-touched true - :obj shape'})) + (cond-> (make-change + component-container + {:type :add-obj + :id (:id shape') + :parent-id (:parent-id shape') + :index index + :ignore-touched true + :obj shape'}) + + (ctn/page? component-container) + (assoc :frame-id (:frame-id shape'))))) mod-obj-change (fn [changes shape'] (update changes :redo-changes conj @@ -899,8 +934,8 @@ changes' (reduce add-obj-change changes new-shapes) changes' (update changes' :redo-changes conj {:type :reg-objects - :component-id (:id component) - :shapes all-parents}) + :component-id (:id component) + :shapes all-parents}) changes' (reduce mod-obj-change changes' updated-shapes) changes' (reduce del-obj-change changes' new-shapes)] diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index c3de20ec9..f97dc792c 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -14,12 +14,14 @@ [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] [app.common.spec :as us] + [app.common.types.component :as ctk] [app.common.types.page :as ctp] [app.common.types.shape.interactions :as ctsi] [app.common.uuid :as uuid] [app.main.data.modal :as md] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.collapse :as dwc] + [app.main.data.workspace.libraries-helpers :as dwlh] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.undo :as dwu] [app.main.data.workspace.zoom :as dwz] @@ -330,7 +332,7 @@ (defn prepare-duplicate-changes "Prepare objects to duplicate: generate new id, give them unique names, move to the desired position, and recalculate parents and frames as needed." - [all-objects page ids delta it] + [all-objects page ids delta it libraries] (let [shapes (map (d/getf all-objects) ids) unames (volatile! (cp/retrieve-used-names (:objects page))) update-unames! (fn [new-name] (vswap! unames conj new-name)) @@ -351,7 +353,8 @@ update-unames! ids-map %2 - delta) + delta + libraries) init-changes))] (-> changes @@ -359,11 +362,25 @@ (prepare-duplicate-guides shapes page ids-map delta)))) (defn- prepare-duplicate-shape-change - ([changes objects page unames update-unames! ids-map obj delta] - (prepare-duplicate-shape-change changes objects page unames update-unames! ids-map obj delta (:frame-id obj) (:parent-id obj))) + ([changes objects page unames update-unames! ids-map obj delta libraries] + (prepare-duplicate-shape-change changes objects page unames update-unames! ids-map obj delta libraries (:frame-id obj) (:parent-id obj))) - ([changes objects page unames update-unames! ids-map obj delta frame-id parent-id] - (if (some? obj) + ([changes objects page unames update-unames! ids-map obj delta libraries frame-id parent-id] + (cond + (nil? obj) + changes + + (ctk/main-instance? obj) + (let [[_new-shape changes] + (dwlh/generate-instantiate-component changes + (:component-file obj) + (:component-id obj) + (gpt/point (:x obj) (:y obj)) + page + libraries)] + changes) + + :else (let [frame? (cph/frame-shape? obj) new-id (ids-map (:id obj)) parent-id (or parent-id frame-id) @@ -392,11 +409,11 @@ ids-map child delta + libraries (if frame? new-id frame-id) new-id)) changes - (map (d/getf objects) (:shapes obj)))) - changes))) + (map (d/getf objects) (:shapes obj))))))) (defn- prepare-duplicate-flows [changes shapes page ids-map] @@ -531,7 +548,7 @@ (defn duplicate-selected ([move-delta?] (duplicate-selected move-delta? false)) - ([move-delta? add-group-id?] + ([move-delta? add-undo-group?] (ptk/reify ::duplicate-selected ptk/WatchEvent (watch [it state _] @@ -545,10 +562,12 @@ (calc-duplicate-delta obj state objects) (gpt/point 0 0)) - changes (->> (prepare-duplicate-changes objects page selected delta it) + libraries (wsh/get-libraries state) + + changes (->> (prepare-duplicate-changes objects page selected delta it libraries) (duplicate-changes-update-indices objects selected)) - changes (cond-> changes add-group-id? (assoc :group-id (uuid/random))) + changes (cond-> changes add-undo-group? (assoc :undo-group (uuid/random))) id-original (first selected) diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index 525fc5397..2aed1ff76 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -14,6 +14,7 @@ [app.common.pages.helpers :as cph] [app.common.spec :as us] [app.common.types.component :as ctk] + [app.common.types.container :as ctn] [app.common.types.page :as ctp] [app.common.types.shape :as cts] [app.common.types.shape-tree :as ctst] @@ -171,7 +172,7 @@ ;; not the root. In this case, they must not be deleted, ;; but hidden (to be able to recover them more easily). (let [shape (get objects shape-id) - component-shape (cph/get-component-shape objects shape)] + component-shape (ctn/get-component-shape objects shape)] (and (ctk/in-component-instance? shape) (not= shape component-shape) (not (ctk/main-instance? component-shape))))) @@ -291,7 +292,7 @@ changes (reduce (fn [changes component-id] ;; It's important to delete the component before the main instance, because we ;; need to store the instance position if we want to restore it later. - (pcb/delete-component changes component-id components-v2)) + (pcb/delete-component changes component-id)) changes components-to-delete) diff --git a/frontend/src/app/main/data/workspace/state_helpers.cljs b/frontend/src/app/main/data/workspace/state_helpers.cljs index dd01688d6..9a569fbd1 100644 --- a/frontend/src/app/main/data/workspace/state_helpers.cljs +++ b/frontend/src/app/main/data/workspace/state_helpers.cljs @@ -40,10 +40,6 @@ ([state page-id] (dm/get-in state [:workspace-data :pages-index page-id :options]))) -(defn lookup-component-objects - ([state component-id] - (dm/get-in state [:workspace-data :components component-id :objects]))) - (defn lookup-local-components ([state] (dm/get-in state [:workspace-data :components]))) diff --git a/frontend/src/app/main/data/workspace/undo.cljs b/frontend/src/app/main/data/workspace/undo.cljs index d17b6de88..c9af39d0c 100644 --- a/frontend/src/app/main/data/workspace/undo.cljs +++ b/frontend/src/app/main/data/workspace/undo.cljs @@ -67,11 +67,11 @@ (add-undo-entry state entry)))) (defn- accumulate-undo-entry - [state {:keys [undo-changes redo-changes group-id]}] + [state {:keys [undo-changes redo-changes undo-group]}] (-> state (update-in [:workspace-undo :transaction :undo-changes] #(into undo-changes %)) (update-in [:workspace-undo :transaction :redo-changes] #(into % redo-changes)) - (assoc-in [:workspace-undo :transaction :group-id] group-id))) + (assoc-in [:workspace-undo :transaction :undo-group] undo-group))) (defn append-undo [entry stack?] @@ -79,29 +79,31 @@ (ptk/reify ::append-undo ptk/UpdateEvent (update [_ state] - (cond - (and (get-in state [:workspace-undo :transaction]) - (or (not stack?) - (d/not-empty? (get-in state [:workspace-undo :transaction :undo-changes])) - (d/not-empty? (get-in state [:workspace-undo :transaction :redo-changes])))) - (accumulate-undo-entry state entry) + (cond + (and (get-in state [:workspace-undo :transaction]) + (or (not stack?) + (d/not-empty? (get-in state [:workspace-undo :transaction :undo-changes])) + (d/not-empty? (get-in state [:workspace-undo :transaction :redo-changes])))) + (accumulate-undo-entry state entry) - stack? - (stack-undo-entry state entry) + stack? + (stack-undo-entry state entry) - :else - (add-undo-entry state entry))))) + :else + (add-undo-entry state entry))))) (def empty-tx {:undo-changes [] :redo-changes []}) -(defn start-undo-transaction [id] +(defn start-undo-transaction + "Start a transaction, so that every changes inside are added together in a single undo entry." + [id] (ptk/reify ::start-undo-transaction ptk/UpdateEvent (update [_ state] ;; We commit the old transaction before starting the new one - (let [current-tx (get-in state [:workspace-undo :transaction]) - pending-tx (get-in state [:workspace-undo :transactions-pending])] + (let [current-tx (get-in state [:workspace-undo :transaction]) + pending-tx (get-in state [:workspace-undo :transactions-pending])] (cond-> state (nil? current-tx) (assoc-in [:workspace-undo :transaction] empty-tx) (nil? pending-tx) (assoc-in [:workspace-undo :transactions-pending] #{id}) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 86ca9d33d..b3487c12a 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -208,7 +208,10 @@ data (:workspace-data state)] (-> file (dissoc :data) - (assoc :pages (:pages data))))) + (assoc :options (:options data) + :components (:components data) + :pages (:pages data) + :pages-index (:pages-index data))))) st/state =)) (def workspace-data diff --git a/frontend/src/app/main/render.cljs b/frontend/src/app/main/render.cljs index ff2c24ee8..f50975111 100644 --- a/frontend/src/app/main/render.cljs +++ b/frontend/src/app/main/render.cljs @@ -310,16 +310,16 @@ update-fn #(update %1 %2 gsh/transform-shape (ctm/move-modifiers vector))] (reduce update-fn objects children-ids)))) - root-shape (get objects root-shape-id) - width (* (:width root-shape) zoom) - height (* (:height root-shape) zoom) - vbox (format-viewbox {:width (:width root-shape 0) - :height (:height root-shape 0)}) + root-shape' (get objects root-shape-id) + width (* (:width root-shape') zoom) + height (* (:height root-shape') zoom) + vbox (format-viewbox {:width (:width root-shape' 0) + :height (:height root-shape' 0)}) root-shape-wrapper (mf/use-memo - (mf/deps objects root-shape) + (mf/deps objects root-shape') (fn [] - (case (:type root-shape) + (case (:type root-shape') :group (group-wrapper-factory objects) :frame (frame-wrapper-factory objects))))] @@ -332,9 +332,9 @@ :xmlns:penpot (when include-metadata? "https://penpot.app/xmlns") :fill "none"} - [:> shape-container {:shape root-shape} + [:> shape-container {:shape root-shape'} [:& (mf/provider muc/is-component?) {:value true} - [:& root-shape-wrapper {:shape root-shape :view-box vbox}]]]])) + [:& root-shape-wrapper {:shape root-shape' :view-box vbox}]]]])) (mf/defc object-svg {::mf/wrap [mf/memo]} diff --git a/frontend/src/app/main/ui/dashboard.cljs b/frontend/src/app/main/ui/dashboard.cljs index 7e0c6ab0b..8498c1d97 100644 --- a/frontend/src/app/main/ui/dashboard.cljs +++ b/frontend/src/app/main/ui/dashboard.cljs @@ -243,7 +243,9 @@ (events/unlistenByKey key1)))) (mf/use-effect on-resize) - [:div.dashboard-content {:on-click #(st/emit! (dd/clear-selected-files)) :ref container} + + [:div.dashboard-content {:on-click #(st/emit! (dd/clear-selected-files)) + :ref container} (case section :dashboard-projects [:* diff --git a/frontend/src/app/main/ui/dashboard/grid.cljs b/frontend/src/app/main/ui/dashboard/grid.cljs index eba451495..2e8378033 100644 --- a/frontend/src/app/main/ui/dashboard/grid.cljs +++ b/frontend/src/app/main/ui/dashboard/grid.cljs @@ -105,12 +105,13 @@ [:span.num-assets (str "\u00A0(") (:count components) ")"]] ;; Unicode 00A0 is non-breaking space [:div.asset-list (for [component (:sample components)] - [:div.asset-list-item {:key (str "assets-component-" (:id component))} - [:& component-svg {:group (get-in component [:objects (:id component)]) - :objects (:objects component)}] - [:div.name-block - [:span.item-name {:title (:name component)} - (:name component)]]]) + (let [root-id (or (:main-instance-id component) (:id component))] ;; Check for components-v2 in library + [:div.asset-list-item {:key (str "assets-component-" (:id component))} + [:& component-svg {:root-shape (get-in component [:objects root-id]) + :objects (:objects component)}] ;; Components in the summary come loaded with objects, even in v2 + [:div.name-block + [:span.item-name {:title (:name component)} + (:name component)]]])) (when (> (:count components) (count (:sample components))) [:div.asset-list-item [:div.name-block diff --git a/frontend/src/app/main/ui/workspace/libraries.cljs b/frontend/src/app/main/ui/workspace/libraries.cljs index b2ba31971..fc18741a3 100644 --- a/frontend/src/app/main/ui/workspace/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/libraries.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.libraries (:require [app.common.data :as d] + [app.common.types.components-list :as ctkl] [app.main.data.dashboard :as dd] [app.main.data.modal :as modal] [app.main.data.workspace.libraries :as dwl] @@ -44,7 +45,7 @@ (defn local-library-str [library] - (let [components-count (count (get-in library [:data :components] [])) + (let [components-count (count (or (ctkl/components-seq (:data library)) [])) graphics-count (count (get-in library [:data :media] [])) colors-count (count (get-in library [:data :colors] [])) typography-count (count (get-in library [:data :typographies] []))] diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index d411219c5..db8877310 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -12,7 +12,8 @@ [app.common.pages.helpers :as cph] [app.common.spec :as us] [app.common.text :as txt] - [app.common.types.component :as ctk] + [app.common.types.components-list :as ctkl] + [app.common.types.file :as ctf] [app.config :as cf] [app.main.data.events :as ev] [app.main.data.modal :as modal] @@ -365,13 +366,20 @@ ;;---- Components box ---- (mf/defc components-item - [{:keys [component renaming listing-thumbs? selected-components + [{:keys [component renaming listing-thumbs? selected-components file on-asset-click on-context-menu on-drag-start do-rename cancel-rename selected-components-full selected-components-paths]}] (let [item-ref (mf/use-ref) dragging? (mf/use-state false) workspace-read-only? (mf/use-ctx ctx/workspace-read-only?) + components-v2 (mf/use-ctx ctx/components-v2) + + file (or (:data file) file) + root-shape (ctf/get-component-root file component) + component-container (if components-v2 + (ctf/get-component-page file component) + component) unselect-all (mf/use-fn (fn [] @@ -440,27 +448,29 @@ :on-drag-over on-drag-over :on-drop on-drop} - [:& component-svg {:root-shape (ctk/get-component-root component) - :objects (:objects component)}] - (let [renaming? (= renaming (:id component))] + (when (and (some? root-shape) (some? component-container)) [:* - [:& editable-label - {:class-name (dom/classnames - :cell-name listing-thumbs? - :item-name (not listing-thumbs?) - :editing renaming?) - :value (cph/merge-path-item (:path component) (:name component)) - :tooltip (cph/merge-path-item (:path component) (:name component)) - :display-value (:name component) - :editing? renaming? - :disable-dbl-click? true - :on-change do-rename - :on-cancel cancel-rename}] - (when @dragging? - [:div.dragging])])])) + [:& component-svg {:root-shape root-shape + :objects (:objects component-container)}] + (let [renaming? (= renaming (:id component))] + [:* + [:& editable-label + {:class-name (dom/classnames + :cell-name listing-thumbs? + :item-name (not listing-thumbs?) + :editing renaming?) + :value (cph/merge-path-item (:path component) (:name component)) + :tooltip (cph/merge-path-item (:path component) (:name component)) + :display-value (:name component) + :editing? renaming? + :disable-dbl-click? true + :on-change do-rename + :on-cancel cancel-rename}] + (when @dragging? + [:div.dragging])])])])) (mf/defc components-group - [{:keys [file-id prefix groups open-groups renaming listing-thumbs? selected-components on-asset-click + [{:keys [file prefix groups open-groups renaming listing-thumbs? selected-components on-asset-click on-drag-start do-rename cancel-rename on-rename-group on-group on-ungroup on-context-menu selected-components-full]}] (let [group-open? (get open-groups prefix true) @@ -495,7 +505,7 @@ :on-drag-leave on-drag-leave :on-drag-over on-drag-over :on-drop on-drop} - [:& asset-group-title {:file-id file-id + [:& asset-group-title {:file-id (:id file) :box :components :path prefix :group-open? group-open? @@ -528,6 +538,7 @@ :key (:id component) :renaming renaming :listing-thumbs? listing-thumbs? + :file file :selected-components selected-components :on-asset-click on-asset-click :on-context-menu on-context-menu @@ -539,7 +550,7 @@ :selected-components-paths selected-components-paths}])]) (for [[path-item content] groups] (when-not (empty? path-item) - [:& components-group {:file-id file-id + [:& components-group {:file file :prefix (cph/merge-path-item prefix path-item) :groups content :open-groups open-groups @@ -556,7 +567,7 @@ :selected-components-full selected-components-full}]))])])) (mf/defc components-box - [{:keys [file-id local? components listing-thumbs? open? reverse-sort? open-groups selected-assets + [{:keys [file local? components listing-thumbs? open? reverse-sort? open-groups selected-assets on-asset-click on-assets-delete on-clear-selection] :as props}] (let [input-ref (mf/use-ref nil) state (mf/use-state {:renaming nil @@ -579,14 +590,14 @@ add-component (mf/use-fn (fn [] - #(st/emit! (dwl/set-assets-box-open file-id :components true)) + #(st/emit! (dwl/set-assets-box-open (:id file) :components true)) (dom/click (mf/ref-val input-ref)))) on-file-selected (mf/use-fn - (mf/deps file-id) + (mf/deps file) (fn [blobs] - (let [params {:file-id file-id + (let [params {:file-id (:id file) :blobs (seq blobs)}] (st/emit! (dwm/upload-media-components params) (ptk/event ::ev/event {::ev/name "add-asset-to-library" @@ -598,22 +609,22 @@ (fn [] (let [undo-id (js/Symbol)] (if (empty? selected-components) - (st/emit! (dwl/duplicate-component {:id (:component-id @state)})) - (do - (st/emit! (dwu/start-undo-transaction undo-id)) - (apply st/emit! (map #(dwl/duplicate-component {:id %}) selected-components)) - (st/emit! (dwu/commit-undo-transaction undo-id))))))) + (st/emit! (dwl/duplicate-component (:id file) (:component-id @state))) + (do + (st/emit! (dwu/start-undo-transaction undo-id)) + (apply st/emit! (map (partial dwl/duplicate-component (:id file)) selected-components)) + (st/emit! (dwu/commit-undo-transaction undo-id))))))) on-delete (mf/use-fn - (mf/deps @state file-id multi-components? multi-assets?) + (mf/deps @state file multi-components? multi-assets?) (fn [] (let [undo-id (js/Symbol)] (if (or multi-components? multi-assets?) (on-assets-delete) (st/emit! (dwu/start-undo-transaction undo-id) (dwl/delete-component {:id (:component-id @state)}) - (dwl/sync-file file-id file-id :components (:component-id @state)) + (dwl/sync-file (:id file) (:id file) :components (:component-id @state)) (dwu/commit-undo-transaction undo-id)))))) on-rename @@ -716,7 +727,7 @@ on-drag-start (mf/use-fn (fn [component event] - (dnd/set-data! event "penpot/component" {:file-id file-id + (dnd/set-data! event "penpot/component" {:file-id (:id file) :component component}) (dnd/set-allowed-effect! event "move"))) @@ -734,7 +745,7 @@ (when (and main-instance-id main-instance-page) ;; Only when :components-v2 is enabled (st/emit! (dw/go-to-main-instance main-instance-page main-instance-id))))))] - [:& asset-section {:file-id file-id + [:& asset-section {:file-id (:id file) :title (tr "workspace.assets.components") :box :components :assets-count (count components) @@ -750,7 +761,7 @@ :on-selected on-file-selected}]])]) [:& asset-section-block {:role :content} - [:& components-group {:file-id file-id + [:& components-group {:file file :prefix "" :groups groups :open-groups open-groups @@ -1931,8 +1942,8 @@ (l/derived (fn [state] (let [wfile (:workspace-data state)] (if (= (:id wfile) id) - (vals (get wfile :components)) - (vals (get-in state [:workspace-libraries id :data :components]))))) + (ctkl/components-seq wfile) + (ctkl/components-seq (get-in state [:workspace-libraries id :data]))))) st/state =)) (defn file-typography-ref @@ -2158,7 +2169,7 @@ i/listing-thumbs)]] (when show-components? - [:& components-box {:file-id (:id file) + [:& components-box {:file file :local? local? :components components :listing-thumbs? listing-thumbs? diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs index 0c3238ea9..d76b465f3 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs @@ -6,7 +6,7 @@ (ns app.main.ui.workspace.sidebar.options.menus.component (:require - [app.common.types.components-list :as ctkl] + [app.common.types.components-list :as ctkl] [app.common.types.file :as ctf] [app.main.data.modal :as modal] [app.main.data.workspace :as dw] diff --git a/frontend/src/app/render.cljs b/frontend/src/app/render.cljs index 69ea2ecd2..96273a902 100644 --- a/frontend/src/app/render.cljs +++ b/frontend/src/app/render.cljs @@ -11,6 +11,7 @@ [app.common.logging :as l] [app.common.math :as mth] [app.common.spec :as us] + [app.common.types.components-list :as ctkl] [app.common.uri :as u] [app.main.data.fonts :as df] [app.main.features :as features] @@ -247,7 +248,7 @@ :border "0px solid black"}]]])] [:ul.nav - (for [[id data] (get-in file [:data :components])] + (for [[id data] (ctkl/components (:data file))] (let [on-click (fn [event] (dom/prevent-default event) (swap! state assoc :component-id id))] diff --git a/frontend/src/app/worker/export.cljs b/frontend/src/app/worker/export.cljs index 325d19c14..08f15742b 100644 --- a/frontend/src/app/worker/export.cljs +++ b/frontend/src/app/worker/export.cljs @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.media :as cm] [app.common.text :as ct] + [app.common.types.components-list :as ctkl] [app.config :as cfg] [app.main.render :as r] [app.main.repo :as rp] @@ -51,7 +52,7 @@ :version current-version :libraries (->> (:libraries file) (into #{}) (mapv str)) :exportType (d/name export-type) - :hasComponents (d/not-empty? (get-in file [:data :components])) + :hasComponents (d/not-empty? (ctkl/components-seq (:data file))) :hasDeletedComponents (d/not-empty? (get-in file [:data :deleted-components])) :hasMedia (d/not-empty? (get-in file [:data :media])) :hasColors (d/not-empty? (get-in file [:data :colors])) @@ -329,7 +330,7 @@ (select-keys (get external-refs (:id file)))) media (-> (get-in file [:data :media]) (select-keys (get external-refs (:id file)))) - components (-> (get-in file [:data :components]) + components (-> (ctkl/components (:data file)) (select-keys (get external-refs (:id file))))] (cond-> target (d/not-empty? colors) @@ -434,7 +435,7 @@ components-stream (->> files-stream (rx/flat-map vals) - (rx/filter #(d/not-empty? (get-in % [:data :components]))) + (rx/filter #(d/not-empty? (ctkl/components-seq (:data %)))) (rx/flat-map parse-library-components)) deleted-components-stream diff --git a/frontend/test/frontend_tests/helpers/libraries.cljs b/frontend/test/frontend_tests/helpers/libraries.cljs index c00776136..359667a31 100644 --- a/frontend/test/frontend_tests/helpers/libraries.cljs +++ b/frontend/test/frontend_tests/helpers/libraries.cljs @@ -7,15 +7,37 @@ (ns frontend-tests.helpers.libraries (:require [app.common.pages.helpers :as cph] - [app.common.types.component :as ctk] [app.common.types.container :as ctn] + [app.common.types.file :as ctf] [app.main.data.workspace.state-helpers :as wsh] - [cljs.pprint :refer [pprint]] [cljs.test :as t :include-macros true] [frontend-tests.helpers.pages :as thp])) ;; ---- Helpers to manage libraries and synchronization +(defn is-main-instance-root + [shape] + (t/is (nil? (:shape-ref shape))) + (t/is (some? (:component-id shape))) + (t/is (= (:component-root? shape) true))) + +(defn is-main-instance-subroot + [shape] + (t/is (some? (:component-id shape))) ; shape-ref may or may be not nil + (t/is (= (:component-root? shape) true))) + +(defn is-main-instance-child + [shape] + (t/is (nil? (:component-id shape))) ; shape-ref may or may be not nil + (t/is (nil? (:component-file shape))) + (t/is (nil? (:component-root? shape)))) + +(defn is-main-instance-inner + [shape] + (if (some? (:component-id shape)) + (is-main-instance-subroot shape) + (is-main-instance-child shape))) + (defn is-instance-root [shape] (t/is (some? (:shape-ref shape))) @@ -58,27 +80,25 @@ (defn resolve-instance "Get the shape with the given id and all its children, and verify that they are a well constructed instance tree." - [state root-inst-id] - (let [page (thp/current-page state) - root-inst (ctn/get-shape page root-inst-id) - shapes-inst (cph/get-children-with-self (:objects page) - root-inst-id)] - (is-instance-root (first shapes-inst)) - (run! is-instance-inner (rest shapes-inst)) + [state root-id] + (let [page (thp/current-page state) + shapes (cph/get-children-with-self (:objects page) + root-id)] + (is-instance-root (first shapes)) + (run! is-instance-inner (rest shapes)) - shapes-inst)) + shapes)) (defn resolve-noninstance "Get the shape with the given id and all its children, and verify that they are not a component instance." - [state root-inst-id] - (let [page (thp/current-page state) - root-inst (ctn/get-shape page root-inst-id) - shapes-inst (cph/get-children-with-self (:objects page) - root-inst-id)] - (run! is-noninstance shapes-inst) + [state root-id] + (let [page (thp/current-page state) + shapes (cph/get-children-with-self (:objects page) + root-id)] + (run! is-noninstance shapes) - shapes-inst)) + shapes)) (defn resolve-instance-and-main "Get the shape with the given id and all its children, and also @@ -87,38 +107,39 @@ (resolve-instance-and-main state root-inst-id false)) ([state root-inst-id subinstance?] - (let [page (thp/current-page state) - root-inst (ctn/get-shape page root-inst-id) + (let [page (thp/current-page state) + root-inst (ctn/get-shape page root-inst-id) + main-instance? (:main-instance? root-inst) - libs (wsh/get-libraries state) - component (cph/get-component libs (:component-id root-inst)) + libs (wsh/get-libraries state) + component (ctf/get-component libs (:component-id root-inst)) + library (ctf/get-component-library libs root-inst) - shapes-inst (cph/get-children-with-self (:objects page) root-inst-id) - shapes-main (cph/get-children-with-self (:objects component) (:shape-ref root-inst)) - - unique-refs (into #{} (map :shape-ref) shapes-inst) + shapes-inst (cph/get-children-with-self (:objects page) root-inst-id) + shapes-main (ctf/get-component-shapes (:data library) component) + unique-refs (into #{} (map :shape-ref) shapes-inst) main-exists? (fn [shape] - (let [component-shape - (cph/get-component-shape (:objects page) shape) - - component - (cph/get-component libs (:component-id component-shape)) - - main-shape - (ctn/get-shape component (:shape-ref shape))] - + (let [main-shape (ctf/get-ref-shape (:data library) component shape)] (t/is (some? main-shape))))] ;; Validate that the instance tree is well constructed - (if subinstance? - (is-instance-subroot (first shapes-inst)) - (is-instance-root (first shapes-inst))) - (run! is-instance-inner (rest shapes-inst)) - (t/is (= (count shapes-inst) - (count shapes-main) - (count unique-refs))) - (run! main-exists? shapes-inst) + (if main-instance? + (do + (if subinstance? + (is-main-instance-subroot (first shapes-inst)) + (is-main-instance-root (first shapes-inst))) + (run! is-main-instance-inner (rest shapes-inst))) + (do + (if subinstance? + (is-instance-subroot (first shapes-inst)) + (is-instance-root (first shapes-inst))) + (run! is-instance-inner (rest shapes-inst)))) + + (t/is (= (count shapes-inst) (count shapes-main))) + (when-not main-instance? + (t/is (= (count shapes-inst) (count unique-refs))) + (run! main-exists? shapes-inst)) [shapes-inst shapes-main component]))) @@ -131,24 +152,11 @@ root-inst (ctn/get-shape page root-inst-id) libs (wsh/get-libraries state) - component (cph/get-component libs (:component-id root-inst)) + component (ctf/get-component libs (:component-id root-inst)) + library (ctf/get-component-library libs root-inst) shapes-inst (cph/get-children-with-self (:objects page) root-inst-id) - shapes-main (cph/get-children-with-self (:objects component) (:shape-ref root-inst)) - - unique-refs (into #{} (map :shape-ref) shapes-inst) - - main-exists? (fn [shape] - (let [component-shape - (cph/get-component-shape (:objects page) shape) - - component - (cph/get-component libs (:component-id component-shape)) - - main-shape - (ctn/get-shape component (:shape-ref shape))] - - (t/is (some? main-shape))))] + shapes-main (ctf/get-component-shapes (:data library) component)] ;; Validate that the instance tree is well constructed (is-instance-root (first shapes-inst)) @@ -158,14 +166,12 @@ (defn resolve-component "Get the component with the given id and all its shapes." [state component-id] - (let [page (thp/current-page state) - libs (wsh/get-libraries state) - component (cph/get-component libs component-id) - root-main (ctk/get-component-root component) - shapes-main (cph/get-children-with-self (:objects component) (:id root-main))] + (let [libs (wsh/get-libraries state) + component (ctf/get-component libs component-id) + library (ctf/get-component-library libs component) + shapes-main (ctf/get-component-shapes (:data library) component)] ;; Validate that the component tree is well constructed (run! is-noninstance shapes-main) [shapes-main component])) - diff --git a/frontend/test/frontend_tests/helpers/pages.cljs b/frontend/test/frontend_tests/helpers/pages.cljs index 6129eea42..94cd86ec7 100644 --- a/frontend/test/frontend_tests/helpers/pages.cljs +++ b/frontend/test/frontend_tests/helpers/pages.cljs @@ -7,20 +7,15 @@ (ns frontend-tests.helpers.pages (:require [app.common.geom.point :as gpt] - [app.common.geom.shapes :as gsh] [app.common.pages :as cp] + [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] [app.common.types.shape :as cts] [app.common.uuid :as uuid] - [app.main.data.workspace :as dw] [app.main.data.workspace.groups :as dwg] [app.main.data.workspace.layout :as layout] [app.main.data.workspace.libraries-helpers :as dwlh] - [app.main.data.workspace.state-helpers :as wsh] - [beicon.core :as rx] - [cljs.pprint :refer [pprint]] - [cljs.test :as t :include-macros true] - [potok.core :as ptk])) + [app.main.data.workspace.state-helpers :as wsh])) ;; ---- Helpers to manage pages and objects @@ -32,10 +27,12 @@ :workspace-layout layout/default-layout :workspace-global layout/default-global :workspace-data {:id current-file-id + :options {:components-v2 true} :components {} :pages [] :pages-index {}} - :workspace-libraries {}}) + :workspace-libraries {} + :features {:components-v2 true}}) (def ^:private idmap (atom {})) @@ -56,6 +53,11 @@ (let [page (current-page state)] (get-in page [:objects (id label)]))) +(defn get-children + [state label] + (let [page (current-page state)] + (cph/get-children (:objects page) (id label)))) + (defn sample-page ([state] (sample-page state {})) ([state {:keys [id name] :as props @@ -106,16 +108,17 @@ objects (wsh/lookup-page-objects state (:id page)) shapes (dwg/shapes-for-grouping objects shape-ids) - [group component-root changes] + [group component-id changes] (dwlh/generate-add-component nil shapes (:objects page) (:id page) current-file-id - true)] + true + dwg/prepare-create-group)] (swap! idmap assoc instance-label (:id group) - component-label (:id component-root)) + component-label component-id) (update state :workspace-data cp/process-changes (:redo-changes changes)))) @@ -126,8 +129,10 @@ (let [page (current-page state) libraries (wsh/get-libraries state) + changes (pcb/empty-changes nil (:id page)) + [new-shape changes] - (dwlh/generate-instantiate-component nil + (dwlh/generate-instantiate-component changes file-id component-id (gpt/point 100 100) @@ -148,7 +153,9 @@ assoc library-id {:id library-id :name name :data {:id library-id + :options (:options data) + :pages (:pages data) + :pages-index (:pages-index data) :components (:components data)}}) (update :workspace-data assoc :components {} :pages [] :pages-index {})))) - diff --git a/frontend/test/frontend_tests/state_components_sync_test.cljs b/frontend/test/frontend_tests/state_components_sync_test.cljs index 538a809e9..9517c93be 100644 --- a/frontend/test/frontend_tests/state_components_sync_test.cljs +++ b/frontend/test/frontend_tests/state_components_sync_test.cljs @@ -3,27 +3,19 @@ ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; ;; Copyright (c) KALEIDOS INC - + (ns frontend-tests.state-components-sync-test (:require [app.common.colors :as clr] - [app.common.data :as d] - [app.common.geom.point :as gpt] - [app.common.pages.helpers :as cph] - [app.common.types.container :as ctn] [app.main.data.workspace :as dw] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.libraries :as dwl] - [app.main.data.workspace.libraries-helpers :as dwlh] [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.state-helpers :as wsh] - [beicon.core :as rx] - [cljs.pprint :refer [pprint]] [cljs.test :as t :include-macros true] [frontend-tests.helpers.events :as the] [frontend-tests.helpers.libraries :as thl] [frontend-tests.helpers.pages :as thp] - [linked.core :as lks] [potok.core :as ptk])) (t/use-fixtures :each @@ -33,848 +25,871 @@ (t/deftest test-touched (t/async done - (try - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1" - :fill-color clr/white - :fill-opacity 1}) - (thp/make-component :instance1 :component-1 - [(thp/id :shape1)])) + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1" + :fill-color clr/white + :fill-opacity 1}) + (thp/make-component :main1 :component1 + [(thp/id :shape1)]) + (thp/instantiate-component :instance1 + (thp/id :component1))) - shape1 (thp/get-shape state :shape1) + [_group1 shape1'] + (thl/resolve-instance state (thp/id :instance1)) - update-fn (fn [shape] - (merge shape {:fill-color clr/test - :fill-opacity 0.5})) + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1 + ; Rect 1 + ; Rect 1 #--> Rect 1 + ; Rect 1* ---> Rect 1 + ; #{:fill-group} + ; + ; [Rect 1] + ; page1 / Rect 1 + ; + (let [[[group shape1] [c-group c-shape1] _component] + (thl/resolve-instance-and-main + new-state + (thp/id :instance1))] - store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Rect 1 #--> Rect 1 - ; Rect 1* ---> Rect 1 (color, opacity) - ; #{:fill-group} - ; - ; [Rect 1] - ; Rect 1 - ; Rect 1 - ; - (let [[[group shape1] [c-group c-shape1] component] - (thl/resolve-instance-and-main - new-state - (thp/id :instance1))] + (t/is (= (:name group) "Rect 1")) + (t/is (= (:touched group) nil)) + (t/is (= (:name shape1) "Rect 1")) + (t/is (= (:touched shape1) #{:fill-group})) + (t/is (= (:fill-color shape1) clr/test)) + (t/is (= (:fill-opacity shape1) 0.5)) - (t/is (= (:name group) "Rect 1")) - (t/is (= (:touched group) nil)) - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:touched shape1) #{:fill-group})) - (t/is (= (:fill-color shape1) clr/test)) - (t/is (= (:fill-opacity shape1) 0.5)) - - (t/is (= (:name c-group) "Rect 1")) - (t/is (= (:touched c-group) nil)) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:touched c-shape1) nil)) - (t/is (= (:fill-color c-shape1) clr/white)) - (t/is (= (:fill-opacity c-shape1) 1)))))] - - (ptk/emit! - store - (dch/update-shapes [(:id shape1)] update-fn) - :the/end))))) + (t/is (= (:name c-group) "Rect 1")) + (t/is (= (:touched c-group) nil)) + (t/is (= (:name c-shape1) "Rect 1")) + (t/is (= (:touched c-shape1) nil)) + (t/is (= (:fill-color c-shape1) clr/white)) + (t/is (= (:fill-opacity c-shape1) 1)))))] + (ptk/emit! + store + (dch/update-shapes [(:id shape1')] + (fn [shape] + (merge shape {:fill-color clr/test + :fill-opacity 0.5}))) + :the/end)))) + (t/deftest test-touched-children-add (t/async done - (try - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1" - :fill-color clr/white - :fill-opacity 1}) - (thp/make-component :instance1 :component-1 - [(thp/id :shape1)]) - (thp/sample-shape :shape2 :circle - {:name "Circle 1"})) + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1" + :fill-color clr/white + :fill-opacity 1}) + (thp/make-component :main1 :component1 + [(thp/id :shape1)]) + (thp/instantiate-component :instance1 + (thp/id :component1)) + (thp/sample-shape :shape2 :circle + {:name "Circle 1"})) - instance1 (thp/get-shape state :instance1) - shape2 (thp/get-shape state :shape2) + instance1 (thp/get-shape state :instance1) + shape2 (thp/get-shape state :shape2) - store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Rect 1 * #--> Rect 1 - ; #{:shapes-group} - ; Circle 1 - ; Rect 1 ---> Rect 1 - ; - ; [Rect 1] - ; Rect 1 - ; Rect 1 - ; - (let [[[group shape1 shape2] [c-group c-shape1] component] - (thl/resolve-instance-and-main-allow-dangling - new-state - (thp/id :instance1))] + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1 + ; Rect 1 + ; Rect 1* #--> Rect 1 + ; #{:shapes-group} + ; Circle 1 + ; Rect 1 ---> Rect 1 + ; + ; [Rect 1] + ; page1 / Rect 1 + ; + (let [[[group shape1 shape2] [c-group c-shape1] _component] + (thl/resolve-instance-and-main-allow-dangling + new-state + (thp/id :instance1))] - (t/is (= (:name group) "Rect 1")) - (t/is (= (:touched group) #{:shapes-group})) - (t/is (= (:name shape1) "Circle 1")) - (t/is (= (:touched shape1) nil)) - (t/is (= (:shape-ref shape1) nil)) - (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:touched shape2) nil)) - (t/is (not= (:shape-ref shape2) nil)) + (t/is (= (:name group) "Rect 1")) + (t/is (= (:touched group) #{:shapes-group})) + (t/is (= (:name shape1) "Circle 1")) + (t/is (= (:touched shape1) nil)) + (t/is (= (:shape-ref shape1) nil)) + (t/is (= (:name shape2) "Rect 1")) + (t/is (= (:touched shape2) nil)) + (t/is (not= (:shape-ref shape2) nil)) - (t/is (= (:name c-group) "Rect 1")) - (t/is (= (:touched c-group) nil)) - (t/is (= (:shape-ref c-group) nil)) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:touched c-shape1) nil)) - (t/is (= (:shape-ref c-shape1) nil)))))] + (t/is (= (:name c-group) "Rect 1")) + (t/is (= (:touched c-group) nil)) + (t/is (= (:shape-ref c-group) nil)) + (t/is (= (:name c-shape1) "Rect 1")) + (t/is (= (:touched c-shape1) nil)) + (t/is (= (:shape-ref c-shape1) nil)))))] - (ptk/emit! - store - (dw/relocate-shapes #{(:id shape2)} (:id instance1) 0) - :the/end))))) + (ptk/emit! + store + (dw/relocate-shapes #{(:id shape2)} (:id instance1) 0) + :the/end)))) (t/deftest test-touched-children-delete (t/async done - (try - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1"}) - (thp/sample-shape :shape2 :rect - {:name "Rect 2"}) - (thp/make-component :instance1 :component-1 - [(thp/id :shape1) - (thp/id :shape2)])) + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1"}) + (thp/sample-shape :shape2 :rect + {:name "Rect 2"}) + (thp/make-component :main1 :component1 + [(thp/id :shape1) + (thp/id :shape2)]) + (thp/instantiate-component :instance1 + (thp/id :component1))) - shape1 (thp/get-shape state :shape1) + [_group1 shape1'] + (thl/resolve-instance state (thp/id :instance1)) - store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Component 1* #--> Component 1 - ; #{:shapes-group} - ; Rect 2 ---> Rect 2 - ; - ; [Component 1] - ; Component 1 - ; Rect 1 - ; Rect 2 - ; - (let [[[group shape2] [c-group c-shape2 c-shape3] component] - (thl/resolve-instance-and-main-allow-dangling - new-state - (thp/id :instance1))] + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Component 1 + ; Rect 1 + ; Rect 2 + ; Component 1 #--> Component 1 + ; Rect 1* ---> Rect 1 + ; #{:visibility-group} + ; Rect 2 ---> Rect 2 + ; + ; [Component 1] + ; page1 / Component 1 + ; + (let [[[group shape1 shape2] [c-group c-shape1 c-shape2] _component] + (thl/resolve-instance-and-main-allow-dangling + new-state + (thp/id :instance1))] - (t/is (= (:name group) "Component 1")) - (t/is (= (:touched group) #{:shapes-group})) - (t/is (not= (:shape-ref group) nil)) - (t/is (= (:name shape2) "Rect 2")) - (t/is (= (:touched shape2) nil)) - (t/is (not= (:shape-ref shape2) nil)) + (t/is (= (:name group) "Component 1")) + (t/is (= (:touched group) nil)) + (t/is (not= (:shape-ref group) nil)) + (t/is (= (:name shape1) "Rect 1")) + (t/is (= (:hidden shape1) true)) ; Instance shapes are not deleted but hidden + (t/is (= (:touched shape1) #{:visibility-group})) + (t/is (not= (:shape-ref shape1) nil)) + (t/is (= (:name shape2) "Rect 2")) + (t/is (= (:touched shape2) nil)) + (t/is (not= (:shape-ref shape2) nil)) - (t/is (= (:name c-group) "Component 1")) - (t/is (= (:touched c-group) nil)) - (t/is (= (:shape-ref c-group) nil)) - (t/is (= (:name c-shape2) "Rect 1")) - (t/is (= (:touched c-shape2) nil)) - (t/is (= (:shape-ref c-shape2) nil)) - (t/is (= (:name c-shape3) "Rect 2")) - (t/is (= (:touched c-shape3) nil)) - (t/is (= (:shape-ref c-shape3) nil)))))] + (t/is (= (:name c-group) "Component 1")) + (t/is (= (:touched c-group) nil)) + (t/is (= (:shape-ref c-group) nil)) + (t/is (= (:name c-shape1) "Rect 1")) + (t/is (= (:touched c-shape1) nil)) + (t/is (= (:shape-ref c-shape1) nil)) + (t/is (= (:name c-shape2) "Rect 2")) + (t/is (= (:touched c-shape2) nil)) + (t/is (= (:shape-ref c-shape2) nil)))))] - (ptk/emit! - store - (dwsh/delete-shapes #{(:id shape1)}) - :the/end))))) + (ptk/emit! + store + (dwsh/delete-shapes #{(:id shape1')}) + :the/end)))) (t/deftest test-touched-children-move (t/async done - (try - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1"}) - (thp/sample-shape :shape2 :rect - {:name "Rect 2"}) - (thp/sample-shape :shape3 :rect - {:name "Rect 3"}) - (thp/make-component :instance1 :component-1 - [(thp/id :shape1) - (thp/id :shape2) - (thp/id :shape3)])) + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1"}) + (thp/sample-shape :shape2 :rect + {:name "Rect 2"}) + (thp/sample-shape :shape3 :rect + {:name "Rect 3"}) + (thp/make-component :main1 :component1 + [(thp/id :shape1) + (thp/id :shape2) + (thp/id :shape3)]) + (thp/instantiate-component :instance1 + (thp/id :component1))) - shape1 (thp/get-shape state :shape1) - instance1 (thp/get-shape state :instance1) + [group1' shape1'] + (thl/resolve-instance state (thp/id :instance1)) - store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Component 1* #--> Component 1 - ; #{:shapes-group} - ; Rect 2 ---> Rect 2 - ; Rect 1 ---> Rect 1 - ; Rect 3 ---> Rect 3 - ; - ; [Component 1] - ; Component 1 - ; Rect 1 - ; Rect 2 - ; Rect 3 - ; - (let [[[group shape1 shape2 shape3] - [c-group c-shape1 c-shape2 c-shape3] component] - (thl/resolve-instance-and-main-allow-dangling - new-state - (thp/id :instance1))] + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Component 1 + ; Rect 1 + ; Rect 2 + ; Rect 3 + ; Component 1* #--> Component 1 + ; #{:shapes-group} + ; Rect 2 ---> Rect 2 + ; Rect 1 ---> Rect 1 + ; Rect 3 ---> Rect 3 + ; + ; [Component 1] + ; page1 / Component 1 + ; + (let [[[group shape1 shape2 shape3] + [c-group c-shape1 c-shape2 c-shape3] _component] + (thl/resolve-instance-and-main-allow-dangling + new-state + (thp/id :instance1))] - (t/is (= (:name group) "Component 1")) - (t/is (= (:touched group) #{:shapes-group})) - (t/is (= (:name shape1) "Rect 2")) - (t/is (= (:touched shape1) nil)) - (t/is (not= (:shape-ref shape1) nil)) - (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:touched shape2) nil)) - (t/is (not= (:shape-ref shape2) nil)) - (t/is (= (:name shape3) "Rect 3")) - (t/is (= (:touched shape3) nil)) - (t/is (not= (:shape-ref shape3) nil)) + (t/is (= (:name group) "Component 1")) + (t/is (= (:touched group) #{:shapes-group})) + (t/is (= (:name shape1) "Rect 2")) + (t/is (= (:touched shape1) nil)) + (t/is (not= (:shape-ref shape1) nil)) + (t/is (= (:name shape2) "Rect 1")) + (t/is (= (:touched shape2) nil)) + (t/is (not= (:shape-ref shape2) nil)) + (t/is (= (:name shape3) "Rect 3")) + (t/is (= (:touched shape3) nil)) + (t/is (not= (:shape-ref shape3) nil)) - (t/is (= (:name c-group) "Component 1")) - (t/is (= (:touched c-group) nil)) - (t/is (= (:shape-ref c-group) nil)) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:touched c-shape1) nil)) - (t/is (= (:shape-ref c-shape1) nil)) - (t/is (= (:name c-shape2) "Rect 2")) - (t/is (= (:touched c-shape2) nil)) - (t/is (= (:shape-ref c-shape2) nil)) - (t/is (= (:name c-shape3) "Rect 3")) - (t/is (= (:touched c-shape3) nil)) - (t/is (= (:shape-ref c-shape3) nil)))))] + (t/is (= (:name c-group) "Component 1")) + (t/is (= (:touched c-group) nil)) + (t/is (= (:shape-ref c-group) nil)) + (t/is (= (:name c-shape1) "Rect 1")) + (t/is (= (:touched c-shape1) nil)) + (t/is (= (:shape-ref c-shape1) nil)) + (t/is (= (:name c-shape2) "Rect 2")) + (t/is (= (:touched c-shape2) nil)) + (t/is (= (:shape-ref c-shape2) nil)) + (t/is (= (:name c-shape3) "Rect 3")) + (t/is (= (:touched c-shape3) nil)) + (t/is (= (:shape-ref c-shape3) nil)))))] - (ptk/emit! - store - (dw/relocate-shapes #{(:id shape1)} (:id instance1) 2) - :the/end))))) + (ptk/emit! + store + (dw/relocate-shapes #{(:id shape1')} (:id group1') 2) + :the/end)))) (t/deftest test-touched-from-lib - (t/async done - (try - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1" - :fill-color clr/white - :fill-opacity 1}) - (thp/make-component :instance1 :component-1 - [(thp/id :shape1)]) - (thp/move-to-library :lib1 "Library 1") - (thp/sample-page) - (thp/instantiate-component :instance2 - (thp/id :component-1) - (thp/id :lib1))) + (t/async + done + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1" + :fill-color clr/white + :fill-opacity 1}) + (thp/make-component :main1 :component1 + [(thp/id :shape1)]) + (thp/move-to-library :lib1 "Library 1") + (thp/sample-page) + (thp/instantiate-component :instance1 + (thp/id :component1) + (thp/id :lib1))) - [instance2 shape2] - (thl/resolve-instance state (thp/id :instance2)) + [_group1 shape1'] + (thl/resolve-instance state (thp/id :instance1)) - update-fn (fn [shape] - (merge shape {:fill-color clr/test - :fill-opacity 0.5})) + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1 #--> Rect 1 + ; Rect 1* ---> Rect 1 + ; #{:fill-group} + ; + (let [[[group shape1] [c-group c-shape1] _component] + (thl/resolve-instance-and-main + new-state + (thp/id :instance1))] - store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Rect 1 #--> Rect 1 - ; Rect 1* ---> Rect 1 (color, opacity) - ; #{:fill-group} - ; - (let [[[group shape1] [c-group c-shape1] component] - (thl/resolve-instance-and-main - new-state - (thp/id :instance2))] + (t/is (= (:name group) "Rect 1")) + (t/is (= (:touched group) nil)) + (t/is (= (:name shape1) "Rect 1")) + (t/is (= (:touched shape1) #{:fill-group})) + (t/is (= (:fill-color shape1) clr/test)) + (t/is (= (:fill-opacity shape1) 0.5)) - (t/is (= (:name group) "Rect 1")) - (t/is (= (:touched group) nil)) - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:touched shape1) #{:fill-group})) - (t/is (= (:fill-color shape1) clr/test)) - (t/is (= (:fill-opacity shape1) 0.5)) + (t/is (= (:name c-group) "Rect 1")) + (t/is (= (:touched c-group) nil)) + (t/is (= (:name c-shape1) "Rect 1")) + (t/is (= (:touched c-shape1) nil)) + (t/is (= (:fill-color c-shape1) clr/white)) + (t/is (= (:fill-opacity c-shape1) 1)))))] - (t/is (= (:name c-group) "Rect 1")) - (t/is (= (:touched c-group) nil)) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:touched c-shape1) nil)) - (t/is (= (:fill-color c-shape1) clr/white)) - (t/is (= (:fill-opacity c-shape1) 1)))))] - - (ptk/emit! - store - (dch/update-shapes [(:id shape2)] update-fn) - :the/end))))) + (ptk/emit! + store + (dch/update-shapes [(:id shape1')] + (fn [shape] + (merge shape {:fill-color clr/test + :fill-opacity 0.5}))) + :the/end)))) (t/deftest test-touched-nested-upper - (t/async done - (try - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1" - :fill-color clr/white - :fill-opacity 1}) - (thp/make-component :instance1 :component-1 - [(thp/id :shape1)]) - (thp/sample-shape :shape2 :circle - {:name "Circle 1" - :fill-color clr/black - :fill-opacity 0}) - (thp/group-shapes :group1 - [(thp/id :instance1) - (thp/id :shape2)]) - (thp/make-component :instance2 :component-2 - [(thp/id :group1)])) + (t/async + done + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1" + :fill-color clr/white + :fill-opacity 1}) + (thp/make-component :main1 :component1 + [(thp/id :shape1)]) + (thp/instantiate-component :instance1 + (thp/id :component1)) + (thp/sample-shape :shape2 :circle + {:name "Circle 1" + :fill-color clr/black + :fill-opacity 0}) + (thp/group-shapes :group1 + [(thp/id :instance1) + (thp/id :shape2)]) + (thp/make-component :main2 :component2 + [(thp/id :group1)]) + (thp/instantiate-component :instance2 + (thp/id :component2))) - [instance2 instance1 shape1 shape2] - (thl/resolve-instance state (thp/id :instance2)) + [_instance2 _instance1 shape1' _shape2'] + (thl/resolve-instance state (thp/id :instance2)) - update-fn (fn [shape] - (merge shape {:fill-color clr/test - :fill-opacity 0.5})) + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1 + ; Rect 1 + ; Group + ; Rect 1 #--> Rect 1 + ; Rect 1 ---> Rect 1 + ; Circle 1 + ; Group #--> Group + ; Rect 1 @--> Rect 1 + ; Rect 1 ---> Rect 1 + ; Circle 1* ---> Circle 1 + ; #{:fill-group} + ; + ; [Rect 1] + ; page1 / Rect 1 + ; + ; [Group] + ; page1 / Group + ; + (let [[[instance2 instance1 shape1 shape2] + [c-instance2 c-instance1 c-shape1 c-shape2] _component] + (thl/resolve-instance-and-main + new-state + (thp/id :instance2))] - store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Group * #--> Group - ; Rect 1 @--> Rect 1 - ; Rect 1 ---> Rect 1 - ; Circle 1* ---> Circle 1 (color, opacity) - ; #{:fill-group} - ; - ; [Rect 1] - ; Rect 1 - ; Rect 1 - ; - ; [Group] - ; Group - ; Rect 1 @--> Rect 1 - ; Rect 1 ---> Rect 1 - ; Circle 1 - ; - (let [[[instance2 instance1 shape1 shape2] - [c-instance2 c-instance1 c-shape1 c-shape2] component] - (thl/resolve-instance-and-main - new-state - (thp/id :instance2))] + (t/is (= (:name instance2) "Group")) + (t/is (= (:touched instance2) nil)) + (t/is (= (:name instance1) "Rect 1")) + (t/is (= (:touched instance1) nil)) + (t/is (= (:name shape1) "Circle 1")) + (t/is (= (:touched shape1) #{:fill-group})) + (t/is (= (:fill-color shape1) clr/test)) + (t/is (= (:fill-opacity shape1) 0.5)) + (t/is (= (:name shape2) "Rect 1")) + (t/is (= (:touched shape2) nil)) + (t/is (= (:fill-color shape2) clr/white)) + (t/is (= (:fill-opacity shape2) 1)) - ; TODO: get and check the instance inside component [Group 1] + (t/is (= (:name c-instance2) "Group")) + (t/is (= (:touched c-instance2) nil)) + (t/is (= (:name c-instance1) "Rect 1")) + (t/is (= (:touched c-instance1) nil)) + (t/is (= (:name c-shape1) "Circle 1")) + (t/is (= (:touched c-shape1) nil)) + (t/is (= (:fill-color c-shape1) clr/black)) + (t/is (= (:fill-opacity c-shape1) 0)) + (t/is (= (:name c-shape2) "Rect 1")) + (t/is (= (:touched c-shape2) nil)) + (t/is (= (:fill-color c-shape2) clr/white)) + (t/is (= (:fill-opacity c-shape2) 1)))))] - (t/is (= (:name instance2) "Group")) - (t/is (= (:touched instance2) nil)) - (t/is (= (:name instance1) "Rect 1")) - (t/is (= (:touched instance1) nil)) - (t/is (= (:name shape1) "Circle 1")) - (t/is (= (:touched shape1) #{:fill-group})) - (t/is (= (:fill-color shape1) clr/test)) - (t/is (= (:fill-opacity shape1) 0.5)) - (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:touched shape2) nil)) - (t/is (= (:fill-color shape2) clr/white)) - (t/is (= (:fill-opacity shape2) 1)) - - (t/is (= (:name c-instance2) "Group")) - (t/is (= (:touched c-instance2) nil)) - (t/is (= (:name c-instance1) "Rect 1")) - (t/is (= (:touched c-instance1) nil)) - (t/is (= (:name c-shape1) "Circle 1")) - (t/is (= (:touched c-shape1) nil)) - (t/is (= (:fill-color c-shape1) clr/black)) - (t/is (= (:fill-opacity c-shape1) 0)) - (t/is (= (:name c-shape2) "Rect 1")) - (t/is (= (:touched c-shape2) nil)) - (t/is (= (:fill-color c-shape2) clr/white)) - (t/is (= (:fill-opacity c-shape2) 1)))))] - - (ptk/emit! - store - (dch/update-shapes [(:id shape1)] update-fn) - :the/end))))) + (ptk/emit! + store + (dch/update-shapes [(:id shape1')] + (fn [shape] + (merge shape {:fill-color clr/test + :fill-opacity 0.5}))) + :the/end)))) (t/deftest test-touched-nested-lower-near (t/async done - (try - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1" - :fill-color clr/white - :fill-opacity 1}) - (thp/make-component :instance1 :component-1 - [(thp/id :shape1)]) - (thp/sample-shape :shape2 :circle - {:name "Circle 1" - :fill-color clr/black - :fill-opacity 0}) - (thp/group-shapes :group1 - [(thp/id :instance1) - (thp/id :shape2)]) - (thp/make-component :instance2 :component-2 - [(thp/id :group1)])) + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1" + :fill-color clr/white + :fill-opacity 1}) + (thp/make-component :main1 :component1 + [(thp/id :shape1)]) + (thp/instantiate-component :instance1 + (thp/id :component1)) + (thp/sample-shape :shape2 :circle + {:name "Circle 1" + :fill-color clr/black + :fill-opacity 0}) + (thp/group-shapes :group1 + [(thp/id :instance1) + (thp/id :shape2)]) + (thp/make-component :instance2 :component2 + [(thp/id :group1)]) + (thp/instantiate-component :instance2 + (thp/id :component2))) - [instance2 instance1 shape1 shape2] - (thl/resolve-instance state (thp/id :instance2)) + [_instance2 _instance1 _shape1' shape2'] + (thl/resolve-instance state (thp/id :instance2)) - update-fn (fn [shape] - (merge shape {:fill-color clr/test - :fill-opacity 0.5})) + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1 + ; Rect 1 + ; Group + ; Rect 1 #--> Rect 1 + ; Rect 1 ---> Rect 1 + ; Circle 1 + ; Group #--> Group + ; Rect 1 @--> Rect 1 + ; Rect 1* ---> Rect 1 + ; #{:fill-group} + ; Circle 1 ---> Circle 1 + ; + ; [Rect 1] + ; page1 / Rect 1 + ; + ; [Group] + ; page1 / Group + ; + (let [[[instance2 instance1 shape1 shape2] + [c-instance2 c-instance1 c-shape1 c-shape2] _component] + (thl/resolve-instance-and-main + new-state + (thp/id :instance2))] - store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Group #--> Group - ; Rect 1 @--> Rect 1 - ; Rect 1* ---> Rect 1 (color, opacity) - ; #{:fill-group} - ; Circle 1 ---> Circle 1 - ; - ; [Rect 1] - ; Rect 1 - ; Rect 1 - ; - ; [Group] - ; Group - ; Rect 1 @--> Rect 1 - ; Rect 1 ---> Rect 1 - ; Circle 1 - ; - (let [[[instance2 instance1 shape1 shape2] - [c-instance2 c-instance1 c-shape1 c-shape2] component] - (thl/resolve-instance-and-main - new-state - (thp/id :instance2))] + (t/is (= (:name instance2) "Group")) + (t/is (= (:touched instance2) nil)) + (t/is (= (:name instance1) "Rect 1")) + (t/is (= (:touched instance1) nil)) + (t/is (= (:name shape1) "Circle 1")) + (t/is (= (:touched shape1) nil)) + (t/is (= (:fill-color shape1) clr/black)) + (t/is (= (:fill-opacity shape1) 0)) + (t/is (= (:name shape2) "Rect 1")) + (t/is (= (:touched shape2) #{:fill-group})) + (t/is (= (:fill-color shape2) clr/test)) + (t/is (= (:fill-opacity shape2) 0.5)) - ; TODO: get and check the instance inside component [Group 1] + (t/is (= (:name c-instance2) "Group")) + (t/is (= (:touched c-instance2) nil)) + (t/is (= (:name c-instance1) "Rect 1")) + (t/is (= (:touched c-instance1) nil)) + (t/is (= (:name c-shape1) "Circle 1")) + (t/is (= (:touched c-shape1) nil)) + (t/is (= (:fill-color c-shape1) clr/black)) + (t/is (= (:fill-opacity c-shape1) 0)) + (t/is (= (:name c-shape2) "Rect 1")) + (t/is (= (:touched c-shape2) nil)) + (t/is (= (:fill-color c-shape2) clr/white)) + (t/is (= (:fill-opacity c-shape2) 1)))))] - (t/is (= (:name instance2) "Group")) - (t/is (= (:touched instance2) nil)) - (t/is (= (:name instance1) "Rect 1")) - (t/is (= (:touched instance1) nil)) - (t/is (= (:name shape1) "Circle 1")) - (t/is (= (:touched shape1) nil)) - (t/is (= (:fill-color shape1) clr/black)) - (t/is (= (:fill-opacity shape1) 0)) - (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:touched shape2) #{:fill-group})) - (t/is (= (:fill-color shape2) clr/test)) - (t/is (= (:fill-opacity shape2) 0.5)) - - (t/is (= (:name c-instance2) "Group")) - (t/is (= (:touched c-instance2) nil)) - (t/is (= (:name c-instance1) "Rect 1")) - (t/is (= (:touched c-instance1) nil)) - (t/is (= (:name c-shape1) "Circle 1")) - (t/is (= (:touched c-shape1) nil)) - (t/is (= (:fill-color c-shape1) clr/black)) - (t/is (= (:fill-opacity c-shape1) 0)) - (t/is (= (:name c-shape2) "Rect 1")) - (t/is (= (:touched c-shape2) nil)) - (t/is (= (:fill-color c-shape2) clr/white)) - (t/is (= (:fill-opacity c-shape2) 1)))))] - - (ptk/emit! - store - (dch/update-shapes [(:id shape2)] update-fn) - :the/end))))) + (ptk/emit! + store + (dch/update-shapes [(:id shape2')] + (fn [shape] + (merge shape {:fill-color clr/test + :fill-opacity 0.5}))) + :the/end)))) (t/deftest test-touched-nested-lower-remote (t/async done - (try - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1" - :fill-color clr/white - :fill-opacity 1}) - (thp/make-component :instance1 :component-1 - [(thp/id :shape1)]) - (thp/sample-shape :shape2 :circle - {:name "Circle 1" - :fill-color clr/black - :fill-opacity 0}) - (thp/group-shapes :group1 - [(thp/id :instance1) - (thp/id :shape2)]) - (thp/make-component :instance2 :component-2 - [(thp/id :group1)])) + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1" + :fill-color clr/white + :fill-opacity 1}) + (thp/make-component :main1 :component1 + [(thp/id :shape1)]) + (thp/instantiate-component :instance1 + (thp/id :component1)) + (thp/sample-shape :shape2 :circle + {:name "Circle 1" + :fill-color clr/black + :fill-opacity 0}) + (thp/group-shapes :group1 + [(thp/id :instance1) + (thp/id :shape2)]) + (thp/make-component :instance2 :component2 + [(thp/id :group1)]) + (thp/instantiate-component :instance2 + (thp/id :component2))) - [instance2 instance1 shape1 shape2] - (thl/resolve-instance state (thp/id :instance2)) + [instance2 _instance1 _shape1' shape2'] + (thl/resolve-instance state (thp/id :instance2)) - update-fn (fn [shape] - (merge shape {:fill-color clr/test - :fill-opacity 0.5})) + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1 + ; Rect 1 + ; Group + ; Rect 1 #--> Rect 1 + ; Rect 1* ---> Rect 1 + ; #{:fill-group} + ; Circle 1 + ; Group #--> Group + ; Rect 1 @--> Rect 1 + ; Rect 1 ---> Rect 1 + ; Circle 1 ---> Circle 1 + ; + ; [Rect 1] + ; page1 / Rect 1 + ; + ; [Group] + ; page1 / Group + ; + (let [[[instance2 instance1 shape1 shape2] + [c-instance2 c-instance1 c-shape1 c-shape2] _component] + (thl/resolve-instance-and-main + new-state + (thp/id :instance2))] - store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Group #--> Group - ; Rect 1 @--> Rect 1 - ; Rect 1 ---> Rect 1 (color, opacity) - ; Circle 1 ---> Circle 1 - ; - ; [Rect 1] - ; Rect 1 - ; Rect 1 - ; - ; [Group] - ; Group - ; Rect 1 @--> Rect 1 - ; Rect 1* ---> Rect 1 (color, opacity) - ; #{:fill-group} - ; Circle 1 - ; - (let [[[instance2 instance1 shape1 shape2] - [c-instance2 c-instance1 c-shape1 c-shape2] component] - (thl/resolve-instance-and-main - new-state - (thp/id :instance2))] + (t/is (= (:name instance2) "Group")) + (t/is (= (:touched instance2) nil)) + (t/is (= (:name instance1) "Rect 1")) + (t/is (= (:touched instance1) nil)) + (t/is (= (:name shape1) "Circle 1")) + (t/is (= (:touched shape1) nil)) + (t/is (= (:fill-color shape1) clr/black)) + (t/is (= (:fill-opacity shape1) 0)) + (t/is (= (:name shape2) "Rect 1")) + (t/is (= (:touched shape2) nil)) + (t/is (= (:fill-color shape2) clr/test)) + (t/is (= (:fill-opacity shape2) 0.5)) - ; TODO: get and check the instance inside component [Group 1] + (t/is (= (:name c-instance2) "Group")) + (t/is (= (:touched c-instance2) nil)) + (t/is (= (:name c-instance1) "Rect 1")) + (t/is (= (:touched c-instance1) nil)) + (t/is (= (:name c-shape1) "Circle 1")) + (t/is (= (:touched c-shape1) nil)) + (t/is (= (:fill-color c-shape1) clr/black)) + (t/is (= (:fill-opacity c-shape1) 0)) + (t/is (= (:name c-shape2) "Rect 1")) + (t/is (= (:touched c-shape2) #{:fill-group})) + (t/is (= (:fill-color c-shape2) clr/test)) + (t/is (= (:fill-opacity c-shape2) 0.5)))))] - (t/is (= (:name instance2) "Group")) - (t/is (= (:touched instance2) nil)) - (t/is (= (:name instance1) "Rect 1")) - (t/is (= (:touched instance1) nil)) - (t/is (= (:name shape1) "Circle 1")) - (t/is (= (:touched shape1) nil)) - (t/is (= (:fill-color shape1) clr/black)) - (t/is (= (:fill-opacity shape1) 0)) - (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:touched shape2) nil)) - (t/is (= (:fill-color shape2) clr/test)) - (t/is (= (:fill-opacity shape2) 0.5)) + (ptk/emit! + store + (dch/update-shapes [(:id shape2')] + (fn [shape] + (merge shape {:fill-color clr/test + :fill-opacity 0.5}))) + (dwl/update-component (:id instance2)) + :the/end)))) - (t/is (= (:name c-instance2) "Group")) - (t/is (= (:touched c-instance2) nil)) - (t/is (= (:name c-instance1) "Rect 1")) - (t/is (= (:touched c-instance1) nil)) - (t/is (= (:name c-shape1) "Circle 1")) - (t/is (= (:touched c-shape1) nil)) - (t/is (= (:fill-color c-shape1) clr/black)) - (t/is (= (:fill-opacity c-shape1) 0)) - (t/is (= (:name c-shape2) "Rect 1")) - (t/is (= (:touched c-shape2) #{:fill-group})) - (t/is (= (:fill-color c-shape2) clr/test)) - (t/is (= (:fill-opacity c-shape2) 0.5)))))] - - (ptk/emit! - store - (dch/update-shapes [(:id shape2)] update-fn) - (dwl/update-component (:id instance2)) - :the/end))))) - -; === Test reset changes ====================== +;; ; === Test reset changes ====================== (t/deftest test-reset-changes (t/async done - (try - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1" - :fill-color clr/white - :fill-opacity 1}) - (thp/make-component :instance1 :component-1 - [(thp/id :shape1)])) + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1" + :fill-color clr/white + :fill-opacity 1}) + (thp/make-component :main1 :component1 + [(thp/id :shape1)]) + (thp/instantiate-component :instance1 + (thp/id :component1))) - shape1 (thp/get-shape state :shape1) - instance1 (thp/get-shape state :instance1) + [instance1 shape1'] + (thl/resolve-instance state (thp/id :instance1)) - update-fn (fn [shape] - (merge shape {:fill-color clr/test - :fill-opacity 0.5})) + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1 + ; Rect 1 + ; Rect 1 #--> Rect 1 + ; Rect 1 ---> Rect 1 + ; + ; + ; [Rect 1] + ; page1 / Rect 1 + ; + (let [[[group shape1] [c-group c-shape1] _component] + (thl/resolve-instance-and-main + new-state + (:id instance1))] - store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Rect 1 #--> Rect 1 - ; Rect 1 ---> Rect 1 - ; - ; [Rect 1] - ; Rect 1-1 - ; Rect 1 - ; - (let [[[group shape1] [c-group c-shape1] component] - (thl/resolve-instance-and-main - new-state - (:id instance1))] + (t/is (= (:name group) "Rect 1")) + (t/is (= (:touched group) nil)) + (t/is (= (:name shape1) "Rect 1")) + (t/is (= (:fill-color shape1) clr/white)) + (t/is (= (:fill-opacity shape1) 1)) + (t/is (= (:touched shape1) nil)) - (t/is (= (:name group) "Rect 1")) - (t/is (= (:touched group) nil)) - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:fill-color shape1) clr/white)) - (t/is (= (:fill-opacity shape1) 1)) - (t/is (= (:touched shape1) nil)) + (t/is (= (:name c-group) "Rect 1")) + (t/is (= (:touched c-group) nil)) + (t/is (= (:name c-shape1) "Rect 1")) + (t/is (= (:fill-color c-shape1) clr/white)) + (t/is (= (:fill-opacity c-shape1) 1)) + (t/is (= (:touched c-shape1) nil)))))] - (t/is (= (:name c-group) "Rect 1")) - (t/is (= (:touched c-group) nil)) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:fill-color c-shape1) clr/white)) - (t/is (= (:fill-opacity c-shape1) 1)) - (t/is (= (:touched c-shape1) nil)))))] - - (ptk/emit! - store - (dch/update-shapes [(:id shape1)] update-fn) - (dwl/reset-component (:id instance1)) - :the/end))))) + (ptk/emit! + store + (dch/update-shapes [(:id shape1')] + (fn [shape] + (merge shape {:fill-color clr/test + :fill-opacity 0.5}))) + (dwl/reset-component (:id instance1)) + :the/end)))) (t/deftest test-reset-children-add (t/async done - (try - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1" - :fill-color clr/white - :fill-opacity 1}) - (thp/make-component :instance1 :component-1 - [(thp/id :shape1)]) - (thp/sample-shape :shape2 :circle - {:name "Circle 1"})) + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1" + :fill-color clr/white + :fill-opacity 1}) + (thp/make-component :main1 :component1 + [(thp/id :shape1)]) + (thp/instantiate-component :instance1 + (thp/id :component1)) + (thp/sample-shape :shape2 :circle + {:name "Circle 1"})) - instance1 (thp/get-shape state :instance1) - shape2 (thp/get-shape state :shape2) + instance1 (thp/get-shape state :instance1) + shape2 (thp/get-shape state :shape2) - store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Rect 1 #--> Rect 1 - ; Rect 1 ---> Rect 1 - ; - ; [Rect 1] - ; Rect 1 - ; Rect 1 - ; - (let [[[group shape1] [c-group c-shape1] component] - (thl/resolve-instance-and-main - new-state - (thp/id :instance1))] + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1 + ; Rect 1 + ; Rect 1 #--> Rect 1 + ; Rect 1 ---> Rect 1 + ; + ; [Rect 1] + ; page1 / Rect 1 + ; + (let [[[group shape1] [c-group c-shape1] _component] + (thl/resolve-instance-and-main + new-state + (thp/id :instance1))] - (t/is (= (:name group) "Rect 1")) - (t/is (= (:touched group) nil)) - (t/is (not= (:shape-ref group) nil)) - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:touched shape1) nil)) - (t/is (not= (:shape-ref shape1) nil)) + (t/is (= (:name group) "Rect 1")) + (t/is (= (:touched group) nil)) + (t/is (not= (:shape-ref group) nil)) + (t/is (= (:name shape1) "Rect 1")) + (t/is (= (:touched shape1) nil)) + (t/is (not= (:shape-ref shape1) nil)) - (t/is (= (:name c-group) "Rect 1")) - (t/is (= (:touched c-group) nil)) - (t/is (= (:shape-ref c-group) nil)) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:touched c-shape1) nil)) - (t/is (= (:shape-ref c-shape1) nil)))))] + (t/is (= (:name c-group) "Rect 1")) + (t/is (= (:touched c-group) nil)) + (t/is (= (:shape-ref c-group) nil)) + (t/is (= (:name c-shape1) "Rect 1")) + (t/is (= (:touched c-shape1) nil)) + (t/is (= (:shape-ref c-shape1) nil)))))] - (ptk/emit! - store - (dw/relocate-shapes #{(:id shape2)} (:id instance1) 0) - (dwl/reset-component (:id instance1)) - :the/end))))) + (ptk/emit! + store + (dw/relocate-shapes #{(:id shape2)} (:id instance1) 0) + (dwl/reset-component (:id instance1)) + :the/end)))) (t/deftest test-reset-children-delete (t/async done - (try - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1"}) - (thp/sample-shape :shape2 :rect - {:name "Rect 2"}) - (thp/make-component :instance1 :component-1 - [(thp/id :shape1) - (thp/id :shape2)])) + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1"}) + (thp/sample-shape :shape2 :rect + {:name "Rect 2"}) + (thp/make-component :main1 :component1 + [(thp/id :shape1) + (thp/id :shape2)]) + (thp/instantiate-component :instance1 + (thp/id :component1))) - instance1 (thp/get-shape state :instance1) - shape1 (thp/get-shape state :shape1) + [instance1 shape1'] + (thl/resolve-instance state (thp/id :instance1)) - store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Component 1 #--> Component 1 - ; Rect 1 ---> Rect 1 - ; Rect 2 ---> Rect 2 - ; - ; [Component 1] - ; Component 1 - ; Rect 1 - ; Rect 2 - ; - (let [[[group shape1 shape2] - [c-group c-shape1 c-shape2] component] - (thl/resolve-instance-and-main - new-state - (thp/id :instance1))] + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Component 1 + ; Rect 1 + ; Rect 2 + ; Component 1 #--> Component 1 + ; Rect 1 ---> Rect 1 + ; Rect 2 ---> Rect 2 + ; + ; [Component 1] + ; page1 / Component 1 + ; + (let [[[group shape1 shape2] + [c-group c-shape1 c-shape2] _component] + (thl/resolve-instance-and-main + new-state + (thp/id :instance1))] - (t/is (= (:name group) "Component 1")) - (t/is (= (:touched group) nil)) - (t/is (not= (:shape-ref group) nil)) - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:touched shape1) nil)) - (t/is (not= (:shape-ref shape1) nil)) - (t/is (= (:name shape2) "Rect 2")) - (t/is (= (:touched shape2) nil)) - (t/is (not= (:shape-ref shape2) nil)) + (t/is (= (:name group) "Component 1")) + (t/is (= (:touched group) nil)) + (t/is (not= (:shape-ref group) nil)) + (t/is (= (:name shape1) "Rect 1")) + (t/is (= (:touched shape1) nil)) + (t/is (not= (:shape-ref shape1) nil)) + (t/is (= (:name shape2) "Rect 2")) + (t/is (= (:touched shape2) nil)) + (t/is (not= (:shape-ref shape2) nil)) - (t/is (= (:name c-group) "Component 1")) - (t/is (= (:touched c-group) nil)) - (t/is (= (:shape-ref c-group) nil)) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:touched c-shape1) nil)) - (t/is (= (:shape-ref c-shape1) nil)) - (t/is (= (:name c-shape2) "Rect 2")) - (t/is (= (:touched c-shape2) nil)) - (t/is (= (:shape-ref c-shape2) nil)))))] + (t/is (= (:name c-group) "Component 1")) + (t/is (= (:touched c-group) nil)) + (t/is (= (:shape-ref c-group) nil)) + (t/is (= (:name c-shape1) "Rect 1")) + (t/is (= (:touched c-shape1) nil)) + (t/is (= (:shape-ref c-shape1) nil)) + (t/is (= (:name c-shape2) "Rect 2")) + (t/is (= (:touched c-shape2) nil)) + (t/is (= (:shape-ref c-shape2) nil)))))] - (ptk/emit! - store - (dwsh/delete-shapes #{(:id shape1)}) - (dwl/reset-component (:id instance1)) - :the/end))))) + (ptk/emit! + store + (dwsh/delete-shapes #{(:id shape1')}) + (dwl/reset-component (:id instance1)) + :the/end)))) (t/deftest test-reset-children-move (t/async done - (try - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1"}) - (thp/sample-shape :shape2 :rect - {:name "Rect 2"}) - (thp/sample-shape :shape3 :rect - {:name "Rect 3"}) - (thp/make-component :instance1 :component-1 - [(thp/id :shape1) - (thp/id :shape2) - (thp/id :shape3)])) + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1"}) + (thp/sample-shape :shape2 :rect + {:name "Rect 2"}) + (thp/sample-shape :shape3 :rect + {:name "Rect 3"}) + (thp/make-component :main1 :component1 + [(thp/id :shape1) + (thp/id :shape2) + (thp/id :shape3)]) + (thp/instantiate-component :instance1 + (thp/id :component1))) - shape1 (thp/get-shape state :shape1) - instance1 (thp/get-shape state :instance1) + [instance1 shape1'] + (thl/resolve-instance state (thp/id :instance1)) - store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Component 1 #--> Component 1 - ; Rect 1 ---> Rect 1 - ; Rect 2 ---> Rect 2 - ; Rect 3 ---> Rect 3 - ; - ; [Component 1] - ; Component 1 - ; Rect 1 - ; Rect 2 - ; Rect 3 - ; - (let [[[group shape1 shape2 shape3] [c-group c-shape1 c-shape2 c-shape3] component] - (thl/resolve-instance-and-main - new-state - (thp/id :instance1))] + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Component 1 + ; Rect 1 + ; Rect 2 + ; Rect 3 + ; Component 1 #--> Component 1 + ; Rect 1 ---> Rect 1 + ; Rect 2 ---> Rect 2 + ; Rect 3 ---> Rect 3 + ; + ; [Component 1] + ; page1 / Component 1 + ; + (let [[[group shape1 shape2 shape3] [c-group c-shape1 c-shape2 c-shape3] _component] + (thl/resolve-instance-and-main + new-state + (thp/id :instance1))] - (t/is (= (:name group) "Component 1")) - (t/is (= (:touched group) nil)) - (t/is (not= (:shape-ref group) nil)) - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:touched shape1) nil)) - (t/is (not= (:shape-ref shape1) nil)) - (t/is (= (:name shape2) "Rect 2")) - (t/is (= (:touched shape2) nil)) - (t/is (not= (:shape-ref shape2) nil)) - (t/is (= (:name shape3) "Rect 3")) - (t/is (= (:touched shape3) nil)) - (t/is (not= (:shape-ref shape3) nil)) + (t/is (= (:name group) "Component 1")) + (t/is (= (:touched group) nil)) + (t/is (not= (:shape-ref group) nil)) + (t/is (= (:name shape1) "Rect 1")) + (t/is (= (:touched shape1) nil)) + (t/is (not= (:shape-ref shape1) nil)) + (t/is (= (:name shape2) "Rect 2")) + (t/is (= (:touched shape2) nil)) + (t/is (not= (:shape-ref shape2) nil)) + (t/is (= (:name shape3) "Rect 3")) + (t/is (= (:touched shape3) nil)) + (t/is (not= (:shape-ref shape3) nil)) - (t/is (= (:name c-group) "Component 1")) - (t/is (= (:touched c-group) nil)) - (t/is (= (:shape-ref c-group) nil)) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:touched c-shape1) nil)) - (t/is (= (:shape-ref c-shape1) nil)) - (t/is (= (:name c-shape2) "Rect 2")) - (t/is (= (:touched c-shape2) nil)) - (t/is (= (:shape-ref c-shape2) nil)) - (t/is (= (:name c-shape3) "Rect 3")) - (t/is (= (:touched c-shape3) nil)) - (t/is (= (:shape-ref c-shape3) nil)))))] + (t/is (= (:name c-group) "Component 1")) + (t/is (= (:touched c-group) nil)) + (t/is (= (:shape-ref c-group) nil)) + (t/is (= (:name c-shape1) "Rect 1")) + (t/is (= (:touched c-shape1) nil)) + (t/is (= (:shape-ref c-shape1) nil)) + (t/is (= (:name c-shape2) "Rect 2")) + (t/is (= (:touched c-shape2) nil)) + (t/is (= (:shape-ref c-shape2) nil)) + (t/is (= (:name c-shape3) "Rect 3")) + (t/is (= (:touched c-shape3) nil)) + (t/is (= (:shape-ref c-shape3) nil)))))] - (ptk/emit! - store - (dw/relocate-shapes #{(:id shape1)} (:id instance1) 2) - (dwl/reset-component (:id instance1)) - :the/end))))) + (ptk/emit! + store + (dw/relocate-shapes #{(:id shape1')} (:id instance1) 2) + (dwl/reset-component (:id instance1)) + :the/end)))) (t/deftest test-reset-from-lib (t/async done - (try (let [state (-> thp/initial-state (thp/sample-page) (thp/sample-shape :shape1 :rect {:name "Rect 1" :fill-color clr/white :fill-opacity 1}) - (thp/make-component :instance1 :component-1 + (thp/make-component :instance1 :component1 [(thp/id :shape1)]) (thp/move-to-library :lib1 "Library 1") (thp/sample-page) (thp/instantiate-component :instance2 - (thp/id :component-1) + (thp/id :component1) (thp/id :lib1))) [instance2 shape2] (thl/resolve-instance state (thp/id :instance2)) - update-fn (fn [shape] - (merge shape {:fill-color clr/test - :fill-opacity 0.5})) - store (the/prepare-store state done (fn [new-state] ; Expected shape tree: @@ -884,10 +899,10 @@ ; Rect 1 #--> Rect 1 ; Rect 1 ---> Rect 1 ; - (let [[[group shape1] [c-group c-shape1] component] + (let [[[group shape1] [c-group c-shape1] _component] (thl/resolve-instance-and-main - new-state - (:id instance2))] + new-state + (:id instance2))] (t/is (= (:name group) "Rect 1")) (t/is (= (:touched group) nil)) @@ -905,1048 +920,1352 @@ (ptk/emit! store - (dch/update-shapes [(:id shape2)] update-fn) + (dch/update-shapes [(:id shape2)] + (fn [shape] + (merge shape {:fill-color clr/test + :fill-opacity 0.5}))) (dwl/reset-component (:id instance2)) - :the/end))))) + :the/end)))) (t/deftest test-reset-nested-upper (t/async done - (try - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1" - :fill-color clr/white - :fill-opacity 1}) - (thp/make-component :instance1 :component-1 - [(thp/id :shape1)]) - (thp/sample-shape :shape2 :circle - {:name "Circle 1" - :fill-color clr/black - :fill-opacity 0}) - (thp/group-shapes :group1 - [(thp/id :instance1) - (thp/id :shape2)]) - (thp/make-component :instance2 :component-2 - [(thp/id :group1)])) + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1" + :fill-color clr/white + :fill-opacity 1}) + (thp/make-component :main1 :component1 + [(thp/id :shape1)]) + (thp/instantiate-component :instance1 + (thp/id :component1)) + (thp/sample-shape :shape2 :circle + {:name "Circle 1" + :fill-color clr/black + :fill-opacity 0}) + (thp/group-shapes :group1 + [(thp/id :instance1) + (thp/id :shape2)]) + (thp/make-component :main2 :component2 + [(thp/id :group1)]) + (thp/instantiate-component :instance2 + (thp/id :component2))) - [instance2 instance1 shape1 shape2] - (thl/resolve-instance state (thp/id :instance2)) + [instance2 _instance1 shape1' _shape2'] + (thl/resolve-instance state (thp/id :instance2)) - update-fn (fn [shape] - (merge shape {:fill-color clr/test - :fill-opacity 0.5})) + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1 + ; Rect 1 + ; Group + ; Rect 1 #--> Rect 1 + ; Rect 1 ---> Rect 1 + ; Circle 1 + ; Group #--> Group + ; Rect 1 @--> Rect 1 + ; Rect 1 ---> Rect 1 + ; Circle 1 ---> Circle 1 + ; + ; [Rect 1] + ; page1 / Rect 1 + ; + ; [Group] + ; page1 / Group + ; + (let [[[instance2 instance1 shape1 shape2] + [c-instance2 c-instance1 c-shape1 c-shape2] _component] + (thl/resolve-instance-and-main + new-state + (thp/id :instance2))] - store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Group #--> Group - ; Rect 1 @--> Rect 1 - ; Rect 1 ---> Rect 1 - ; Circle 1 ---> Circle 1 - ; - ; [Rect 1] - ; Rect 1 - ; Rect 1 - ; - ; [Group] - ; Group - ; Rect 1 @--> Rect 1 - ; Rect 1 ---> Rect 1 - ; Circle 1 - ; - (let [[[instance2 instance1 shape1 shape2] - [c-instance2 c-instance1 c-shape1 c-shape2] component] - (thl/resolve-instance-and-main - new-state - (thp/id :instance2))] + (t/is (= (:name instance2) "Group")) + (t/is (= (:touched instance2) nil)) + (t/is (= (:name instance1) "Rect 1")) + (t/is (= (:touched instance1) nil)) + (t/is (= (:name shape1) "Circle 1")) + (t/is (= (:touched shape1) nil)) + (t/is (= (:fill-color shape1) clr/black)) + (t/is (= (:fill-opacity shape1) 0)) + (t/is (= (:name shape2) "Rect 1")) + (t/is (= (:touched shape2) nil)) + (t/is (= (:fill-color shape2) clr/white)) + (t/is (= (:fill-opacity shape2) 1)) - ; TODO: get and check the instance inside component [Group] + (t/is (= (:name c-instance2) "Group")) + (t/is (= (:touched c-instance2) nil)) + (t/is (= (:name c-instance1) "Rect 1")) + (t/is (= (:touched c-instance1) nil)) + (t/is (= (:name c-shape1) "Circle 1")) + (t/is (= (:touched c-shape1) nil)) + (t/is (= (:fill-color c-shape1) clr/black)) + (t/is (= (:fill-opacity c-shape1) 0)) + (t/is (= (:name c-shape2) "Rect 1")) + (t/is (= (:touched c-shape2) nil)) + (t/is (= (:fill-color c-shape2) clr/white)) + (t/is (= (:fill-opacity c-shape2) 1)))))] - (t/is (= (:name instance2) "Group")) - (t/is (= (:touched instance2) nil)) - (t/is (= (:name instance1) "Rect 1")) - (t/is (= (:touched instance1) nil)) - (t/is (= (:name shape1) "Circle 1")) - (t/is (= (:touched shape1) nil)) - (t/is (= (:fill-color shape1) clr/black)) - (t/is (= (:fill-opacity shape1) 0)) - (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:touched shape2) nil)) - (t/is (= (:fill-color shape2) clr/white)) - (t/is (= (:fill-opacity shape2) 1)) - - (t/is (= (:name c-instance2) "Group")) - (t/is (= (:touched c-instance2) nil)) - (t/is (= (:name c-instance1) "Rect 1")) - (t/is (= (:touched c-instance1) nil)) - (t/is (= (:name c-shape1) "Circle 1")) - (t/is (= (:touched c-shape1) nil)) - (t/is (= (:fill-color c-shape1) clr/black)) - (t/is (= (:fill-opacity c-shape1) 0)) - (t/is (= (:name c-shape2) "Rect 1")) - (t/is (= (:touched c-shape2) nil)) - (t/is (= (:fill-color c-shape2) clr/white)) - (t/is (= (:fill-opacity c-shape2) 1)))))] - - (ptk/emit! - store - (dch/update-shapes [(:id shape1)] update-fn) - (dwl/reset-component (:id instance2)) - :the/end))))) + (ptk/emit! + store + (dch/update-shapes [(:id shape1')] + (fn [shape] + (merge shape {:fill-color clr/test + :fill-opacity 0.5}))) + (dwl/reset-component (:id instance2)) + :the/end)))) (t/deftest test-reset-nested-lower-near (t/async done - (try - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1" - :fill-color clr/white - :fill-opacity 1}) - (thp/make-component :instance1 :component-1 - [(thp/id :shape1)]) - (thp/sample-shape :shape2 :circle - {:name "Circle 1" - :fill-color clr/black - :fill-opacity 0}) - (thp/group-shapes :group1 - [(thp/id :instance1) - (thp/id :shape2)]) - (thp/make-component :instance2 :component-2 - [(thp/id :group1)])) + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1" + :fill-color clr/white + :fill-opacity 1}) + (thp/make-component :main1 :component1 + [(thp/id :shape1)]) + (thp/instantiate-component :instance1 + (thp/id :component1)) + (thp/sample-shape :shape2 :circle + {:name "Circle 1" + :fill-color clr/black + :fill-opacity 0}) + (thp/group-shapes :group1 + [(thp/id :instance1) + (thp/id :shape2)]) + (thp/make-component :instance2 :component2 + [(thp/id :group1)]) + (thp/instantiate-component :instance2 + (thp/id :component2))) - [instance2 instance1 shape1 shape2] - (thl/resolve-instance state (thp/id :instance2)) + [instance2 instance1 _shape1' shape2'] + (thl/resolve-instance state (thp/id :instance2)) - update-fn (fn [shape] - (merge shape {:fill-color clr/test - :fill-opacity 0.5})) + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1 + ; Rect 1 + ; Group + ; Rect 1 #--> Rect 1 + ; Rect 1 ---> Rect 1 + ; Circle 1 + ; Group #--> Group + ; Rect 1 @--> Rect 1 + ; Rect 1 ---> Rect 1 + ; Circle 1 ---> Circle 1 + ; + ; [Rect 1] + ; page1 / Rect 1 + ; + ; [Group] + ; page1 / Group + ; + (let [[[instance2 instance1 shape1 shape2] + [c-instance2 c-instance1 c-shape1 c-shape2] _component] + (thl/resolve-instance-and-main + new-state + (thp/id :instance2))] - store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Group #--> Group - ; Rect 1 @--> Rect 1 - ; Rect 1 ---> Rect 1 - ; Circle 1 ---> Circle 1 - ; - ; [Rect 1] - ; Rect 1 - ; Rect 1 (color, opacity) - ; - ; [Group] - ; Group - ; Rect 1 @--> Rect 1 - ; Rect 1 ---> Rect 1 - ; Circle 1 - ; - (let [[[instance2 instance1 shape1 shape2] - [c-instance2 c-instance1 c-shape1 c-shape2] component] - (thl/resolve-instance-and-main - new-state - (thp/id :instance2))] + (t/is (= (:name instance2) "Group")) + (t/is (= (:touched instance2) nil)) + (t/is (= (:name instance1) "Rect 1")) + (t/is (= (:touched instance1) nil)) + (t/is (= (:name shape1) "Circle 1")) + (t/is (= (:touched shape1) nil)) + (t/is (= (:fill-color shape1) clr/black)) + (t/is (= (:fill-opacity shape1) 0)) + (t/is (= (:name shape2) "Rect 1")) + (t/is (= (:touched shape2) nil)) + (t/is (= (:fill-color shape2) clr/white)) + (t/is (= (:fill-opacity shape2) 1)) - ; TODO: get and check the instance inside component [Group] + (t/is (= (:name c-instance2) "Group")) + (t/is (= (:touched c-instance2) nil)) + (t/is (= (:name c-instance1) "Rect 1")) + (t/is (= (:touched c-instance1) nil)) + (t/is (= (:name c-shape1) "Circle 1")) + (t/is (= (:touched c-shape1) nil)) + (t/is (= (:fill-color c-shape1) clr/black)) + (t/is (= (:fill-opacity c-shape1) 0)) + (t/is (= (:name c-shape2) "Rect 1")) + (t/is (= (:touched c-shape2) nil)) + (t/is (= (:fill-color c-shape2) clr/white)) + (t/is (= (:fill-opacity c-shape2) 1)))))] - (t/is (= (:name instance2) "Group")) - (t/is (= (:touched instance2) nil)) - (t/is (= (:name instance1) "Rect 1")) - (t/is (= (:touched instance1) nil)) - (t/is (= (:name shape1) "Circle 1")) - (t/is (= (:touched shape1) nil)) - (t/is (= (:fill-color shape1) clr/black)) - (t/is (= (:fill-opacity shape1) 0)) - (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:touched shape2) nil)) - (t/is (= (:fill-color shape2) clr/white)) - (t/is (= (:fill-opacity shape2) 1)) - - (t/is (= (:name c-instance2) "Group")) - (t/is (= (:touched c-instance2) nil)) - (t/is (= (:name c-instance1) "Rect 1")) - (t/is (= (:touched c-instance1) nil)) - (t/is (= (:name c-shape1) "Circle 1")) - (t/is (= (:touched c-shape1) nil)) - (t/is (= (:fill-color c-shape1) clr/black)) - (t/is (= (:fill-opacity c-shape1) 0)) - (t/is (= (:name c-shape2) "Rect 1")) - (t/is (= (:touched c-shape2) nil)) - (t/is (= (:fill-color c-shape2) clr/white)) - (t/is (= (:fill-opacity c-shape2) 1)))))] - - (ptk/emit! - store - (dch/update-shapes [(:id shape2)] update-fn) - (dwl/update-component (:id instance1)) - (dwl/reset-component (:id instance2)) - :the/end))))) + (ptk/emit! + store + (dch/update-shapes [(:id shape2')] + (fn [shape] + (merge shape {:fill-color clr/test + :fill-opacity 0.5}))) + (dwl/update-component (:id instance1)) + (dwl/reset-component (:id instance2)) + :the/end)))) (t/deftest test-reset-nested-lower-remote (t/async done - (try - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1" - :fill-color clr/white - :fill-opacity 1}) - (thp/make-component :instance1 :component-1 - [(thp/id :shape1)]) - (thp/sample-shape :shape2 :circle - {:name "Circle 1" - :fill-color clr/black - :fill-opacity 0}) - (thp/group-shapes :group1 - [(thp/id :instance1) - (thp/id :shape2)]) - (thp/make-component :instance2 :component-2 - [(thp/id :group1)])) + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1" + :fill-color clr/white + :fill-opacity 1}) + (thp/make-component :main1 :component1 + [(thp/id :shape1)]) + (thp/instantiate-component :instance1 + (thp/id :component1)) + (thp/sample-shape :shape2 :circle + {:name "Circle 1" + :fill-color clr/black + :fill-opacity 0}) + (thp/group-shapes :group1 + [(thp/id :instance1) + (thp/id :shape2)]) + (thp/make-component :instance2 :component2 + [(thp/id :group1)]) + (thp/instantiate-component :instance2 + (thp/id :component2))) - [instance2 instance1 shape1 shape2] - (thl/resolve-instance state (thp/id :instance2)) + [instance2 instance1 _shape1' shape2'] + (thl/resolve-instance state (thp/id :instance2)) - update-fn (fn [shape] - (merge shape {:fill-color clr/test - :fill-opacity 0.5})) + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1 + ; Rect 1 + ; Group + ; Rect 1 #--> Rect 1 + ; Rect 1* ---> Rect 1 + ; #{:fill-group} + ; Circle 1 + ; Group #--> Group + ; Rect 1 @--> Rect 1 + ; (remote-synced) + ; Rect 1 ---> Rect 1 + ; (remote-synced) + ; Circle 1 ---> Circle 1 + ; + ; [Rect 1] + ; page1 / Rect 1 + ; + ; [Group] + ; page1 / Group + ; + (let [[[instance2 instance1 shape1 shape2] + [c-instance2 c-instance1 c-shape1 c-shape2] _component] + (thl/resolve-instance-and-main + new-state + (thp/id :instance2))] - store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Group #--> Group - ; Rect 1 @--> Rect 1 - ; Rect 1 ---> Rect 1 - ; Circle 1 ---> Circle 1 - ; - ; [Rect 1] - ; Rect 1 - ; Rect 1 - ; - ; [Group] - ; Group - ; Rect 1 @--> Rect 1 - ; Rect 1 ---> Rect 1 (color, opacity) - ; #{:fill-group} - ; Circle 1 - ; - (let [[[instance2 instance1 shape1 shape2] - [c-instance2 c-instance1 c-shape1 c-shape2] component] - (thl/resolve-instance-and-main - new-state - (thp/id :instance2))] + (t/is (= (:name instance2) "Group")) + (t/is (= (:touched instance2) nil)) + (t/is (= (:name instance1) "Rect 1")) + (t/is (= (:touched instance1) nil)) + (t/is (= (:name shape1) "Circle 1")) + (t/is (= (:touched shape1) nil)) + (t/is (= (:fill-color shape1) clr/black)) + (t/is (= (:fill-opacity shape1) 0)) + (t/is (= (:name shape2) "Rect 1")) + (t/is (= (:touched shape2) nil)) + (t/is (= (:fill-color shape2) clr/white)) + (t/is (= (:fill-opacity shape2) 1)) - ; TODO: get and check the instance inside component [Group 1] + (t/is (= (:name c-instance2) "Group")) + (t/is (= (:touched c-instance2) nil)) + (t/is (= (:name c-instance1) "Rect 1")) + (t/is (= (:touched c-instance1) nil)) + (t/is (= (:name c-shape1) "Circle 1")) + (t/is (= (:touched c-shape1) nil)) + (t/is (= (:fill-color c-shape1) clr/black)) + (t/is (= (:fill-opacity c-shape1) 0)) + (t/is (= (:name c-shape2) "Rect 1")) + (t/is (= (:touched c-shape2) #{:fill-group})) + (t/is (= (:fill-color c-shape2) clr/test)) + (t/is (= (:fill-opacity c-shape2) 0.5)))))] - (t/is (= (:name instance2) "Group")) - (t/is (= (:touched instance2) nil)) - (t/is (= (:name instance1) "Rect 1")) - (t/is (= (:touched instance1) nil)) - (t/is (= (:name shape1) "Circle 1")) - (t/is (= (:touched shape1) nil)) - (t/is (= (:fill-color shape1) clr/black)) - (t/is (= (:fill-opacity shape1) 0)) - (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:touched shape2) nil)) - (t/is (= (:fill-color shape2) clr/white)) - (t/is (= (:fill-opacity shape2) 1)) + (ptk/emit! + store + (dch/update-shapes [(:id shape2')] + (fn [shape] + (merge shape {:fill-color clr/test + :fill-opacity 0.5}))) + (dwl/update-component (:id instance2)) + (dwl/reset-component (:id instance1)) + :the/end)))) - (t/is (= (:name c-instance2) "Group")) - (t/is (= (:touched c-instance2) nil)) - (t/is (= (:name c-instance1) "Rect 1")) - (t/is (= (:touched c-instance1) nil)) - (t/is (= (:name c-shape1) "Circle 1")) - (t/is (= (:touched c-shape1) nil)) - (t/is (= (:fill-color c-shape1) clr/black)) - (t/is (= (:fill-opacity c-shape1) 0)) - (t/is (= (:name c-shape2) "Rect 1")) - (t/is (= (:touched c-shape2) #{:fill-group})) - (t/is (= (:fill-color c-shape2) clr/test)) - (t/is (= (:fill-opacity c-shape2) 0.5)))))] - - (ptk/emit! - store - (dch/update-shapes [(:id shape2)] update-fn) - (dwl/update-component (:id instance2)) - (dwl/reset-component (:id instance1)) - :the/end))))) - -; === Test update component ====================== +;; ; === Test update component ====================== (t/deftest test-update-component (t/async done - (try - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1" - :fill-color clr/white - :fill-opacity 1}) - (thp/make-component :instance1 :component-1 - [(thp/id :shape1)]) - (thp/instantiate-component :instance2 - (thp/id :component-1))) + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1" + :fill-color clr/white + :fill-opacity 1}) + (thp/make-component :main1 :component1 + [(thp/id :shape1)]) + (thp/instantiate-component :instance1 + (thp/id :component1)) + (thp/instantiate-component :instance2 + (thp/id :component1))) - shape1 (thp/get-shape state :shape1) - instance1 (thp/get-shape state :instance1) - instance2 (thp/get-shape state :instance2) + [instance1 shape1'] + (thl/resolve-instance state (thp/id :instance1)) - update-fn (fn [shape] - (merge shape {:fill-color clr/test - :fill-opacity 0.5})) + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1 + ; Rect 1 + ; Rect 1 #--> Rect 1 + ; Rect 1 ---> Rect 1 + ; Rect 1 #--> Rect 1 + ; Rect 1 ---> Rect 1 <== (not updated) + ; + ; [Rect 1] + ; page1 / Rect 1 + ; + (let [[[main1 shape1] [c-main1 c-shape1] component1] + (thl/resolve-instance-and-main + new-state + (thp/id :main1)) - store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Rect 1 #--> Rect 1 - ; Rect 1 ---> Rect 1 (color, opacity) - ; Rect 1-2 - ; Rect 1 ---> Rect 1 - ; - ; [Rect 1] - ; Rect 1 - ; Rect 1 (color, opacity) - ; - (let [[[instance1 shape1] [c-instance1 c-shape1] component1] - (thl/resolve-instance-and-main - new-state - (:id instance1)) + [[instance1 shape2] [c-instance1 c-shape2] component2] + (thl/resolve-instance-and-main + new-state + (thp/id :instance1)) - [[instance2 shape2] [c-instance2 c-shape2] component2] - (thl/resolve-instance-and-main - new-state - (:id instance2))] + [[instance2 shape3] [c-instance2 c-shape3] component3] + (thl/resolve-instance-and-main + new-state + (thp/id :instance2))] - (t/is (= (:name instance1) "Rect 1")) - (t/is (= (:touched instance1) nil)) - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:fill-color shape1) clr/test)) - (t/is (= (:fill-opacity shape1) 0.5)) - (t/is (= (:touched shape1) nil)) + (t/is (= (:name main1) "Rect 1")) + (t/is (= (:touched main1) nil)) + (t/is (= (:shape-ref main1) nil)) + (t/is (= (:name shape1) "Rect 1")) + (t/is (= (:fill-color shape1) clr/test)) + (t/is (= (:fill-opacity shape1) 0.5)) + (t/is (= (:touched shape1) nil)) + (t/is (= (:shape-ref shape1) nil)) - (t/is (= (:name instance2) "Rect 1")) - (t/is (= (:touched instance2) nil)) - (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:fill-color shape2) clr/white)) - (t/is (= (:fill-opacity shape2) 1)) - (t/is (= (:touched shape2) nil)) + (t/is (= (:name instance1) "Rect 1")) + (t/is (= (:touched instance1) nil)) + (t/is (= (:shape-ref instance1) (:id c-main1))) + (t/is (= (:name shape2) "Rect 1")) + (t/is (= (:fill-color shape2) clr/test)) + (t/is (= (:fill-opacity shape2) 0.5)) + (t/is (= (:touched shape2) nil)) + (t/is (= (:shape-ref shape2) (:id c-shape1))) - (t/is (= (:name c-instance1) "Rect 1")) - (t/is (= (:touched c-instance1) nil)) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:fill-color c-shape1) clr/test)) - (t/is (= (:fill-opacity c-shape1) 0.5)) - (t/is (= (:touched c-shape1) nil)) + (t/is (= (:name instance2) "Rect 1")) + (t/is (= (:touched instance2) nil)) + (t/is (= (:shape-ref instance2) (:id c-main1))) + (t/is (= (:name shape3) "Rect 1")) + (t/is (= (:fill-color shape3) clr/white)) + (t/is (= (:fill-opacity shape3) 1)) + (t/is (= (:touched shape3) nil)) + (t/is (= (:shape-ref shape3) (:id c-shape1))) - (t/is (= component1 component2)) - (t/is (= c-instance2 c-instance1)) - (t/is (= c-shape2 c-shape1)))))] + (t/is (= component1 component2 component3)) + (t/is (= c-main1 main1)) + (t/is (= c-shape1 shape1)) + (t/is (= c-instance1 c-main1)) + (t/is (= c-shape2 c-shape1)) + (t/is (= c-instance2 c-main1)) + (t/is (= c-shape3 c-shape1)))))] - (ptk/emit! - store - (dch/update-shapes [(:id shape1)] update-fn) - (dwl/update-component (:id instance1)) - :the/end))))) + (ptk/emit! + store + (dch/update-shapes [(:id shape1')] + (fn [shape] + (merge shape {:fill-color clr/test + :fill-opacity 0.5}))) + (dwl/update-component (:id instance1)) + :the/end)))) (t/deftest test-update-component-and-sync (t/async done - (try - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1" - :fill-color clr/white - :fill-opacity 1}) - (thp/make-component :instance1 :component-1 - [(thp/id :shape1)]) - (thp/instantiate-component :instance2 - (thp/id :component-1))) + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1" + :fill-color clr/white + :fill-opacity 1}) + (thp/make-component :main1 :component1 + [(thp/id :shape1)]) + (thp/instantiate-component :instance1 + (thp/id :component1)) + (thp/instantiate-component :instance2 + (thp/id :component1))) - file (wsh/get-local-file state) + file (wsh/get-local-file state) - shape1 (thp/get-shape state :shape1) - instance1 (thp/get-shape state :instance1) - instance2 (thp/get-shape state :instance2) + [instance1 shape1'] + (thl/resolve-instance state (thp/id :instance1)) - update-fn (fn [shape] - (merge shape {:fill-color clr/test - :fill-opacity 0.5})) + [_instance2 _shape1''] + (thl/resolve-instance state (thp/id :instance2)) - store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Rect 1 #--> Rect 1 - ; Rect 1 ---> Rect 1 (color, opacity) - ; Rect 1 #--> Rect 1 - ; Rect 1 ---> Rect 1 (color, opacity) - ; - ; [Rect 1] - ; Rect 1 - ; Rect 1 (color, opacity) - ; - (let [[[instance1 shape1] [c-instance1 c-shape1] component1] - (thl/resolve-instance-and-main - new-state - (:id instance1)) + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1 + ; Rect 1 + ; Rect 1 #--> Rect 1 + ; Rect 1 ---> Rect 1 + ; Rect 1 #--> Rect 1 + ; Rect 1 ---> Rect 1 + ; + ; [Rect 1] + ; page1 / Rect 1 + ; + (let [[[main1 shape1] [c-main1 c-shape1] component1] + (thl/resolve-instance-and-main + new-state + (thp/id :main1)) - [[instance2 shape2] [c-instance2 c-shape2] component2] - (thl/resolve-instance-and-main - new-state - (:id instance2))] + [[instance1 shape2] [c-instance1 c-shape2] component2] + (thl/resolve-instance-and-main + new-state + (thp/id :instance1)) - (t/is (= (:name instance1) "Rect 1")) - (t/is (= (:touched instance1) nil)) - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:fill-color shape1) clr/test)) - (t/is (= (:fill-opacity shape1) 0.5)) - (t/is (= (:touched shape1) nil)) + [[instance2 shape3] [c-instance2 c-shape3] component3] + (thl/resolve-instance-and-main + new-state + (thp/id :instance2))] - (t/is (= (:name instance2) "Rect 1")) - (t/is (= (:touched instance2) nil)) - (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:fill-color shape2) clr/test)) - (t/is (= (:fill-opacity shape2) 0.5)) - (t/is (= (:touched shape2) nil)) + (t/is (= (:name main1) "Rect 1")) + (t/is (= (:touched main1) nil)) + (t/is (= (:shape-ref main1) nil)) + (t/is (= (:name shape1) "Rect 1")) + (t/is (= (:fill-color shape1) clr/test)) + (t/is (= (:fill-opacity shape1) 0.5)) + (t/is (= (:touched shape1) nil)) + (t/is (= (:shape-ref shape1) nil)) - (t/is (= (:name c-instance1) "Rect 1")) - (t/is (= (:touched c-instance1) nil)) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:fill-color c-shape1) clr/test)) - (t/is (= (:fill-opacity c-shape1) 0.5)) - (t/is (= (:touched c-shape1) nil)) + (t/is (= (:name instance1) "Rect 1")) + (t/is (= (:touched instance1) nil)) + (t/is (= (:shape-ref instance1) (:id c-main1))) + (t/is (= (:name shape2) "Rect 1")) + (t/is (= (:fill-color shape2) clr/test)) + (t/is (= (:fill-opacity shape2) 0.5)) + (t/is (= (:touched shape2) nil)) + (t/is (= (:shape-ref shape2) (:id c-shape1))) - (t/is (= component1 component2)) - (t/is (= c-instance2 c-instance1)) - (t/is (= c-shape2 c-shape1)))))] + (t/is (= (:name instance2) "Rect 1")) + (t/is (= (:touched instance2) nil)) + (t/is (= (:shape-ref instance2) (:id c-main1))) + (t/is (= (:name shape3) "Rect 1")) + (t/is (= (:fill-color shape3) clr/test)) + (t/is (= (:fill-opacity shape3) 0.5)) + (t/is (= (:touched shape3) nil)) + (t/is (= (:shape-ref shape3) (:id c-shape1))) - (ptk/emit! - store - (dch/update-shapes [(:id shape1)] update-fn) - (dwl/update-component-sync (:id instance1) (:id file)) - :the/end))))) + (t/is (= component1 component2 component3)) + (t/is (= c-main1 main1)) + (t/is (= c-shape1 shape1)) + (t/is (= c-instance1 c-main1)) + (t/is (= c-shape2 c-shape1)) + (t/is (= c-instance2 c-main1)) + (t/is (= c-shape3 c-shape1)))))] + + (ptk/emit! + store + (dch/update-shapes [(:id shape1')] + (fn [shape] + (merge shape {:fill-color clr/test + :fill-opacity 0.5}))) + (dwl/update-component-sync (:id instance1) (:id file)) + :the/end)))) (t/deftest test-update-preserve-touched (t/async done - (try - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1" - :fill-color clr/white - :fill-opacity 1}) - (thp/make-component :instance1 :component-1 - [(thp/id :shape1)]) - (thp/instantiate-component :instance2 - (thp/id :component-1))) + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1" + :fill-color clr/white + :fill-opacity 1}) + (thp/make-component :main1 :component1 + [(thp/id :shape1)]) + (thp/instantiate-component :instance1 + (thp/id :component1)) + (thp/instantiate-component :instance2 + (thp/id :component1))) - file (wsh/get-local-file state) + file (wsh/get-local-file state) - shape1 (thp/get-shape state :shape1) - instance1 (thp/get-shape state :instance1) - instance2 (thp/get-shape state :instance2) + [instance1 shape1'] + (thl/resolve-instance state (thp/id :instance1)) - shape2 (ctn/get-shape (wsh/lookup-page state) - (first (:shapes instance2))) + [_instance2 shape1''] + (thl/resolve-instance state (thp/id :instance2)) - update-fn1 (fn [shape] - (merge shape {:fill-color clr/test - :stroke-width 0.5})) + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1 + ; Rect 1 + ; Rect 1 #--> Rect 1 + ; Rect 1 ---> Rect 1 + ; Rect 1 #--> Rect 1 + ; Rect 1* ---> Rect 1 + ; #{:stroke-group} + ; + ; [Rect 1] + ; page1 / Rect 1 + ; + (let [[[main1 shape1] [c-main1 c-shape1] component1] + (thl/resolve-instance-and-main + new-state + (thp/id :main1)) - update-fn2 (fn [shape] - (merge shape {:stroke-width 0.2})) + [[instance1 shape2] [c-instance1 c-shape2] component2] + (thl/resolve-instance-and-main + new-state + (thp/id :instance1)) - store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Rect 1 #--> Rect 1 - ; Rect 1 ---> Rect 1 (color, stroke) - ; Rect 1 #--> Rect 1 - ; Rect 1* ---> Rect 1 (color, stroke2) - ; #{:stroke-group} - ; - ; [Rect 1] - ; Rect 1-1 - ; Rect 1 (color, stroke) - ; - (let [[[instance1 shape1] [c-instance1 c-shape1] component1] - (thl/resolve-instance-and-main - new-state - (:id instance1)) + [[instance2 shape3] [c-instance2 c-shape3] component3] + (thl/resolve-instance-and-main + new-state + (thp/id :instance2))] - [[instance2 shape2] [c-instance2 c-shape2] component2] - (thl/resolve-instance-and-main - new-state - (:id instance2))] + (t/is (= (:name main1) "Rect 1")) + (t/is (= (:touched main1) nil)) + (t/is (= (:shape-ref main1) nil)) + (t/is (= (:name shape1) "Rect 1")) + (t/is (= (:fill-color shape1) clr/test)) + (t/is (= (:stroke-width shape1) 0.5)) + (t/is (= (:touched shape1) nil)) + (t/is (= (:shape-ref shape1) nil)) - (t/is (= (:name instance1) "Rect 1")) - (t/is (= (:touched instance1) nil)) - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:fill-color shape1) clr/test)) - (t/is (= (:stroke-width shape1) 0.5)) - (t/is (= (:touched shape1) nil)) + (t/is (= (:name instance1) "Rect 1")) + (t/is (= (:touched instance1) nil)) + (t/is (= (:shape-ref instance1) (:id c-main1))) + (t/is (= (:name shape2) "Rect 1")) + (t/is (= (:fill-color shape2) clr/test)) + (t/is (= (:stroke-width shape2) 0.5)) + (t/is (= (:touched shape2) nil)) + (t/is (= (:shape-ref shape2) (:id c-shape1))) - (t/is (= (:name instance2) "Rect 1")) - (t/is (= (:touched instance2) nil)) - (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:fill-color shape2) clr/test)) - (t/is (= (:stroke-width shape2) 0.2)) - (t/is (= (:touched shape2) #{:stroke-group})) + (t/is (= (:name instance2) "Rect 1")) + (t/is (= (:touched instance2) nil)) + (t/is (= (:shape-ref instance2) (:id c-main1))) + (t/is (= (:name shape3) "Rect 1")) + (t/is (= (:fill-color shape3) clr/test)) + (t/is (= (:stroke-width shape3) 0.2)) + (t/is (= (:touched shape3) #{:stroke-group})) + (t/is (= (:shape-ref shape3) (:id c-shape1))) - (t/is (= (:name c-instance1) "Rect 1")) - (t/is (= (:touched c-instance1) nil)) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:fill-color c-shape1) clr/test)) - (t/is (= (:stroke-width c-shape1) 0.5)) - (t/is (= (:touched c-shape1) nil)) + (t/is (= component1 component2 component3)) + (t/is (= c-main1 main1)) + (t/is (= c-shape1 shape1)) + (t/is (= c-instance1 c-main1)) + (t/is (= c-shape2 c-shape1)) + (t/is (= c-instance2 c-main1)) + (t/is (= c-shape3 c-shape1)))))] - (t/is (= component1 component2)) - (t/is (= c-instance2 c-instance1)) - (t/is (= c-shape2 c-shape1)))))] - - (ptk/emit! - store - (dch/update-shapes [(:id shape1)] update-fn1) - (dch/update-shapes [(:id shape2)] update-fn2) - (dwl/update-component-sync (:id instance1) (:id file)) - :the/end))))) + (ptk/emit! + store + (dch/update-shapes [(:id shape1')] + (fn [shape] + (merge shape {:fill-color clr/test + :stroke-width 0.5}))) + (dch/update-shapes [(:id shape1'')] + (fn [shape] + (merge shape {:stroke-width 0.2}))) + (dwl/update-component-sync (:id instance1) (:id file)) + :the/end)))) (t/deftest test-update-children-add (t/async done - (try - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1" - :fill-color clr/white - :fill-opacity 1}) - (thp/make-component :instance1 :component-1 - [(thp/id :shape1)]) - (thp/sample-shape :shape2 :circle - {:name "Circle 1"})) + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1" + :fill-color clr/white + :fill-opacity 1}) + (thp/make-component :main1 :component1 + [(thp/id :shape1)]) + (thp/instantiate-component :instance1 + (thp/id :component1)) + (thp/instantiate-component :instance2 + (thp/id :component1)) + (thp/sample-shape :shape2 :circle + {:name "Circle 1"})) - file (wsh/get-local-file state) + file (wsh/get-local-file state) - instance1 (thp/get-shape state :instance1) - shape2 (thp/get-shape state :shape2) + instance1 (thp/get-shape state :instance1) + shape2 (thp/get-shape state :shape2) - store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Rect 1 #--> Rect 1 - ; Circle 1 ---> Circle 1 - ; Rect 1 ---> Rect 1 - ; - ; [Rect 1] - ; Rect 1 - ; Circle 1 - ; Rect 1 - ; - (let [[[group shape1 shape2] - [c-group c-shape1 c-shape2] component] - (thl/resolve-instance-and-main - new-state - (thp/id :instance1))] + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1 + ; Circle 1 + ; Rect 1 + ; Rect 1 #--> Rect 1 + ; Circle 1 ---> Circle 1 + ; Rect 1 ---> Rect 1 + ; Rect 1 #--> Rect 1 + ; Circle 1 ---> Circle 1 + ; Rect 1 ---> Rect 1 + ; + ; [Rect 1] + ; page1 / Rect 1 + ; + (let [[[main1 shape1 shape2] + [c-main1 c-shape1 c-shape2] component1] + (thl/resolve-instance-and-main + new-state + (thp/id :main1)) - (t/is (= (:name group) "Rect 1")) - (t/is (= (:touched group) nil)) - (t/is (not= (:shape-ref group) nil)) - (t/is (= (:name shape1) "Circle 1")) - (t/is (= (:touched shape1) nil)) - (t/is (not= (:shape-ref shape1) nil)) - (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:touched shape2) nil)) - (t/is (not= (:shape-ref shape2) nil)) + [[instance1 shape3 shape4] + [c-instance1 c-shape3 c-shape4] component2] + (thl/resolve-instance-and-main + new-state + (thp/id :instance1)) - (t/is (= (:name c-group) "Rect 1")) - (t/is (= (:touched c-group) nil)) - (t/is (= (:shape-ref c-group) nil)) - (t/is (= (:name c-shape1) "Circle 1")) - (t/is (= (:touched c-shape1) nil)) - (t/is (= (:shape-ref c-shape1) nil)) - (t/is (= (:name c-shape2) "Rect 1")) - (t/is (= (:touched c-shape2) nil)) - (t/is (= (:shape-ref c-shape2) nil)))))] + [[instance2 shape5 shape6] + [c-instance2 c-shape5 c-shape6] component3] + (thl/resolve-instance-and-main + new-state + (thp/id :instance2))] - (ptk/emit! - store - (dw/relocate-shapes #{(:id shape2)} (:id instance1) 0) - (dwl/update-component-sync (:id instance1) (:id file)) - :the/end))))) + (t/is (= (:name main1) "Rect 1")) + (t/is (= (:touched main1) nil)) + (t/is (= (:shape-ref main1) nil)) + (t/is (= (:name shape1) "Circle 1")) + (t/is (= (:touched shape1) nil)) + (t/is (= (:shape-ref shape1) nil)) + (t/is (= (:name shape2) "Rect 1")) + (t/is (= (:touched shape2) nil)) + (t/is (= (:shape-ref shape2) nil)) + + (t/is (= (:name instance1) "Rect 1")) + (t/is (= (:touched instance1) nil)) + (t/is (= (:shape-ref instance1) (:id c-main1))) + (t/is (= (:name shape3) "Circle 1")) + (t/is (= (:touched shape3) nil)) + (t/is (= (:shape-ref shape3) (:id c-shape1))) + (t/is (= (:name shape4) "Rect 1")) + (t/is (= (:touched shape4) nil)) + (t/is (= (:shape-ref shape4) (:id c-shape2))) + + (t/is (= (:name instance2) "Rect 1")) + (t/is (= (:touched instance2) nil)) + (t/is (= (:shape-ref instance2) (:id c-main1))) + (t/is (= (:name shape5) "Circle 1")) + (t/is (= (:touched shape5) nil)) + (t/is (= (:shape-ref shape5) (:id c-shape1))) + (t/is (= (:name shape6) "Rect 1")) + (t/is (= (:touched shape6) nil)) + (t/is (= (:shape-ref shape4) (:id c-shape2))) + + (t/is (= component1 component2 component3)) + (t/is (= c-main1 main1)) + (t/is (= c-shape1 shape1)) + (t/is (= c-shape2 shape2)) + (t/is (= c-instance1 c-main1)) + (t/is (= c-shape3 c-shape1)) + (t/is (= c-shape4 c-shape2)) + (t/is (= c-instance2 c-main1)) + (t/is (= c-shape5 c-shape1)) + (t/is (= c-shape6 c-shape2)))))] + + (ptk/emit! + store + (dw/relocate-shapes #{(:id shape2)} (:id instance1) 0) + (dwl/update-component-sync (:id instance1) (:id file)) + :the/end)))) (t/deftest test-update-children-delete (t/async done - (try - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1"}) - (thp/sample-shape :shape2 :rect - {:name "Rect 2"}) - (thp/make-component :instance1 :component-1 - [(thp/id :shape1) - (thp/id :shape2)])) + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1"}) + (thp/sample-shape :shape2 :rect + {:name "Rect 2"}) + (thp/make-component :main1 :component1 + [(thp/id :shape1) + (thp/id :shape2)]) + (thp/instantiate-component :instance1 + (thp/id :component1)) + (thp/instantiate-component :instance2 + (thp/id :component1))) - file (wsh/get-local-file state) + file (wsh/get-local-file state) - instance1 (thp/get-shape state :instance1) - shape1 (thp/get-shape state :shape1) + [instance1 shape1' _shape2'] + (thl/resolve-instance state (thp/id :instance1)) - store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Component 1 #--> Component 1 - ; Rect 2 ---> Rect 2 - ; - ; [Component 1] - ; Component 1 - ; Rect 2 - ; - (let [[[group shape2] [c-group c-shape2] component] - (thl/resolve-instance-and-main - new-state - (thp/id :instance1))] + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Component 1 + ; Rect 1 + ; Rect 2 + ; Component 1 #--> Component 1 + ; Rect 1 ---> Rect 1 + ; Rect 2 ---> Rect 2 + ; Component 1 #--> Component 1 + ; Rect 1 ---> Rect 1 + ; Rect 2 ---> Rect 2 + ; + ; [Component 1] + ; page1 / Component 1 + ; + (let [[[main1 shape1 shape2] + [c-main1 c-shape1 c-shape2] component1] + (thl/resolve-instance-and-main + new-state + (thp/id :main1)) - (t/is (= (:name group) "Component 1")) - (t/is (= (:touched group) nil)) - (t/is (not= (:shape-ref group) nil)) - (t/is (= (:name shape2) "Rect 2")) - (t/is (= (:touched shape2) nil)) - (t/is (not= (:shape-ref shape2) nil)) + [[instance1 shape3 shape4] + [c-instance1 c-shape3 c-shape4] component2] + (thl/resolve-instance-and-main + new-state + (thp/id :instance1)) - (t/is (= (:name c-group) "Component 1")) - (t/is (= (:touched c-group) nil)) - (t/is (= (:shape-ref c-group) nil)) - (t/is (= (:name c-shape2) "Rect 2")) - (t/is (= (:touched c-shape2) nil)) - (t/is (= (:shape-ref c-shape2) nil)))))] + [[instance2 shape5 shape6] + [c-instance2 c-shape5 c-shape6] component3] + (thl/resolve-instance-and-main + new-state + (thp/id :instance2))] - (ptk/emit! - store - (dwsh/delete-shapes #{(:id shape1)}) - (dwl/update-component-sync (:id instance1) (:id file)) - :the/end))))) + (t/is (= (:name main1) "Component 1")) + (t/is (= (:touched main1) nil)) + (t/is (= (:shape-ref main1) nil)) + (t/is (= (:name shape1) "Rect 1")) + (t/is (= (:hidden shape1) true)) ; Instance shapes are not deleted but hidden + (t/is (= (:touched shape1) nil)) + (t/is (= (:shape-ref shape1) nil)) + (t/is (= (:name shape2) "Rect 2")) + (t/is (= (:hidden shape2) nil)) + (t/is (= (:touched shape2) nil)) + (t/is (= (:shape-ref shape2) nil)) + + (t/is (= (:name instance1) "Component 1")) + (t/is (= (:touched instance1) nil)) + (t/is (= (:shape-ref instance1) (:id c-main1))) + (t/is (= (:name shape3) "Rect 1")) + (t/is (= (:hidden shape3) true)) + (t/is (= (:touched shape3) nil)) + (t/is (= (:shape-ref shape3) (:id c-shape1))) + (t/is (= (:name shape4) "Rect 2")) + (t/is (= (:hidden shape4) nil)) + (t/is (= (:touched shape4) nil)) + (t/is (= (:shape-ref shape4) (:id c-shape2))) + + (t/is (= (:name instance2) "Component 1")) + (t/is (= (:touched instance2) nil)) + (t/is (= (:shape-ref instance2) (:id c-main1))) + (t/is (= (:name shape5) "Rect 1")) + (t/is (= (:hidden shape5) true)) + (t/is (= (:touched shape5) nil)) + (t/is (= (:shape-ref shape5) (:id c-shape1))) + (t/is (= (:name shape6) "Rect 2")) + (t/is (= (:hidden shape6) nil)) + (t/is (= (:touched shape6) nil)) + (t/is (= (:shape-ref shape6) (:id c-shape2))) + + (t/is (= component1 component2 component3)) + (t/is (= c-main1 main1)) + (t/is (= c-shape1 shape1)) + (t/is (= c-shape2 shape2)) + (t/is (= c-instance1 c-main1)) + (t/is (= c-shape3 c-shape1)) + (t/is (= c-shape4 c-shape2)) + (t/is (= c-instance2 c-main1)) + (t/is (= c-shape5 c-shape1)) + (t/is (= c-shape6 c-shape2)))))] + + (ptk/emit! + store + (dwsh/delete-shapes #{(:id shape1')}) + (dwl/update-component-sync (:id instance1) (:id file)) + :the/end)))) (t/deftest test-update-children-move (t/async done - (try - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1"}) - (thp/sample-shape :shape2 :rect - {:name "Rect 2"}) - (thp/sample-shape :shape3 :rect - {:name "Rect 3"}) - (thp/make-component :instance1 :component-1 - [(thp/id :shape1) - (thp/id :shape2) - (thp/id :shape3)])) + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1"}) + (thp/sample-shape :shape2 :rect + {:name "Rect 2"}) + (thp/sample-shape :shape3 :rect + {:name "Rect 3"}) + (thp/make-component :main1 :component1 + [(thp/id :shape1) + (thp/id :shape2) + (thp/id :shape3)]) + (thp/instantiate-component :instance1 + (thp/id :component1)) + (thp/instantiate-component :instance2 + (thp/id :component1))) - file (wsh/get-local-file state) + file (wsh/get-local-file state) - shape1 (thp/get-shape state :shape1) - instance1 (thp/get-shape state :instance1) + [instance1 shape1' _shape2' _shape3'] + (thl/resolve-instance state (thp/id :instance1)) - store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Component 1 #--> Component 1 - ; Rect 2 ---> Rect 2 - ; Rect 1 ---> Rect 1 - ; Rect 3 ---> Rect 3 - ; - ; [Component 1] - ; Component 1 - ; Rect 2 - ; Rect 1 - ; Rect 3 - ; - (let [[[group shape1 shape2 shape3] [c-group c-shape1 c-shape2 c-shape3] component] - (thl/resolve-instance-and-main - new-state - (thp/id :instance1))] + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Component 1 + ; Rect 2 + ; Rect 1 + ; Rect 3 + ; Component 1 #--> Component 1 + ; Rect 2 ---> Rect 2 + ; Rect 1 ---> Rect 1 + ; Rect 3 ---> Rect 3 + ; Component 1 #--> Component 1 + ; Rect 2 ---> Rect 2 + ; Rect 1 ---> Rect 1 + ; Rect 3 ---> Rect 3 + ; + ; [Component 1] + ; page1 / Component 1 + ; + (let [[[main1 shape1 shape2 shape3] + [c-main1 c-shape1 c-shape2 c-shape3] component1] + (thl/resolve-instance-and-main + new-state + (thp/id :main1)) - (t/is (= (:name group) "Component 1")) - (t/is (= (:touched group) nil)) - (t/is (not= (:shape-ref group) nil)) - (t/is (= (:touched shape1) nil)) - (t/is (not= (:shape-ref shape1) nil)) - (t/is (= (:name shape1) "Rect 2")) - (t/is (= (:touched shape2) nil)) - (t/is (not= (:shape-ref shape2) nil)) - (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:touched shape3) nil)) - (t/is (not= (:shape-ref shape3) nil)) - (t/is (= (:name shape3) "Rect 3")) + [[instance1 shape4 shape5 shape6] + [c-instance1 c-shape4 c-shape5 c-shape6] component2] + (thl/resolve-instance-and-main + new-state + (thp/id :instance1)) - (t/is (= (:name c-group) "Component 1")) - (t/is (= (:touched c-group) nil)) - (t/is (= (:shape-ref c-group) nil)) - (t/is (= (:name c-shape1) "Rect 2")) - (t/is (= (:touched c-shape1) nil)) - (t/is (= (:shape-ref c-shape1) nil)) - (t/is (= (:name c-shape2) "Rect 1")) - (t/is (= (:touched c-shape2) nil)) - (t/is (= (:shape-ref c-shape2) nil)) - (t/is (= (:name c-shape3) "Rect 3")) - (t/is (= (:touched c-shape3) nil)) - (t/is (= (:shape-ref c-shape3) nil)))))] + [[instance2 shape7 shape8 shape9] + [c-instance2 c-shape7 c-shape8 c-shape9] component3] + (thl/resolve-instance-and-main + new-state + (thp/id :instance2))] - (ptk/emit! - store - (dw/relocate-shapes #{(:id shape1)} (:id instance1) 2) - (dwl/update-component-sync (:id instance1) (:id file)) - :the/end))))) + (t/is (= (:name main1) "Component 1")) + (t/is (= (:touched main1) nil)) + (t/is (= (:shape-ref main1) nil)) + (t/is (= (:name shape1) "Rect 2")) + (t/is (= (:touched shape1) nil)) + (t/is (= (:shape-ref shape1) nil)) + (t/is (= (:name shape2) "Rect 1")) + (t/is (= (:touched shape2) nil)) + (t/is (= (:shape-ref shape2) nil)) + (t/is (= (:name shape3) "Rect 3")) + (t/is (= (:touched shape3) nil)) + (t/is (= (:shape-ref shape3) nil)) + + (t/is (= (:name instance1) "Component 1")) + (t/is (= (:touched instance1) nil)) + (t/is (= (:shape-ref instance1) (:id c-main1))) + (t/is (= (:name shape4) "Rect 2")) + (t/is (= (:touched shape4) nil)) + (t/is (= (:shape-ref shape4) (:id c-shape1))) + (t/is (= (:name shape5) "Rect 1")) + (t/is (= (:touched shape5) nil)) + (t/is (= (:shape-ref shape5) (:id c-shape2))) + (t/is (= (:name shape6) "Rect 3")) + (t/is (= (:touched shape6) nil)) + (t/is (= (:shape-ref shape6) (:id c-shape3))) + + (t/is (= (:name instance2) "Component 1")) + (t/is (= (:touched instance2) nil)) + (t/is (= (:shape-ref instance2) (:id c-main1))) + (t/is (= (:name shape7) "Rect 2")) + (t/is (= (:touched shape7) nil)) + (t/is (= (:shape-ref shape7) (:id c-shape1))) + (t/is (= (:name shape8) "Rect 1")) + (t/is (= (:touched shape8) nil)) + (t/is (= (:shape-ref shape8) (:id c-shape2))) + (t/is (= (:name shape9) "Rect 3")) + (t/is (= (:touched shape9) nil)) + (t/is (= (:shape-ref shape9) (:id c-shape3))) + + (t/is (= component1 component2 component3)) + (t/is (= c-main1 main1)) + (t/is (= c-shape1 shape1)) + (t/is (= c-shape2 shape2)) + (t/is (= c-shape3 shape3)) + (t/is (= c-instance1 c-main1)) + (t/is (= c-shape4 c-shape4)) + (t/is (= c-shape5 c-shape5)) + (t/is (= c-shape6 c-shape6)) + (t/is (= c-instance2 c-main1)) + (t/is (= c-shape7 c-shape7)) + (t/is (= c-shape8 c-shape8)) + (t/is (= c-shape9 c-shape9)))))] + + (ptk/emit! + store + (dw/relocate-shapes #{(:id shape1')} (:id instance1) 2) + (dwl/update-component-sync (:id instance1) (:id file)) + :the/end)))) (t/deftest test-update-from-lib (t/async done - (try - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1" - :fill-color clr/white - :fill-opacity 1}) - (thp/make-component :instance1 :component-1 - [(thp/id :shape1)]) - (thp/move-to-library :lib1 "Library 1") - (thp/sample-page) - (thp/instantiate-component :instance2 - (thp/id :component-1) - (thp/id :lib1))) + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1" + :fill-color clr/white + :fill-opacity 1}) + (thp/make-component :main1 :component1 + [(thp/id :shape1)]) + (thp/move-to-library :lib1 "Library 1") + (thp/sample-page) + (thp/instantiate-component :instance1 + (thp/id :component1) + (thp/id :lib1)) + (thp/instantiate-component :instance2 + (thp/id :component1) + (thp/id :lib1))) - [instance2 shape2] - (thl/resolve-instance state (thp/id :instance2)) + [instance1 shape1'] + (thl/resolve-instance state (thp/id :instance1)) - update-fn (fn [shape] - (merge shape {:fill-color clr/test - :fill-opacity 0.5})) + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1 #--> Rect 1 + ; Rect 1 ---> Rect 1 + ; Rect 1 #--> Rect 1 + ; Rect 1 ---> Rect 1 + ; + (let [[[instance1 shape1] [c-instance1 c-shape1] _component1] + (thl/resolve-instance-and-main + new-state + (thp/id :instance1)) + + [[instance2 shape2] [_c-instance2 _c-shape2] _component2] + (thl/resolve-instance-and-main + new-state + (thp/id :instance2))] - store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Rect 1 #--> Rect 1 (color, opacity) - ; Rect 1 ---> Rect 1 - ; - (let [[[group shape1] [c-group c-shape1] component] - (thl/resolve-instance-and-main - new-state - (:id instance2))] + (t/is (= (:name instance1) "Rect 1")) + (t/is (= (:touched instance1) nil)) + (t/is (= (:name shape1) "Rect 1")) + (t/is (= (:fill-color shape1) clr/test)) + (t/is (= (:fill-opacity shape1) 0.5)) + (t/is (= (:touched shape1) nil)) - (t/is (= (:name group) "Rect 1")) - (t/is (= (:touched group) nil)) - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:fill-color shape1) clr/test)) - (t/is (= (:fill-opacity shape1) 0.5)) - (t/is (= (:touched shape1) nil)) + (t/is (= (:name c-instance1) "Rect 1")) + (t/is (= (:touched c-instance1) nil)) + (t/is (= (:name c-shape1) "Rect 1")) + (t/is (= (:fill-color c-shape1) clr/test)) + (t/is (= (:fill-opacity c-shape1) 0.5)) + (t/is (= (:touched c-shape1) nil)) - (t/is (= (:name c-group) "Rect 1")) - (t/is (= (:touched c-group) nil)) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:fill-color c-shape1) clr/test)) - (t/is (= (:fill-opacity c-shape1) 0.5)) - (t/is (= (:touched c-shape1) nil)))))] + (t/is (= (:name instance2) "Rect 1")) + (t/is (= (:touched instance2) nil)) + (t/is (= (:name shape2) "Rect 1")) + (t/is (= (:fill-color shape2) clr/test)) + (t/is (= (:fill-opacity shape2) 0.5)) + (t/is (= (:touched shape2) nil)))))] - (ptk/emit! - store - (dch/update-shapes [(:id shape2)] update-fn) - (dwl/update-component (:id instance2)) - :the/end))))) + (ptk/emit! + store + (dch/update-shapes [(:id shape1')] + (fn [shape] + (merge shape {:fill-color clr/test + :fill-opacity 0.5}))) + (dwl/update-component-sync (:id instance1) (thp/id :lib1)) + :the/end)))) (t/deftest test-update-nested-upper (t/async done - (try - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1" - :fill-color clr/white - :fill-opacity 1}) - (thp/make-component :instance1 :component-1 - [(thp/id :shape1)]) - (thp/sample-shape :shape2 :circle - {:name "Circle 1" - :fill-color clr/black - :fill-opacity 0}) - (thp/group-shapes :group1 - [(thp/id :instance1) - (thp/id :shape2)]) - (thp/make-component :instance2 :component-2 - [(thp/id :group1)])) + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1" + :fill-color clr/white + :fill-opacity 1}) + (thp/make-component :main1 :component1 + [(thp/id :shape1)]) + (thp/instantiate-component :instance1 + (thp/id :component1)) + (thp/sample-shape :shape2 :circle + {:name "Circle 1" + :fill-color clr/black + :fill-opacity 0}) + (thp/group-shapes :group1 + [(thp/id :instance1) + (thp/id :shape2)]) + (thp/make-component :main2 :component2 + [(thp/id :group1)]) + (thp/instantiate-component :instance2 + (thp/id :component2)) + (thp/instantiate-component :instance3 + (thp/id :component2))) - [instance2 instance1 shape1 shape2] - (thl/resolve-instance state (thp/id :instance2)) + file (wsh/get-local-file state) - update-fn (fn [shape] - (merge shape {:fill-color clr/test - :fill-opacity 0.5})) + [instance2 _instance1 shape1' _shape2'] + (thl/resolve-instance state (thp/id :instance2)) - store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Group #--> Group - ; Rect 1 @--> Rect 1 - ; Rect 1 ---> Rect 1 - ; Circle 1 ---> Circle 1 (color, opacity) - ; - ; [Rect 1] - ; Rect 1 - ; Rect 1 - ; - ; [Group] - ; Group - ; Rect 1 @--> Rect 1 - ; Rect 1 ---> Rect 1 - ; Circle 1 (color, opacity) - ; - (let [[[instance2 instance1 shape1 shape2] - [c-instance2 c-instance1 c-shape1 c-shape2] component] - (thl/resolve-instance-and-main - new-state - (thp/id :instance2))] + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1 + ; Rect 1 + ; Group + ; Rect 1 #--> Rect 1 + ; Rect 1 ---> Rect 1 + ; Circle 1 + ; Group #--> Group + ; Rect 1 @--> Rect 1 + ; Rect 1 ---> Rect 1 + ; Circle 1 ---> Circle 1 + ; Group #--> Group + ; Rect 1 @--> Rect 1 + ; Rect 1 ---> Rect 1 + ; Circle 1 ---> Circle 1 + ; + ; [Rect 1] + ; page1 / Rect 1 + ; + ; [Group] + ; page1 / Group + ; + (let [[[instance2 instance1 shape1 shape2] + [c-instance2 c-instance1 c-shape1 c-shape2] _component1] + (thl/resolve-instance-and-main + new-state + (thp/id :instance2)) + + [[instance4 instance3 shape3 shape4] + [_c-instance4 _c-instance3 _c-shape3 _c-shape4] _component2] + (thl/resolve-instance-and-main + new-state + (thp/id :instance3))] - ; TODO: get and check the instance inside component [Group 1] + (t/is (= (:name instance2) "Group")) + (t/is (= (:touched instance2) nil)) + (t/is (= (:name instance1) "Rect 1")) + (t/is (= (:touched instance1) nil)) + (t/is (= (:name shape1) "Circle 1")) + (t/is (= (:touched shape1) nil)) + (t/is (= (:fill-color shape1) clr/test)) + (t/is (= (:fill-opacity shape1) 0.5)) + (t/is (= (:name shape2) "Rect 1")) + (t/is (= (:touched shape2) nil)) + (t/is (= (:fill-color shape2) clr/white)) + (t/is (= (:fill-opacity shape2) 1)) - (t/is (= (:name instance2) "Group")) - (t/is (= (:touched instance2) nil)) - (t/is (= (:name instance1) "Rect 1")) - (t/is (= (:touched instance1) nil)) - (t/is (= (:name shape1) "Circle 1")) - (t/is (= (:touched shape1) nil)) - (t/is (= (:fill-color shape1) clr/test)) - (t/is (= (:fill-opacity shape1) 0.5)) - (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:touched shape2) nil)) - (t/is (= (:fill-color shape2) clr/white)) - (t/is (= (:fill-opacity shape2) 1)) + (t/is (= (:name c-instance2) "Group")) + (t/is (= (:touched c-instance2) nil)) + (t/is (= (:name c-instance1) "Rect 1")) + (t/is (= (:touched c-instance1) nil)) + (t/is (= (:name c-shape1) "Circle 1")) + (t/is (= (:touched c-shape1) nil)) + (t/is (= (:fill-color c-shape1) clr/test)) + (t/is (= (:fill-opacity c-shape1) 0.5)) + (t/is (= (:name c-shape2) "Rect 1")) + (t/is (= (:touched c-shape2) nil)) + (t/is (= (:fill-color c-shape2) clr/white)) + (t/is (= (:fill-opacity c-shape2) 1)) - (t/is (= (:name c-instance2) "Group")) - (t/is (= (:touched c-instance2) nil)) - (t/is (= (:name c-instance1) "Rect 1")) - (t/is (= (:touched c-instance1) nil)) - (t/is (= (:name c-shape1) "Circle 1")) - (t/is (= (:touched c-shape1) nil)) - (t/is (= (:fill-color c-shape1) clr/test)) - (t/is (= (:fill-opacity c-shape1) 0.5)) - (t/is (= (:name c-shape2) "Rect 1")) - (t/is (= (:touched c-shape2) nil)) - (t/is (= (:fill-color c-shape2) clr/white)) - (t/is (= (:fill-opacity c-shape2) 1)))))] + (t/is (= (:name instance4) "Group")) + (t/is (= (:touched instance4) nil)) + (t/is (= (:name instance3) "Rect 1")) + (t/is (= (:touched instance3) nil)) + (t/is (= (:name shape3) "Circle 1")) + (t/is (= (:touched shape3) nil)) + (t/is (= (:fill-color shape3) clr/test)) + (t/is (= (:fill-opacity shape3) 0.5)) + (t/is (= (:name shape4) "Rect 1")) + (t/is (= (:touched shape4) nil)) + (t/is (= (:fill-color shape4) clr/white)) + (t/is (= (:fill-opacity shape4) 1)))))] - (ptk/emit! - store - (dch/update-shapes [(:id shape1)] update-fn) - (dwl/update-component (:id instance2)) - :the/end))))) + (ptk/emit! + store + (dch/update-shapes [(:id shape1')] + (fn [shape] + (merge shape {:fill-color clr/test + :fill-opacity 0.5}))) + (dwl/update-component-sync (:id instance2) (:id file)) + :the/end)))) (t/deftest test-update-nested-lower-near (t/async done - (try - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1" - :fill-color clr/white - :fill-opacity 1}) - (thp/make-component :instance1 :component-1 - [(thp/id :shape1)]) - (thp/sample-shape :shape2 :circle - {:name "Circle 1" - :fill-color clr/black - :fill-opacity 0}) - (thp/group-shapes :group1 - [(thp/id :instance1) - (thp/id :shape2)]) - (thp/make-component :instance2 :component-2 - [(thp/id :group1)])) + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1" + :fill-color clr/white + :fill-opacity 1}) + (thp/make-component :main1 :component1 + [(thp/id :shape1)]) + (thp/instantiate-component :instance1 + (thp/id :component1)) + (thp/sample-shape :shape2 :circle + {:name "Circle 1" + :fill-color clr/black + :fill-opacity 0}) + (thp/group-shapes :group1 + [(thp/id :instance1) + (thp/id :shape2)]) + (thp/make-component :main2 :component2 + [(thp/id :group1)]) + (thp/instantiate-component :instance2 + (thp/id :component2)) + (thp/instantiate-component :instance3 + (thp/id :component2))) - [instance2 instance1 shape1 shape2] - (thl/resolve-instance state (thp/id :instance2)) + file (wsh/get-local-file state) - update-fn (fn [shape] - (merge shape {:fill-color clr/test - :fill-opacity 0.5})) + [instance2 instance1 _shape1' shape2'] + (thl/resolve-instance state (thp/id :instance2)) - store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Group #--> Group - ; Rect 1 @--> Rect 1 - ; Rect 1 ---> Rect 1 (color, opacity) - ; Circle 1 ---> Circle 1 - ; - ; [Rect 1] - ; Rect 1 - ; Rect 1 - ; - ; [Group] - ; Group - ; Rect 1 @--> Rect 1 - ; Rect 1 ---> Rect 1 (color, opacity) - ; Circle 1 - ; - (let [[[instance2 instance1 shape1 shape2] - [c-instance2 c-instance1 c-shape1 c-shape2] component] - (thl/resolve-instance-and-main - new-state - (thp/id :instance2))] + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1 + ; Rect 1 + ; Group + ; Rect 1 #--> Rect 1 + ; Rect 1 ---> Rect 1 + ; Circle 1 + ; Group #--> Group + ; Rect 1 @--> Rect 1 + ; Rect 1 ---> Rect 1 + ; Circle 1 ---> Circle 1 + ; Group #--> Group + ; Rect 1 @--> Rect 1 + ; Rect 1 ---> Rect 1 + ; Circle 1 ---> Circle 1 + ; + ; [Rect 1] + ; page1 / Rect 1 + ; + ; [Group] + ; page1 / Group + ; + (let [[[instance2 instance1 shape1 shape2] + [c-instance2 c-instance1 c-shape1 c-shape2] _component1] + (thl/resolve-instance-and-main + new-state + (thp/id :instance2)) + + [[instance4 instance3 shape3 shape4] + [_c-instance4 _c-instance3 _c-shape3 _c-shape4] _component2] + (thl/resolve-instance-and-main + new-state + (thp/id :instance3))] - ; TODO: get and check the instance inside component [Group 1] + (t/is (= (:name instance2) "Group")) + (t/is (= (:touched instance2) nil)) + (t/is (= (:name instance1) "Rect 1")) + (t/is (= (:touched instance1) nil)) + (t/is (= (:name shape1) "Circle 1")) + (t/is (= (:touched shape1) nil)) + (t/is (= (:fill-color shape1) clr/black)) + (t/is (= (:fill-opacity shape1) 0)) + (t/is (= (:name shape2) "Rect 1")) + (t/is (= (:touched shape2) nil)) + (t/is (= (:fill-color shape2) clr/test)) + (t/is (= (:fill-opacity shape2) 0.5)) - (t/is (= (:name instance2) "Group")) - (t/is (= (:touched instance2) nil)) - (t/is (= (:name instance1) "Rect 1")) - (t/is (= (:touched instance1) nil)) - (t/is (= (:name shape1) "Circle 1")) - (t/is (= (:touched shape1) nil)) - (t/is (= (:fill-color shape1) clr/black)) - (t/is (= (:fill-opacity shape1) 0)) - (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:touched shape2) nil)) - (t/is (= (:fill-color shape2) clr/test)) - (t/is (= (:fill-opacity shape2) 0.5)) + (t/is (= (:name c-instance2) "Group")) + (t/is (= (:touched c-instance2) nil)) + (t/is (= (:name c-instance1) "Rect 1")) + (t/is (= (:touched c-instance1) nil)) + (t/is (= (:name c-shape1) "Circle 1")) + (t/is (= (:touched c-shape1) nil)) + (t/is (= (:fill-color c-shape1) clr/black)) + (t/is (= (:fill-opacity c-shape1) 0)) + (t/is (= (:name c-shape2) "Rect 1")) + (t/is (= (:touched c-shape2) nil)) + (t/is (= (:fill-color c-shape2) clr/test)) + (t/is (= (:fill-opacity c-shape2) 0.5)) - (t/is (= (:name c-instance2) "Group")) - (t/is (= (:touched c-instance2) nil)) - (t/is (= (:name c-instance1) "Rect 1")) - (t/is (= (:touched c-instance1) nil)) - (t/is (= (:name c-shape1) "Circle 1")) - (t/is (= (:touched c-shape1) nil)) - (t/is (= (:fill-color c-shape1) clr/black)) - (t/is (= (:fill-opacity c-shape1) 0)) - (t/is (= (:name c-shape2) "Rect 1")) - (t/is (= (:touched c-shape2) nil)) - (t/is (= (:fill-color c-shape2) clr/test)) - (t/is (= (:fill-opacity c-shape2) 0.5)))))] + (t/is (= (:name instance4) "Group")) + (t/is (= (:touched instance4) nil)) + (t/is (= (:name instance3) "Rect 1")) + (t/is (= (:touched instance3) nil)) + (t/is (= (:name shape3) "Circle 1")) + (t/is (= (:touched shape3) nil)) + (t/is (= (:fill-color shape3) clr/black)) + (t/is (= (:fill-opacity shape3) 0)) + (t/is (= (:name shape4) "Rect 1")) + (t/is (= (:touched shape4) nil)) + (t/is (= (:fill-color shape4) clr/test)) + (t/is (= (:fill-opacity shape4) 0.5)))))] - (ptk/emit! - store - (dch/update-shapes [(:id shape2)] update-fn) - (dwl/update-component (:id instance1)) - (dwl/update-component (:id instance2)) - :the/end))))) + (ptk/emit! + store + (dch/update-shapes [(:id shape2')] + (fn [shape] + (merge shape {:fill-color clr/test + :fill-opacity 0.5}))) + (dwl/update-component (:id instance1)) + (dwl/update-component-sync (:id instance2) (:id file)) + :the/end)))) (t/deftest test-update-nested-lower-remote (t/async done - (try - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1" - :fill-color clr/white - :fill-opacity 1}) - (thp/make-component :instance1 :component-1 - [(thp/id :shape1)]) - (thp/sample-shape :shape2 :circle - {:name "Circle 1" - :fill-color clr/black - :fill-opacity 0}) - (thp/group-shapes :group1 - [(thp/id :instance1) - (thp/id :shape2)]) - (thp/make-component :instance2 :component-2 - [(thp/id :group1)])) + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1" + :fill-color clr/white + :fill-opacity 1}) + (thp/make-component :main1 :component1 + [(thp/id :shape1)]) + (thp/instantiate-component :instance1 + (thp/id :component1)) + (thp/sample-shape :shape2 :circle + {:name "Circle 1" + :fill-color clr/black + :fill-opacity 0}) + (thp/group-shapes :group1 + [(thp/id :instance1) + (thp/id :shape2)]) + (thp/make-component :main2 :component2 + [(thp/id :group1)]) + (thp/instantiate-component :instance2 + (thp/id :component2)) + (thp/instantiate-component :instance3 + (thp/id :component2))) - [instance2 instance1 shape1 shape2] - (thl/resolve-instance state (thp/id :instance2)) + file (wsh/get-local-file state) - update-fn (fn [shape] - (merge shape {:fill-color clr/test - :fill-opacity 0.5})) + [_instance2 instance1 _shape1' shape2'] + (thl/resolve-instance state (thp/id :instance2)) - store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Group #--> Group - ; Rect 1 @--> Rect 1 - ; Rect 1 ---> Rect 1 (color, opacity) - ; Circle 1 ---> Circle 1 - ; - ; [Rect 1] - ; Rect 1 - ; Rect 1 (color, opacity) - ; - ; [Group] - ; Group - ; Rect 1 @--> Rect 1 - ; Rect 1 ---> Rect 1 - ; Circle 1 - ; - (let [[[instance2 instance1 shape1 shape2] - [c-instance2 c-instance1 c-shape1 c-shape2] component] - (thl/resolve-instance-and-main - new-state - (thp/id :instance2))] + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1 + ; Rect 1 + ; Group + ; Rect 1 #--> Rect 1 + ; Rect 1 ---> Rect 1 + ; Circle 1 + ; Group #--> Group + ; Rect 1 @--> Rect 1 + ; (remote-synced) + ; Rect 1 ---> Rect 1 + ; (remote-synced) + ; Circle 1 ---> Circle 1 + ; Group #--> Group + ; Rect 1 @--> Rect 1 + ; Rect 1 ---> Rect 1 + ; Circle 1 ---> Circle 1 + ; + ; [Rect 1] + ; page1 / Rect 1 + ; + ; [Group] + ; page1 / Group + ; + (let [[[instance2 instance1 shape1 shape2] + [c-instance2 c-instance1 c-shape1 c-shape2] _component1] + (thl/resolve-instance-and-main + new-state + (thp/id :instance2)) + + [[instance4 instance3 shape3 shape4] + [_c-instance4 _c-instance3 _c-shape3 _c-shape4] _component2] + (thl/resolve-instance-and-main + new-state + (thp/id :instance3))] - ; TODO: get and check the instance inside component [Group 1] + (t/is (= (:name instance2) "Group")) + (t/is (= (:touched instance2) nil)) + (t/is (= (:name instance1) "Rect 1")) + (t/is (= (:touched instance1) nil)) + (t/is (= (:name shape1) "Circle 1")) + (t/is (= (:touched shape1) nil)) + (t/is (= (:fill-color shape1) clr/black)) + (t/is (= (:fill-opacity shape1) 0)) + (t/is (= (:name shape2) "Rect 1")) + (t/is (= (:touched shape2) nil)) + (t/is (= (:fill-color shape2) clr/test)) + (t/is (= (:fill-opacity shape2) 0.5)) - (t/is (= (:name instance2) "Group")) - (t/is (= (:touched instance2) nil)) - (t/is (= (:name instance1) "Rect 1")) - (t/is (= (:touched instance1) nil)) - (t/is (= (:name shape1) "Circle 1")) - (t/is (= (:touched shape1) nil)) - (t/is (= (:fill-color shape1) clr/black)) - (t/is (= (:fill-opacity shape1) 0)) - (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:touched shape2) nil)) - (t/is (= (:fill-color shape2) clr/test)) - (t/is (= (:fill-opacity shape2) 0.5)) + (t/is (= (:name c-instance2) "Group")) + (t/is (= (:touched c-instance2) nil)) + (t/is (= (:name c-instance1) "Rect 1")) + (t/is (= (:touched c-instance1) nil)) + (t/is (= (:name c-shape1) "Circle 1")) + (t/is (= (:touched c-shape1) nil)) + (t/is (= (:fill-color c-shape1) clr/black)) + (t/is (= (:fill-opacity c-shape1) 0)) + (t/is (= (:name c-shape2) "Rect 1")) + (t/is (= (:touched c-shape2) nil)) + (t/is (= (:fill-color c-shape2) clr/test)) + (t/is (= (:fill-opacity c-shape2) 0.5)) - (t/is (= (:name c-instance2) "Group")) - (t/is (= (:touched c-instance2) nil)) - (t/is (= (:name c-instance1) "Rect 1")) - (t/is (= (:touched c-instance1) nil)) - (t/is (= (:name c-shape1) "Circle 1")) - (t/is (= (:touched c-shape1) nil)) - (t/is (= (:fill-color c-shape1) clr/black)) - (t/is (= (:fill-opacity c-shape1) 0)) - (t/is (= (:name c-shape2) "Rect 1")) - (t/is (= (:touched c-shape2) nil)) - (t/is (= (:fill-color c-shape2) clr/white)) - (t/is (= (:fill-opacity c-shape2) 1)))))] - - (ptk/emit! - store - (dch/update-shapes [(:id shape2)] update-fn) - (dwl/update-component (:id instance1)) - :the/end))))) + (t/is (= (:name instance4) "Group")) + (t/is (= (:touched instance4) nil)) + (t/is (= (:name instance3) "Rect 1")) + (t/is (= (:touched instance3) nil)) + (t/is (= (:name shape3) "Circle 1")) + (t/is (= (:touched shape3) nil)) + (t/is (= (:fill-color shape3) clr/black)) + (t/is (= (:fill-opacity shape3) 0)) + (t/is (= (:name shape4) "Rect 1")) + (t/is (= (:touched shape4) nil)) + (t/is (= (:fill-color shape4) clr/test)) + (t/is (= (:fill-opacity shape4) 0.5)))))] + (ptk/emit! + store + (dch/update-shapes [(:id shape2')] + (fn [shape] + (merge shape {:fill-color clr/test + :fill-opacity 0.5}))) + (dwl/update-component-sync (:id instance1) (:id file)) + :the/end)))) diff --git a/frontend/test/frontend_tests/state_components_test.cljs b/frontend/test/frontend_tests/state_components_test.cljs index e0bfdfe84..5315f79d5 100644 --- a/frontend/test/frontend_tests/state_components_test.cljs +++ b/frontend/test/frontend_tests/state_components_test.cljs @@ -1,24 +1,19 @@ (ns frontend-tests.state-components-test (:require - [app.common.data :as d] - [app.common.geom.point :as gpt] - [app.common.pages.helpers :as cph] - [app.common.types.container :as ctn] - [app.common.types.file :as ctf] - [app.main.data.workspace :as dw] - [app.main.data.workspace.groups :as dwg] - [app.main.data.workspace.libraries :as dwl] - [app.main.data.workspace.libraries-helpers :as dwlh] - [app.main.data.workspace.state-helpers :as wsh] - [beicon.core :as rx] - [cljs.pprint :refer [pprint]] - [cljs.test :as t :include-macros true] - [clojure.stacktrace :as stk] - [frontend-tests.helpers.events :as the] - [frontend-tests.helpers.libraries :as thl] - [frontend-tests.helpers.pages :as thp] - [linked.core :as lks] - [potok.core :as ptk])) + [app.common.geom.point :as gpt] + [app.common.types.components-list :as ctkl] + [app.common.types.container :as ctn] + [app.common.types.file :as ctf] + [app.main.data.workspace :as dw] + [app.main.data.workspace.groups :as dwg] + [app.main.data.workspace.libraries :as dwl] + [app.main.data.workspace.state-helpers :as wsh] + [cljs.test :as t :include-macros true] + [frontend-tests.helpers.events :as the] + [frontend-tests.helpers.libraries :as thl] + [frontend-tests.helpers.pages :as thp] + [linked.core :as lks] + [potok.core :as ptk])) (t/use-fixtures :each {:before thp/reset-idmap!}) @@ -37,7 +32,8 @@ ;; Uncomment to debug ;; (ctf/dump-tree (get new-state :workspace-data) ;; (get new-state :current-page-id) - ;; (get new-state :workspace-libraries)) + ;; (get new-state :workspace-libraries) + ;; false true) ; Expected shape tree: ; @@ -54,8 +50,8 @@ [[group shape1] [c-group c-shape1] component] (thl/resolve-instance-and-main - new-state - (:parent-id shape1)) + new-state + (:parent-id shape1)) file (wsh/get-local-file new-state)] @@ -73,40 +69,6 @@ (dwl/add-component) :the/end))))) -;; Remove definitely when we ensure that the other method works -;; well in more advanced tests. -#_(t/deftest test-add-component-from-single-shape - (t/async - done - (let [state (-> thp/initial-state - (thp/sample-page) - (thp/sample-shape :shape1 :rect - {:name "Rect 1"}))] - - (->> state - (the/do-update (dw/select-shape (thp/id :shape1))) - (the/do-watch-update dwl/add-component) - (rx/do - (fn [new-state] - (let [shape1 (thp/get-shape new-state :shape1) - - [[group shape1] [c-group c-shape1] component] - (thl/resolve-instance-and-main - new-state - (:parent-id shape1)) - - file (wsh/get-local-file new-state)] - - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:name group) "Component 1")) - (t/is (= (:name component) "Component 1")) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:name c-group) "Component 1")) - - (thl/is-from-file group file)))) - - (rx/subs done #(throw %)))))) - (t/deftest test-add-component-from-several-shapes (t/async done @@ -120,17 +82,14 @@ store (the/prepare-store state done (fn [new-state] ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Component 1 #--> Component 1 - ; Rect 1 ---> Rect 1 - ; Rect-2 ---> Rect-2 - ; - ; [Component 1] - ; Component 1 - ; Rect 1 - ; Rect-2 + ; [Page] + ; Root Frame + ; Component 1 + ; Rect 1 + ; Rect-2 + ; + ; [Component 1] + ; page1 / Component 1 ; (let [shape1 (thp/get-shape new-state :shape1) @@ -138,8 +97,8 @@ [c-group c-shape1 c-shape2] component] (thl/resolve-instance-and-main - new-state - (:parent-id shape1)) + new-state + (:parent-id shape1)) file (wsh/get-local-file new-state)] @@ -174,38 +133,36 @@ (thp/id :shape2)])) store (the/prepare-store state done - (fn [new-state] + (fn [new-state] ; Expected shape tree: ; - ; [Page] - ; Root Frame - ; Group #--> Group - ; Rect 1 ---> Rect 1 - ; Rect-2 ---> Rect-2 + ; [Page] + ; Root Frame + ; Group + ; Rect 1 + ; Rect-2 + ; + ; [Group] + ; page1 / Group ; - ; [Group] - ; Group - ; Rect 1 - ; Rect-2 - ; - (let [[[group shape1 shape2] - [c-group c-shape1 c-shape2] - component] - (thl/resolve-instance-and-main - new-state - (thp/id :group1)) + (let [[[group shape1 shape2] + [c-group c-shape1 c-shape2] + component] + (thl/resolve-instance-and-main + new-state + (thp/id :group1)) - file (wsh/get-local-file new-state)] + file (wsh/get-local-file new-state)] - (t/is (= (:name shape1) "Rect 1")) - (t/is (= (:name shape2) "Rect-2")) - (t/is (= (:name group) "Group")) - (t/is (= (:name component) "Group")) - (t/is (= (:name c-shape1) "Rect 1")) - (t/is (= (:name c-shape2) "Rect-2")) - (t/is (= (:name c-group) "Group")) + (t/is (= (:name shape1) "Rect 1")) + (t/is (= (:name shape2) "Rect-2")) + (t/is (= (:name group) "Group")) + (t/is (= (:name component) "Group")) + (t/is (= (:name c-shape1) "Rect 1")) + (t/is (= (:name c-shape2) "Rect-2")) + (t/is (= (:name c-group) "Group")) - (thl/is-from-file group file))))] + (thl/is-from-file group file))))] (ptk/emit! store @@ -220,42 +177,39 @@ (thp/sample-page) (thp/sample-shape :shape1 :rect {:name "Rect 1"}) - (thp/make-component :instance1 :component1 + (thp/make-component :main1 :component1 [(thp/id :shape1)])) store (the/prepare-store state done (fn [new-state] ; Expected shape tree: ; - ; [Page] - ; Root Frame - ; Rect 1 #--> Rect 1 - ; Rect 1 @--> Rect 1 - ; Rect 1 ---> Rect 1 - ; - ; [Rect 1] - ; Rect 1 - ; Rect 1 - ; - ; [Rect 1] - ; Rect 1 - ; Rect 1 @--> Rect 1 - ; Rect 1 ---> Rect 1 + ; [Page] + ; Root Frame + ; Rect 1 + ; Rect 1 + ; Rect 1 + ; + ; [Rect 1] + ; page1 / Rect 1 + ; + ; [Rect 1] + ; page1 / Rect 1 ; (let [[[instance1 shape1] [c-instance1 c-shape1] component1] (thl/resolve-instance-and-main - new-state - (thp/id :instance1) - true) + new-state + (thp/id :main1) + true) [[instance2 instance1' shape1'] [c-instance2 c-instance1' c-shape1'] component2] (thl/resolve-instance-and-main - new-state - (:parent-id instance1))] + new-state + (:parent-id instance1))] (t/is (= (:name shape1) "Rect 1")) (t/is (= (:name instance1) "Rect 1")) @@ -273,7 +227,7 @@ (ptk/emit! store - (dw/select-shape (thp/id :instance1)) + (dw/select-shape (thp/id :main1)) (dwl/add-component) :the/end)))) @@ -284,34 +238,34 @@ (thp/sample-page) (thp/sample-shape :shape1 :rect {:name "Rect 1"}) - (thp/make-component :instance1 :component-1 + (thp/make-component :main1 :component1 [(thp/id :shape1)])) - instance1 (thp/get-shape state :instance1) + main1 (thp/get-shape state :main1) store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Rect-2 #--> Renamed component - ; Rect 1 ---> Rect 1 - ; - ; [Renamed] - ; Renamed component - ; Rect 1 - (let [libs (wsh/get-libraries new-state) - component (cph/get-component libs - (:component-file instance1) - (:component-id instance1))] - (t/is (= (:name component) - "Renamed component")))))] + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1 + ; Rect 1 + ; + ; [Renamed component] + ; page1 / Rect 1 + ; + (let [libs (wsh/get-libraries new-state) + component (ctf/get-component libs + (:component-file main1) + (:component-id main1))] + (t/is (= (:name component) + "Renamed component")))))] (ptk/emit! - store - (dwl/rename-component (:component-id instance1) "Renamed component") - :the/end)))) + store + (dwl/rename-component (:component-id main1) "Renamed component") + :the/end)))) (t/deftest test-duplicate-component (t/async @@ -320,28 +274,28 @@ (thp/sample-page) (thp/sample-shape :shape1 :rect {:name "Rect 1"}) - (thp/make-component :instance1 :component-1 + (thp/make-component :main1 :component1 [(thp/id :shape1)])) - instance1 (thp/get-shape state :instance1) - component-id (:component-id instance1) + main1 (thp/get-shape state :main1) + component-id (:component-id main1) store (the/prepare-store state done (fn [new-state] ; Expected shape tree: ; - ; [Page] - ; Root Frame - ; Rect 1 #--> Rect 1 - ; Rect 1 ---> Rect 1 - ; + ; [Page] + ; Root Frame + ; Rect 1 + ; Rect 1 + ; Rect 1 #--> Rect 1 + ; Rect 1 ---> Rect 1 + ; ; [Rect 1] - ; Rect 1 - ; Rect 1 - ; - ; [Rect 1] - ; Rect 1 - ; Rect 1 + ; page1 / Rect 1 + ; + ; [Rect 1] + ; page1 / Rect 1 ; (let [new-component-id (->> (get-in new-state [:workspace-data @@ -350,24 +304,24 @@ (filter #(not= % component-id)) (first)) - [[instance1 shape1] - [c-instance1 c-shape1] - component1] + [[_instance1 _shape1] + [_c-instance1 _c-shape1] + _component1] (thl/resolve-instance-and-main - new-state - (:id instance1)) + new-state + (:id main1)) - [[c-component2 c-shape2] + [[_c-component2 _c-shape2] component2] (thl/resolve-component - new-state - new-component-id)] + new-state + new-component-id)] (t/is (= (:name component2) "Rect 1")))))] (ptk/emit! store - (dwl/duplicate-component {:id component-id}) + (dwl/duplicate-component thp/current-file-id component-id) :the/end)))) (t/deftest test-delete-component @@ -377,42 +331,115 @@ (thp/sample-page) (thp/sample-shape :shape1 :rect {:name "Rect 1"}) - (thp/make-component :instance1 :component-1 - [(thp/id :shape1)])) - - file (wsh/get-local-file state) - - instance1 (thp/get-shape state :instance1) - component-id (:component-id instance1) + (thp/make-component :main1 :component1 + [(thp/id :shape1)]) + (thp/instantiate-component :instance1 + (thp/id :component1))) store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Rect-2 - ; Rect 1 - ; - (let [[instance1 shape1] - (thl/resolve-noninstance - new-state - (:id instance1)) + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1 #--> ? + ; Rect 1 ---> ? + ; + (let [[main1 shape1] + (thl/resolve-noninstance + new-state + (thp/id :main1)) - libs (wsh/get-libraries new-state) - component (cph/get-component libs - (:component-file instance1) - (:component-id instance1))] + [[instance1 shape2] [c-instance1 c-shape2] component1] + (thl/resolve-instance-and-main-allow-dangling + new-state + (thp/id :instance1)) - (t/is (some? instance1)) - (t/is (some? shape1)) - (t/is (nil? component)))))] + file (wsh/get-local-file new-state) + component2 (ctkl/get-component file (thp/id :component1)) + component3 (ctkl/get-deleted-component file (thp/id :component1)) + + saved-objects (:objects component3) + saved-main1 (get saved-objects (:shape-ref instance1)) + saved-shape2 (get saved-objects (:shape-ref shape2))] + + (t/is (nil? main1)) + (t/is (nil? shape1)) + + (t/is (= (:name instance1) "Rect 1")) + (t/is (= (:touched instance1) nil)) + (t/is (not= (:shape-ref instance1) nil)) + (t/is (= (:name shape2) "Rect 1")) + (t/is (= (:touched shape2) nil)) + (t/is (not= (:shape-ref shape2) nil)) + (t/is (nil? c-instance1)) + (t/is (nil? c-shape2)) + (t/is (nil? component1)) + + (t/is (nil? component2)) + + (t/is (= (:name component3) "Rect 1")) + (t/is (= (:deleted component3) true)) + (t/is (some? (:objects component3))) + + (t/is (= (:name saved-main1) "Rect 1")) + (t/is (= (:name saved-shape2) "Rect 1")))))] (ptk/emit! - store - (dwl/delete-component {:id component-id}) - (dwl/sync-file (:id file) (:id file) :components component-id) - :the/end)))) + store + (dwl/delete-component {:id (thp/id :component1)}) + :the/end)))) + +(t/deftest test-restore-component + (t/async + done + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1"}) + (thp/make-component :main1 :component1 + [(thp/id :shape1)]) + (thp/instantiate-component :instance1 + (thp/id :component1))) + + store (the/prepare-store state done + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1 #--> Rect 1 + ; Rect 1 ---> Rect 1 + ; Rect 1 + ; Rect 1 + ; + ; [Rect 1] + ; page1 / Rect 1 + ; + (let [[[instance1 shape2] [c-instance1 c-shape2] component1] + (thl/resolve-instance-and-main + new-state + (thp/id :instance1)) + + file (wsh/get-local-file new-state) + component2 (ctkl/get-component file (thp/id :component1)) + + saved-objects (:objects component2)] + + (t/is (= (:name instance1) "Rect 1")) + (t/is (= (:name shape2) "Rect 1")) + (t/is (= (:name c-instance1) "Rect 1")) + (t/is (= (:name c-shape2) "Rect 1")) + + (t/is (some? component1)) + (t/is (some? component2)) + (t/is (nil? saved-objects)))))] + + (ptk/emit! + store + (dwl/delete-component {:id (thp/id :component1)}) + (dwl/restore-component thp/current-file-id (thp/id :component1)) + :the/end)))) (t/deftest test-instantiate-component (t/async @@ -421,46 +448,45 @@ (thp/sample-page) (thp/sample-shape :shape1 :rect {:name "Rect 1"}) - (thp/make-component :instance1 :component-1 + (thp/make-component :main1 :component1 [(thp/id :shape1)])) file (wsh/get-local-file state) - component-id (thp/id :component-1) - instance1 (thp/get-shape state :instance1) + component-id (thp/id :component1) + main1 (thp/get-shape state :main1) store (the/prepare-store state done (fn [new-state] ; Expected shape tree: ; - ; [Page] - ; Root Frame + ; [Page] + ; Root Frame + ; Rect 1 + ; Rect 1 ; Rect 1 #--> Rect 1 - ; Rect 1 ---> Rect 1 - ; Rect 1 #--> Rect 1 - ; Rect 1 ---> Rect 1 - ; - ; [Rect 1] - ; Rect 1 - ; Rect 1 + ; Rect 1 ---> Rect 1 + ; + ; [Rect 1] + ; page1 / Rect 1 ; (let [new-instance-id (-> new-state wsh/lookup-selected first) - [[instance2 shape2] - [c-instance2 c-shape2] + [[instance1 shape2] + [c-instance1 c-shape2] component] (thl/resolve-instance-and-main - new-state - new-instance-id)] + new-state + new-instance-id)] - (t/is (not= (:id instance1) (:id instance2))) + (t/is (not= (:id main1) (:id instance1))) (t/is (= (:id component) component-id)) - (t/is (= (:name instance2) "Rect 1")) + (t/is (= (:name instance1) "Rect 1")) (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:name c-instance2) "Rect 1")) + (t/is (= (:name c-instance1) "Rect 1")) (t/is (= (:name c-shape2) "Rect 1")) - (t/is (= (:component-file instance2) + (t/is (= (:component-file instance1) thp/current-file-id)))))] (ptk/emit! @@ -477,13 +503,13 @@ (thp/sample-page) (thp/sample-shape :shape1 :rect {:name "Rect 1"}) - (thp/make-component :instance1 :component-1 + (thp/make-component :main1 :component1 [(thp/id :shape1)]) (thp/move-to-library :lib1 "Library 1") (thp/sample-page)) library-id (thp/id :lib1) - component-id (thp/id :component-1) + component-id (thp/id :component1) store (the/prepare-store state done (fn [new-state] @@ -498,19 +524,19 @@ wsh/lookup-selected first) - [[instance2 shape2] - [c-instance2 c-shape2] + [[instance1 shape2] + [c-instance1 c-shape2] component] (thl/resolve-instance-and-main new-state new-instance-id)] (t/is (= (:id component) component-id)) - (t/is (= (:name instance2) "Rect 1")) + (t/is (= (:name instance1) "Rect 1")) (t/is (= (:name shape2) "Rect 1")) - (t/is (= (:name c-instance2) "Rect 1")) + (t/is (= (:name c-instance1) "Rect 1")) (t/is (= (:name c-shape2) "Rect 1")) - (t/is (= (:component-file instance2) library-id)))))] + (t/is (= (:component-file instance1) library-id)))))] (ptk/emit! store @@ -526,32 +552,34 @@ (thp/sample-page) (thp/sample-shape :shape1 :rect {:name "Rect 1"}) - (thp/make-component :instance1 :component-1 - [(thp/id :shape1)])) + (thp/make-component :main1 :component1 + [(thp/id :shape1)]) + (thp/instantiate-component :instance1 + (thp/id :component1))) - instance1 (thp/get-shape state :instance1) - component-id (:component-id instance1) + instance1 (thp/get-shape state :instance1) store (the/prepare-store state done - (fn [new-state] - ; Expected shape tree: - ; - ; [Page] - ; Root Frame - ; Rect-2 - ; Rect 1 - ; - ; [Rect-2] - ; Rect-2 - ; Rect 1 - ; - (let [[instance1 shape1] - (thl/resolve-noninstance - new-state - (:id instance1))] + (fn [new-state] + ; Expected shape tree: + ; + ; [Page] + ; Root Frame + ; Rect 1 + ; Rect 1 + ; Rect 1 + ; Rect 1 + ; + ; [Rect 1] + ; page1 / Rect 1 + ; + (let [[instance2 shape1] + (thl/resolve-noninstance + new-state + (:id instance1))] - (t/is (some? instance1)) - (t/is (some? shape1)))))] + (t/is (some? instance2)) + (t/is (some? shape1)))))] (ptk/emit! store @@ -566,28 +594,21 @@ (thp/sample-shape :shape1 :rect {:name "Rect 1"})) - file (wsh/get-local-file state) - instance1 (thp/get-shape state :instance1) - component-id (:component-id instance1) - store (the/prepare-store state done (fn [new-state] ; Expected shape tree: ; - ; [Page] - ; Root Frame - ; Group #--> Group - ; Rect 1 @--> Rect 1 - ; Rect 1 ---> Rect 1 - ; + ; [Page] + ; Root Frame + ; Group + ; Rect 1 + ; Rect 1 + ; ; [Rect 1] - ; Rect 1 - ; Rect 1 - ; - ; [Group] - ; Group - ; Rect 1 @--> Rect 1 - ; Rect 1 ---> Rect 1 + ; page1 / Rect 1 + ; + ; [Group] + ; page1 / Group ; (let [page (thp/current-page new-state) shape1 (thp/get-shape new-state :shape1) @@ -623,46 +644,41 @@ (thp/sample-page) (thp/sample-shape :shape1 :rect {:name "Rect 1"}) - (thp/make-component :instance1 :component-1 + (thp/make-component :main1 :component1 [(thp/id :shape1)]) - (thp/group-shapes :group1 - [(thp/id :instance1)]) - (thp/make-component :instance2 :component-2 - [(thp/id :group1)])) + (thp/make-component :main2 :component-2 + [(thp/id :main1)])) file (wsh/get-local-file state) - instance1 (thp/get-shape state :instance1) - instance2 (thp/get-shape state :instance2) - component-id (:component-id instance2) + main1 (thp/get-shape state :main1) + main2 (thp/get-shape state :main2) + component-id (:component-id main2) store (the/prepare-store state done (fn [new-state] ; Expected shape tree: ; - ; [Page] - ; Root Frame - ; Rect 1 #--> Rect 1 - ; Rect 1 @--> Rect 1 - ; Rect 1 ---> Rect 1 - ; Rect 1 #--> Rect 1 - ; Rect 1 @--> Rect 1 - ; Rect 1 ---> Rect 1 - ; + ; [Page] + ; Root Frame + ; Rect 1 + ; Rect 1 + ; Rect 1 + ; Rect 1 #--> Rect 1 + ; Rect 1 @--> Rect 1 + ; Rect 1 ---> Rect 1 + ; ; [Rect 1] - ; Rect 1 - ; Rect 1 - ; - ; [Rect 1] - ; Rect 1 - ; Rect 1 @--> Rect 1 - ; Rect 1 ---> Rect 1 + ; page1 / Rect 1 + ; + ; [Rect 1] + ; page1 / Rect 1 ; (let [new-instance-id (-> new-state wsh/lookup-selected first) - [[instance3 shape3 shape4] - [c-instance3 c-shape3 c-shape4] + [[instance1 shape1 shape2] + [c-instance1 c-shape1 c-shape2] component] (thl/resolve-instance-and-main new-state @@ -670,19 +686,19 @@ ; TODO: get and check the instance inside component [Rect-2] - (t/is (not= (:id instance1) (:id instance3))) + (t/is (not= (:id main1) (:id instance1))) (t/is (= (:id component) component-id)) - (t/is (= (:name instance3) "Rect 1")) - (t/is (= (:name shape3) "Rect 1")) - (t/is (= (:name shape4) "Rect 1")) - (t/is (= (:name c-instance3) "Rect 1")) - (t/is (= (:name c-shape3) "Rect 1")) - (t/is (= (:name c-shape4) "Rect 1")))))] + (t/is (= (:name instance1) "Rect 1")) + (t/is (= (:name shape1) "Rect 1")) + (t/is (= (:name shape2) "Rect 1")) + (t/is (= (:name c-instance1) "Rect 1")) + (t/is (= (:name c-shape1) "Rect 1")) + (t/is (= (:name c-shape2) "Rect 1")))))] (ptk/emit! store (dwl/instantiate-component (:id file) - (:component-id instance2) + (:component-id main2) (gpt/point 100 100)) :the/end)))) @@ -693,38 +709,36 @@ (thp/sample-page) (thp/sample-shape :shape1 :rect {:name "Rect 1"}) - (thp/make-component :instance1 :component-1 + (thp/make-component :main1 :component1 [(thp/id :shape1)]) (thp/move-to-library :lib1 "Library 1") (thp/sample-page) - (thp/instantiate-component :instance2 - (thp/id :component-1) + (thp/instantiate-component :instance1 + (thp/id :component1) (thp/id :lib1))) - library-id (thp/id :lib1) - component-id (thp/id :component-1) + file (wsh/get-local-file state) + library-id (thp/id :lib1) store (the/prepare-store state done (fn [new-state] ; Expected shape tree: ; - ; [Page] - ; Root Frame - ; Group #--> Group - ; Rect 1 @--> Rect 1 - ; Rect 1 ---> Rect 1 + ; [Page] + ; Root Frame + ; Group + ; Rect 1 #--> Rect 1 + ; Rect 1 ---> Rect 1 + ; + ; [Group] + ; page1 / Group ; - ; [Group] - ; Group - ; Rect 1 @--> Rect 1 - ; Rect 1 ---> Rect 1 - ; - (let [instance2 (thp/get-shape new-state :instance2) + (let [instance1 (thp/get-shape new-state :instance1) - [[group1 shape1 shape2] [c-group1 c-shape1 c-shape2] component] + [[group1 shape1 shape2] [c-group1 c-shape1 c-shape2] _component] (thl/resolve-instance-and-main new-state - (:parent-id instance2))] + (:parent-id instance1))] (t/is (= (:name group1) "Group")) (t/is (= (:name shape1) "Rect 1")) @@ -735,14 +749,13 @@ (t/is (= (:component-file group1) thp/current-file-id)) (t/is (= (:component-file shape1) library-id)) (t/is (= (:component-file shape2) nil)) - (t/is (= (:component-file c-group1) nil)) + (t/is (= (:component-file c-group1) (:id file))) (t/is (= (:component-file c-shape1) library-id)) (t/is (= (:component-file c-shape2) nil)))))] (ptk/emit! store - (dw/select-shape (thp/id :instance2)) + (dw/select-shape (thp/id :instance1)) dwg/group-selected (dwl/add-component) :the/end)))) -