0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-09 22:31:50 -05:00

Merge pull request #5641 from penpot/palba-fix-shape-ref-cycle

🐛 Fix shape-ref cycles
This commit is contained in:
Pablo Alba 2025-01-22 15:48:01 +01:00 committed by GitHub
commit 85d3a052c9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 67 additions and 20 deletions

View file

@ -320,6 +320,35 @@
(pcb/with-file-data file-data) (pcb/with-file-data file-data)
(pcb/update-shapes shape-ids detach-shape)))))) (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 (defmethod repair-error :shape-ref-in-main
[_ {:keys [shape page-id] :as error} file-data _] [_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape (let [repair-shape

View file

@ -55,7 +55,8 @@
:component-nil-objects-not-allowed :component-nil-objects-not-allowed
:instance-head-not-frame :instance-head-not-frame
:misplaced-slot :misplaced-slot
:missing-slot}) :missing-slot
:shape-ref-cycle})
(def ^:private schema:error (def ^:private schema:error
[:map {:title "ValidationError"} [:map {:title "ValidationError"}
@ -482,6 +483,18 @@
"This deleted component has children with the same swap slot" "This deleted component has children with the same swap slot"
component file nil)))) 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 (defn- check-component
"Validate semantic coherence of a component. Report all errors found." "Validate semantic coherence of a component. Report all errors found."
@ -491,7 +504,8 @@
"Objects list cannot be nil" "Objects list cannot be nil"
component file nil)) component file nil))
(when (:deleted component) (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 (defn- get-orphan-shapes
[{:keys [objects] :as page}] [{:keys [objects] :as page}]

View file

@ -348,24 +348,28 @@
(true? (= (:id component) (:id ref-component))))) (true? (= (:id component) (:id ref-component)))))
(defn find-swap-slot (defn find-swap-slot
[shape container file libraries] ([shape container file libraries]
(if-let [swap-slot (ctk/get-swap-slot shape)] (find-swap-slot shape container file libraries #{}))
swap-slot ([shape container file libraries viewed-ids]
(let [ref-shape (find-ref-shape file (if (contains? viewed-ids (:id shape)) ;; prevent cycles
container nil
libraries (if-let [swap-slot (ctk/get-swap-slot shape)]
shape swap-slot
:include-deleted? true (let [ref-shape (find-ref-shape file
:with-context? true) container
shape-meta (meta ref-shape) libraries
ref-file (:file shape-meta) shape
ref-container (:container shape-meta)] :include-deleted? true
(when ref-shape :with-context? true)
(if-let [swap-slot (ctk/get-swap-slot ref-shape)] shape-meta (meta ref-shape)
swap-slot ref-file (:file shape-meta)
(if (ctk/main-instance? ref-shape) ref-container (:container shape-meta)]
(:id shape) (when ref-shape
(find-swap-slot ref-shape ref-container ref-file libraries))))))) (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? (defn match-swap-slot?
[shape-main shape-inst container-inst container-main file libraries] [shape-main shape-inst container-inst container-main file libraries]