From 7592530fb46c5cf07c8d9d49a93944f7efec92dd Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Wed, 22 Jan 2025 12:05:50 +0100 Subject: [PATCH] :bug: Fix shape-ref cycles --- common/src/app/common/files/repair.cljc | 29 ++++++++++++++++ common/src/app/common/files/validate.cljc | 18 ++++++++-- common/src/app/common/types/file.cljc | 40 +++++++++++++---------- 3 files changed, 67 insertions(+), 20 deletions(-) diff --git a/common/src/app/common/files/repair.cljc b/common/src/app/common/files/repair.cljc index 67f90dafe..5d80c844f 100644 --- a/common/src/app/common/files/repair.cljc +++ b/common/src/app/common/files/repair.cljc @@ -320,6 +320,35 @@ (pcb/with-file-data file-data) (pcb/update-shapes shape-ids detach-shape)))))) + +(defmethod repair-error :shape-ref-cycle + [_ {:keys [shape args] :as error} file-data _] + (let [repair-component + (fn [component] + (let [objects (:objects component) ;; we only have encounter this on deleted components, + ;; so the relevant objects are inside the component + to-detach (->> (:cycles-ids args) + (map #(get objects %)) + (map #(ctn/get-head-shape objects %)) + (map :id) + distinct + (mapcat #(ctn/get-children-in-instance objects %)) + (map :id) + set)] + + (update component :objects + (fn [objects] + (reduce-kv (fn [acc k v] + (if (contains? to-detach k) + (assoc acc k (ctk/detach-shape v)) + (assoc acc k v))) + {} + objects)))))] + (log/dbg :hint "repairing component :shape-ref-cycle" :id (:id shape) :name (:name shape)) + (-> (pcb/empty-changes nil nil) + (pcb/with-library-data file-data) + (pcb/update-component (:id shape) repair-component)))) + (defmethod repair-error :shape-ref-in-main [_ {:keys [shape page-id] :as error} file-data _] (let [repair-shape diff --git a/common/src/app/common/files/validate.cljc b/common/src/app/common/files/validate.cljc index 79e6cf301..f1f2bdda9 100644 --- a/common/src/app/common/files/validate.cljc +++ b/common/src/app/common/files/validate.cljc @@ -55,7 +55,8 @@ :component-nil-objects-not-allowed :instance-head-not-frame :misplaced-slot - :missing-slot}) + :missing-slot + :shape-ref-cycle}) (def ^:private schema:error [:map {:title "ValidationError"} @@ -482,6 +483,18 @@ "This deleted component has children with the same swap slot" component file nil)))) +(defn check-ref-cycles + [component file] + (let [cycles-ids (->> component + :objects + vals + (filter #(= (:id %) (:shape-ref %))) + (map :id))] + + (when (seq cycles-ids) + (report-error :shape-ref-cycle + "This deleted component has shapes with shape-ref pointing to self" + component file nil :cycles-ids cycles-ids)))) (defn- check-component "Validate semantic coherence of a component. Report all errors found." @@ -491,7 +504,8 @@ "Objects list cannot be nil" component file nil)) (when (:deleted component) - (check-component-duplicate-swap-slot component file))) + (check-component-duplicate-swap-slot component file) + (check-ref-cycles component file))) (defn- get-orphan-shapes [{:keys [objects] :as page}] diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index 8236631ef..9b575179a 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -335,24 +335,28 @@ (true? (= (:id component) (:id ref-component))))) (defn find-swap-slot - [shape container file libraries] - (if-let [swap-slot (ctk/get-swap-slot shape)] - swap-slot - (let [ref-shape (find-ref-shape file - container - libraries - shape - :include-deleted? true - :with-context? true) - shape-meta (meta ref-shape) - ref-file (:file shape-meta) - ref-container (:container shape-meta)] - (when ref-shape - (if-let [swap-slot (ctk/get-swap-slot ref-shape)] - swap-slot - (if (ctk/main-instance? ref-shape) - (:id shape) - (find-swap-slot ref-shape ref-container ref-file libraries))))))) + ([shape container file libraries] + (find-swap-slot shape container file libraries #{})) + ([shape container file libraries viewed-ids] + (if (contains? viewed-ids (:id shape)) ;; prevent cycles + nil + (if-let [swap-slot (ctk/get-swap-slot shape)] + swap-slot + (let [ref-shape (find-ref-shape file + container + libraries + shape + :include-deleted? true + :with-context? true) + shape-meta (meta ref-shape) + ref-file (:file shape-meta) + ref-container (:container shape-meta)] + (when ref-shape + (if-let [swap-slot (ctk/get-swap-slot ref-shape)] + swap-slot + (if (ctk/main-instance? ref-shape) + (:id shape) + (find-swap-slot ref-shape ref-container ref-file libraries (conj viewed-ids (:id shape))))))))))) (defn match-swap-slot? [shape-main shape-inst container-inst container-main file libraries]