diff --git a/common/src/app/common/files/comp_processors.cljc b/common/src/app/common/files/comp_processors.cljc index 2fc5ec81d1..7d9402a280 100644 --- a/common/src/app/common/files/comp_processors.cljc +++ b/common/src/app/common/files/comp_processors.cljc @@ -6,8 +6,26 @@ (ns app.common.files.comp-processors (:require + [app.common.types.component :as ctk] [app.common.types.file :as ctf])) - (defn fix-missing-swap-slots - [file] - file) \ No newline at end of file +(defn fix-missing-swap-slots + [file libraries] + (ctf/update-all-shapes + file + (fn [shape] + (if (and (ctk/instance-head? shape) (ctk/in-component-copy? shape)) + (let [{:keys [container]} + (meta shape) + + ref-shape + (ctf/find-ref-shape file container libraries shape :include-deleted? true :with-context? true)] + (println "comparing" (:name shape) "with ref" (some-> ref-shape :name)) + (if ref-shape + (if (and (not= (:shape-ref shape) (:id ref-shape)) + (nil? (ctk/get-swap-slot shape))) + (let [updated-shape (ctk/set-swap-slot shape (:id ref-shape))] + {:result :update :updated-shape updated-shape}) + {:result :keep}) + {:result :keep})) + {:result :keep})))) diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index 324528854b..2fbac96c57 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -16,6 +16,7 @@ [app.common.types.components-list :as ctkl] [app.common.types.pages-list :as ctpl] [app.common.types.plugins :refer [schema:plugin-data]] + [app.common.types.shape :as cts] [app.common.types.shape-tree :as ctst] [app.common.types.shape.layout :as ctl] [app.common.types.text :as cttx] @@ -55,6 +56,10 @@ [page-or-component type] (assoc page-or-component :type type)) +(defn unmake-container + [container] + (dissoc container :type)) + (defn page? [container] (= (:type container) :page)) diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index 3733359a6c..00bd787bd1 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -225,6 +225,86 @@ (ctpl/update-page file-data (:id container) f) (ctkl/update-component file-data (:id container) f))) +(defn update-pages + "Update all pages inside the file" + [file-data f] + (update file-data :pages-index d/update-vals + (fn [page] + (-> page + (ctn/make-container :page) + (f) + (ctn/unmake-container))))) + +(defn update-components + "Update all components inside the file" + [file-data f] + (d/update-when file-data :components d/update-vals + (fn [component] + (-> component + (ctn/make-container :component) + (f) + (ctn/unmake-container))))) + +(defn update-containers + "Update all pages and components inside the file" + [file-data f] + (-> file-data + (update-pages f) + (update-components f))) + +(defn update-objects-tree + "Do a depth-first traversal of the shapes in a container, doing different kinds of updates. + The function f receives a shape with a context metadata with the container. + It must return a map with the following keys: + - :result -> :keep, :update or :remove + - :updated-shape -> the updated shape if result is :update" + [container f] + (letfn [(update-shape-recursive + [container shape-id] + (let [shape (ctst/get-shape container shape-id)] + (when (not shape) + (throw (ex-info "Shape not found" {:shape-id shape-id}))) + (let [shape (with-meta shape {:container container}) + + {:keys [result updated-shape]} (f shape) + + container' + (case result + :keep + container + + :update + (ctst/set-shape container updated-shape) + + :remove + (ctst/delete-shape container shape-id true) + + :else + (throw (ex-info "Invalid result from update function" {:result result})))] + + (reduce update-shape-recursive + container' + (:shapes shape)))))] + + (let [root-id (if (ctn/page? container) + uuid/zero + (:main-instance-id container))] + + (if (:objects container) + (update-shape-recursive container root-id) + container)))) + +(defn update-all-shapes + "Update all shapes in the file, using the update-objects-tree function for each container" + [file f] + (update-file-data + file + (fn [file-data] + (update-containers + file-data + (fn [container] + (update-objects-tree container f)))))) + ;; Asset helpers (defn find-component-file [file libraries component-file] diff --git a/common/test/common_tests/files/comp_processors_test.cljc b/common/test/common_tests/files/comp_processors_test.cljc index a96a7e72eb..e5dbd7abb7 100644 --- a/common/test/common_tests/files/comp_processors_test.cljc +++ b/common/test/common_tests/files/comp_processors_test.cljc @@ -6,47 +6,118 @@ (ns common-tests.files.comp-processors-test (:require - [app.common.files.comp-processors :as cfcp] -;; [app.common.test-helpers.components :as thc] + [app.common.data :as d] + [app.common.files.comp-processors :as cfcp] + [app.common.test-helpers.components :as thc] [app.common.test-helpers.compositions :as tho] [app.common.test-helpers.files :as thf] -;; [app.common.test-helpers.ids-map :as thi] -;; [app.common.test-helpers.shapes :as ths] + [app.common.test-helpers.ids-map :as thi] + [app.common.test-helpers.shapes :as ths] + [app.common.types.component :as ctk] [clojure.test :as t])) (t/deftest test-fix-missing-swap-slots (t/testing "empty file should not need any action" - (let [file (thf/sample-file :file1)] - (t/is (= file (cfcp/fix-missing-swap-slots file))))) + (let [file (thf/sample-file :file1) + file' (cfcp/fix-missing-swap-slots file {})] + (t/is (empty? (d/map-diff file file'))))) (t/testing "file without components should not need any action" (let [file ;; :frame1 [:name Frame1] ;; :child1 [:name Rect1] (-> (thf/sample-file :file1) - (tho/add-frame-with-child :frame1 :shape1))] + (tho/add-frame-with-child :frame1 :shape1)) - (t/is (= file (cfcp/fix-missing-swap-slots file))))) - - (t/testing "file with not swapped components should not need any action" + file' (cfcp/fix-missing-swap-slots file {})] + + (t/is (empty? (d/map-diff file file'))))) + + (t/testing "file with nested not swapped components should not need any action" (let [file - ;; {:main1-root} [:name Frame1] # [Component :component1] - ;; :main1-child [:name Rect1] - ;; - ;; {:main2-root} [:name Frame2] # [Component :component2] - ;; :nested-head [:name Frame1] @--> [Component :component1] :main1-root - ;; [:name Rect1] ---> :main1-child - ;; - ;; :copy2 [:name Frame2] #--> [Component :component2] :main2-root - ;; [:name Frame1] @--> [Component :component1] :nested-head - ;; [:name Rect1] ---> + ;; {:main1-root} [:name Frame1] # [Component :component1] + ;; :main1-child [:name Rect1] + ;; + ;; {:main2-root} [:name Frame2] # [Component :component2] + ;; :nested-head [:name Frame1] @--> [Component :component1] :main1-root + ;; [:name Rect1] ---> :main1-child + ;; + ;; :copy2 [:name Frame2] #--> [Component :component2] :main2-root + ;; [:name Frame1] @--> [Component :component1] :nested-head + ;; [:name Rect1] ---> (-> (thf/sample-file :file1) (tho/add-nested-component-with-copy :component1 :main1-root :main1-child :component2 :main2-root :nested-head - :copy2 :copy2-root))] + :copy2 :copy2-root)) - (t/is (= file (cfcp/fix-missing-swap-slots file))))) - - - ) + file' (cfcp/fix-missing-swap-slots file {})] + + (t/is (empty? (d/map-diff file file'))))) + + (t/testing "file with a normally swapped copy should not need any action" + (let [file + ;; {:main1-root} [:name Frame1] # [Component :component1] + ;; :main1-child [:name Rect1] + ;; + ;; {:main2-root} [:name Frame2] # [Component :component2] + ;; :nested-head [:name Frame1] @--> [Component :component1] :main1-root + ;; [:name Rect1] ---> :main1-child + ;; + ;; {:main3-root} [:name Frame3] # [Component :component3] + ;; :main3-child [:name Rect3] + ;; + ;; :copy2 [:name Frame2] #--> [Component :component2] :main2-root + ;; :copy2-nested-head [:name Frame3] @--> [Component :component3] :main3-root + ;; {swap-slot :nested-head} + ;; [:name Rect3] ---> :main3-child + (-> (thf/sample-file :file1) + (tho/add-nested-component :component1 :main1-root :main1-child + :component2 :main2-root :nested-head) + (thc/instantiate-component :component2 :copy2 :children-labels [:copy2-nested-head]) + (tho/add-simple-component :component3 :main3-root :main3-child + :root-params {:name "Frame3"} + :child-params {:name "Rect3"}) + (tho/swap-component-in-first-child :copy2 :component3)) + + file' (cfcp/fix-missing-swap-slots file {})] + + (t/is (empty? (d/map-diff file file'))))) + + (t/testing "file with a swapped copy with broken slot should have it repaired" + (println "==start test==================================================") + (let [file + ;; {:main1-root} [:name Frame1] # [Component :component1] + ;; :main1-child [:name Rect1] + ;; + ;; {:main2-root} [:name Frame2] # [Component :component2] + ;; :nested-head [:name Frame1] @--> [Component :component1] :main1-root + ;; [:name Rect1] ---> :main1-child + ;; + ;; {:main3-root} [:name Frame3] # [Component :component3] + ;; :main3-child [:name Rect3] + ;; + ;; :copy2 [:name Frame2] #--> [Component :component2] :main2-root + ;; :copy2-nested-head [:name Frame3] @--> [Component :component3] :main3-root + ;; NO SWAP SLOT + ;; [:name Rect3] ---> :main3-child + (-> (thf/sample-file :file1) + (tho/add-nested-component :component1 :main1-root :main1-child + :component2 :main2-root :nested-head) + (thc/instantiate-component :component2 :copy2 :children-labels [:copy2-nested-head]) + (tho/add-simple-component :component3 :main3-root :main3-child + :root-params {:name "Frame3"} + :child-params {:name "Rect3"}) + (tho/swap-component-in-first-child :copy2 :component3) + (ths/update-shape :copy2-nested-head :touched nil)) + + file' (cfcp/fix-missing-swap-slots file {}) + + diff (d/map-diff file file') + + copy2-nested-head' (ths/get-shape file' :copy2-nested-head)] + + (thf/dump-file file :keys [:name :swap-slot-label] :show-refs? true) + (println "====================================================") + (prn "diff" diff) + (t/is (= (ctk/get-swap-slot copy2-nested-head') (thi/id :nested-head))))))