0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-24 23:49:45 -05:00

Add some migrations and fixes to component-v2 migration

This commit is contained in:
Andrés Moya 2023-10-11 14:42:10 +02:00 committed by Andrey Antukh
parent 8f5d315573
commit 2fe820304e
6 changed files with 277 additions and 64 deletions

View file

@ -253,11 +253,18 @@
(into #{} (comp (filter pmap/pointer-map?) (into #{} (comp (filter pmap/pointer-map?)
(map pmap/get-id))))) (map pmap/get-id)))))
(declare get-file-libraries)
;; FIXME: file locking ;; FIXME: file locking
(defn- process-components-v2-feature (defn- process-components-v2-feature
"A special case handling of the components/v2 feature." "A special case handling of the components/v2 feature."
[{:keys [features data] :as file}] [conn {:keys [features data] :as file}]
(let [data (ctf/migrate-to-components-v2 data) (let [libraries (-> (->> (get-file-libraries conn (:id file)) ; This may be slow, but it's executed only once,
(map #(db/get conn :file {:id (:id %)})) ; in the migration to components-v2
(map #(update % :data blob/decode))
(d/index-by :id))
(assoc (:id file) file))
data (ctf/migrate-to-components-v2 data libraries)
features (conj features "components/v2")] features (conj features "components/v2")]
(-> file (-> file
(assoc ::pmg/migrated true) (assoc ::pmg/migrated true)
@ -265,7 +272,7 @@
(assoc :data data)))) (assoc :data data))))
(defn handle-file-features! (defn handle-file-features!
[{:keys [features] :as file} client-features] [conn {:keys [features] :as file} client-features]
;; Check features compatibility between the currently supported features on ;; Check features compatibility between the currently supported features on
;; the current backend instance and the file retrieved from the database ;; the current backend instance and the file retrieved from the database
@ -287,7 +294,7 @@
;; components and breaking the whole file." ;; components and breaking the whole file."
(and (contains? client-features "components/v2") (and (contains? client-features "components/v2")
(not (contains? features "components/v2"))) (not (contains? features "components/v2")))
(as-> file (process-components-v2-feature file)) (as-> file (process-components-v2-feature conn file))
;; This operation is needed for backward comapatibility with frontends that ;; This operation is needed for backward comapatibility with frontends that
;; does not support pointer-map resolution mechanism; this just resolves the ;; does not support pointer-map resolution mechanism; this just resolves the
@ -355,7 +362,7 @@
(decode-row) (decode-row)
(pmg/migrate-file)) (pmg/migrate-file))
file (handle-file-features! file client-features)] file (handle-file-features! conn file client-features)]
;; NOTE: when file is migrated, we break the rule of no perform ;; NOTE: when file is migrated, we break the rule of no perform
;; mutations on get operations and update the file with all ;; mutations on get operations and update the file with all

View file

@ -230,7 +230,7 @@
;; to be executed on a separated executor for avoid to do the ;; to be executed on a separated executor for avoid to do the
;; CPU intensive operation on vthread. ;; CPU intensive operation on vthread.
file (-> (climit/configure cfg :update-file) file (-> (climit/configure cfg :update-file)
(climit/submit! (partial update-file-data file libraries changes skip-validate)))] (climit/submit! (partial update-file-data conn file libraries changes skip-validate)))]
(db/insert! conn :file-change (db/insert! conn :file-change
{:id (uuid/next) {:id (uuid/next)
@ -264,11 +264,22 @@
(get-lagged-changes conn params)))) (get-lagged-changes conn params))))
(defn- update-file-data (defn- update-file-data
[file libraries changes skip-validate] [conn file libraries changes skip-validate]
(let [validate (fn [file] (let [validate (fn [file]
(when (and (cf/flags :file-validation) (when (and (cf/flags :file-validation)
(not skip-validate)) (not skip-validate))
(val/validate-file file libraries :throw? true)))] (val/validate-file file libraries :throw? true)))
do-migrate-v2 (fn [file]
;; When migrating to components-v2 we need the libraries even
;; if the validations are disabled.
(let [libraries (or (seq libraries)
(-> (->> (files/get-file-libraries conn (:id file))
(map #(get-file conn (:id %)))
(map #(update % :data blob/decode))
(d/index-by :id))
(assoc (:id file) file)))]
(ctf/migrate-to-components-v2 file libraries)))]
(-> file (-> file
(update :revn inc) (update :revn inc)
(update :data (fn [data] (update :data (fn [data]
@ -280,7 +291,7 @@
(and (contains? ffeat/*current* "components/v2") (and (contains? ffeat/*current* "components/v2")
(not (contains? ffeat/*previous* "components/v2"))) (not (contains? ffeat/*previous* "components/v2")))
(ctf/migrate-to-components-v2) (do-migrate-v2)
:always :always
(cp/process-changes changes)))) (cp/process-changes changes))))

View file

@ -91,7 +91,7 @@
(defn validate-geometry (defn validate-geometry
"Validate that the shape has valid coordinates, selrect and points." "Validate that the shape has valid coordinates, selrect and points."
[shape file page] [shape file page]
(when (and (not= (:type shape) :path) (when (and (not (#{:path :bool} (:type shape)))
(or (nil? (:x shape)) ; This may occur in root shape (uuid/zero) in old files (or (nil? (:x shape)) ; This may occur in root shape (uuid/zero) in old files
(nil? (:y shape)) (nil? (:y shape))
(nil? (:width shape)) (nil? (:width shape))

View file

@ -94,6 +94,18 @@
[container shape] [container shape]
(map #(get-shape container %) (:shapes shape))) (map #(get-shape container %) (:shapes shape)))
(defn get-children-in-instance
"Get the shape and their children recursively, but stopping when
a component nested instance is found."
[objects id]
(letfn [(get-children-rec [children id]
(let [shape (get objects id)]
(if (and (ctk/instance-head? shape) (seq children))
children
(into (conj children shape)
(mapcat #(get-children-rec children %) (:shapes shape))))))]
(get-children-rec [] id)))
(defn get-component-shape (defn get-component-shape
"Get the parent top shape linked to a component for this shape, if any" "Get the parent top shape linked to a component for this shape, if any"
([objects shape] (get-component-shape objects shape nil)) ([objects shape] (get-component-shape objects shape nil))
@ -163,6 +175,8 @@
(cond (cond
(or (nil? shape) (cph/root? shape)) (or (nil? shape) (cph/root? shape))
false false
(nil? (:parent-id shape)) ; This occurs in the root of components v1
true
(ctk/main-instance? shape) (ctk/main-instance? shape)
true true
(ctk/instance-head? shape) (ctk/instance-head? shape)
@ -254,7 +268,6 @@
(assoc :frame-id uuid/zero)) (assoc :frame-id uuid/zero))
(get-shape component (:id component))) (get-shape component (:id component)))
orig-pos (gpt/point (:x component-shape) (:y component-shape)) orig-pos (gpt/point (:x component-shape) (:y component-shape))
delta (gpt/subtract position orig-pos) delta (gpt/subtract position orig-pos)

View file

@ -56,8 +56,8 @@
[:typographies {:optional true} [:typographies {:optional true}
[:map-of {:gen/max 2} ::sm/uuid ::cty/typography]] [:map-of {:gen/max 2} ::sm/uuid ::cty/typography]]
[:media {:optional true} [:media {:optional true}
[:map-of {:gen/max 5} ::sm/uuid ::media-object]] [:map-of {:gen/max 5} ::sm/uuid ::media-object]]])
])
(def file-data? (def file-data?
(sm/pred-fn ::data)) (sm/pred-fn ::data))
@ -368,11 +368,13 @@
(let [library-page (ctp/make-empty-page (uuid/next) "Library backup")] (let [library-page (ctp/make-empty-page (uuid/next) "Library backup")]
[(ctpl/add-page file-data library-page) (:id library-page) (gpt/point 0 0)])))) [(ctpl/add-page file-data library-page) (:id library-page) (gpt/point 0 0)]))))
(declare preprocess-file)
(defn migrate-to-components-v2 (defn migrate-to-components-v2
"If there is any component in the file library, add a new 'Library backup', generate "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. main instances for all components there and remove shapes from library components.
Mark the file with the :components-v2 option." Mark the file with the :components-v2 option."
[file-data] [file-data libraries]
(let [migrated? (dm/get-in file-data [:options :components-v2])] (let [migrated? (dm/get-in file-data [:options :components-v2])]
(if migrated? (if migrated?
file-data file-data
@ -384,46 +386,75 @@
[file-data page-id start-pos] [file-data page-id start-pos]
(get-or-add-library-page file-data grid-gap) (get-or-add-library-page file-data grid-gap)
migrate-component-shape
(fn [shape delta component-file component-id]
(cond-> shape
(nil? (:parent-id shape))
(assoc :parent-id uuid/zero
:main-instance true
:component-root true
:component-file component-file
:component-id component-id
:type :frame ; Old groups must be converted
:fills [] ; to frames and conform to spec
:hide-in-viewer true
:rx 0
:ry 0)
(nil? (:frame-id shape))
(assoc :frame-id uuid/zero)
:always
(gsh/move delta)))
add-main-instance add-main-instance
(fn [file-data component position] (fn [file-data component position]
(let [page (ctpl/get-page file-data page-id) (let [shapes (cph/get-children-with-self (:objects component)
(:id component))
[new-shape new-shapes] root-shape (first shapes)
(ctn/make-component-instance page orig-pos (gpt/point (:x root-shape) (:y root-shape))
component delta (gpt/subtract position orig-pos)
file-data
position
false
{:main-instance? true
:force-frame-id uuid/zero
:keep-ids? true})
add-shapes
(fn [page]
(reduce (fn [page shape]
(ctst/add-shape (:id shape)
shape
page
(:frame-id shape)
(:parent-id shape)
nil ; <- As shapes are ordered, we can safely add each
true)) ; one at the end of the parent's children list.
page
new-shapes))
update-component xf-shape (map #(migrate-component-shape %
(fn [component] delta
(-> component (:id file-data)
(assoc :main-instance-id (:id new-shape) (:id component)))
:main-instance-page page-id) new-shapes
(dissoc :objects)))] (into [] xf-shape shapes)
add-shapes
(fn [page]
(reduce (fn [page shape]
(ctst/add-shape (:id shape)
shape
page
(:frame-id shape)
(:parent-id shape)
nil ; <- As shapes are ordered, we can safely add each
true)) ; one at the end of the parent's children list.
page
new-shapes))
update-component
(fn [component]
(-> component
(assoc :main-instance-id (:id root-shape)
:main-instance-page page-id)
(dissoc :objects)))]
(-> file-data (-> file-data
(ctpl/update-page page-id add-shapes) (ctpl/update-page page-id add-shapes)
(ctkl/update-component (:id component) update-component)))) (ctkl/update-component (:id component) update-component))))
add-instance-grid add-instance-grid
(fn [file-data components] (fn [file-data]
(let [position-seq (ctst/generate-shape-grid (let [components (->> file-data
(ctkl/components-seq)
(sort-by :name)
(reverse))
position-seq (ctst/generate-shape-grid
(map (partial get-component-root file-data) components) (map (partial get-component-root file-data) components)
start-pos start-pos
grid-gap)] grid-gap)]
@ -436,28 +467,174 @@
file-data file-data
(recur (add-main-instance file-data component position) (recur (add-main-instance file-data component position)
(rest components-seq) (rest components-seq)
(rest position-seq))))))) (rest position-seq)))))))]
root-to-board
(fn [shape]
(cond-> shape
(and (ctk/instance-head? shape)
(not (cph/frame-shape? shape)))
(assoc :type :frame
:fills []
:hide-in-viewer true
:rx 0
:ry 0)))
roots-to-board
(fn [page]
(update page :objects update-vals root-to-board))]
(-> file-data (-> file-data
(add-instance-grid (reverse (sort-by :name components))) (preprocess-file libraries)
(update :pages-index update-vals roots-to-board) (add-instance-grid)
(assoc-in [:options :components-v2] true)))))))) (assoc-in [:options :components-v2] true))))))))
(defn- preprocess-file
"Apply some specific migrations or fixes to things that are allowed in v1 but not in v2,
or that are the result of old bugs."
[file-data libraries]
(let [detached-ids (volatile! #{})
detach-shape
(fn [container shape]
; Detach a shape. If it's inside a component, add it to detached-ids, for further use.
(let [is-component? (let [root-shape (ctst/get-shape container (:id container))]
(and (some? root-shape) (nil? (:parent-id root-shape))))]
(when is-component?
(vswap! detached-ids conj (:id shape)))
(ctk/detach-shape shape)))
fix-orphan-shapes
(fn [file-data]
; Find shapes that are not listed in their parent's children list.
; Remove them, and also their children
(letfn [(fix-container [container]
(reduce fix-shape container (ctn/shapes-seq container)))
(fix-shape
[container shape]
(if-not (or (= (:id shape) uuid/zero)
(nil? (:parent-id shape)))
(let [parent (ctst/get-shape container (:parent-id shape))
exists? (d/index-of (:shapes parent) (:id shape))]
(if (nil? exists?)
(let [ids (cph/get-children-ids-with-self (:objects container) (:id shape))]
(update container :objects #(reduce dissoc % ids)))
container))
container))]
(-> file-data
(update :pages-index update-vals fix-container)
(update :components update-vals fix-container))))
remove-nested-roots
(fn [file-data]
; Remove :component-root in head shapes that are nested.
(letfn [(fix-container [container]
(update container :objects update-vals (partial fix-shape container)))
(fix-shape [container shape]
(let [parent (ctst/get-shape container (:parent-id shape))]
(if (and (ctk/instance-root? shape)
(ctn/in-any-component? (:objects container) parent))
(dissoc shape :component-root)
shape)))]
(-> file-data
(update :pages-index update-vals fix-container)
(update :components update-vals fix-container))))
add-not-nested-roots
(fn [file-data]
; Add :component-root in head shapes that are not nested.
(letfn [(fix-container [container]
(update container :objects update-vals (partial fix-shape container)))
(fix-shape [container shape]
(let [parent (ctst/get-shape container (:parent-id shape))]
(if (and (ctk/subinstance-head? shape)
(not (ctn/in-any-component? (:objects container) parent)))
(assoc shape :component-root true)
shape)))]
(-> file-data
(update :pages-index update-vals fix-container)
(update :components update-vals fix-container))))
fix-orphan-copies
(fn [file-data]
; Detach shapes that were inside a copy (have :shape-ref) but now they aren't.
(letfn [(fix-container [container]
(update container :objects update-vals (partial fix-shape container)))
(fix-shape [container shape]
(let [parent (ctst/get-shape container (:parent-id shape))]
(if (and (ctk/in-component-copy? shape)
(not (ctk/instance-head? shape))
(not (ctk/in-component-copy? parent)))
(detach-shape container shape)
shape)))]
(-> file-data
(update :pages-index update-vals fix-container)
(update :components update-vals fix-container))))
remap-refs
(fn [file-data]
; Remap shape-refs so that they point to the near main.
; At the same time, if there are any dangling ref, detach the shape and its children.
(letfn [(fix-container [container]
(reduce fix-shape container (ctn/shapes-seq container)))
(fix-shape
[container shape]
(if (ctk/in-component-copy? shape)
; First look for the direct shape.
(let [root (ctn/get-component-shape (:objects container) shape)
libraries (assoc-in libraries [(:id file-data) :data] file-data)
library (get libraries (:component-file root))
component (ctkl/get-component (:data library) (:component-id root) true)
direct-shape (ctst/get-shape component (:shape-ref shape))]
(if (some? direct-shape)
; If it exists, there is nothing else to do.
container
; If not found, find the near shape.
(let [near-shape (d/seek #(= (:shape-ref %) (:shape-ref shape))
(ctn/shapes-seq component))]
(if (some? near-shape)
; If found, update the ref to point to the near shape.
(ctn/update-shape container (:id shape) #(assoc % :shape-ref (:id near-shape)))
; If not found, it may be a fostered component. Try to locate a direct shape
; in the head component.
(let [head (ctn/get-head-shape (:objects container) shape)
library-2 (get libraries (:component-file head))
component-2 (ctkl/get-component (:data library-2) (:component-id head) true)
direct-shape-2 (ctst/get-shape component-2 (:shape-ref shape))]
(if (some? direct-shape-2)
; If it exists, there is nothing else to do.
container
; If not found, detach shape and all children (stopping if a nested instance is reached)
(let [children (ctn/get-children-in-instance (:objects container) (:id shape))]
(reduce #(ctn/update-shape %1 (:id %2) (partial detach-shape %1))
container
children))))))))
container))]
(-> file-data
(update :pages-index update-vals fix-container)
(update :components update-vals fix-container))))
fix-copies-of-detached
(fn [file-data]
; Find any copy that is referencing a detached shape inside a component, and
; undo the nested copy, converting it into a direct copy.
(letfn [(fix-container [container]
(update container :objects update-vals fix-shape))
(fix-shape [shape]
(cond-> shape
(@detached-ids (:shape-ref shape))
(dissoc shape
:component-id
:component-file
:component-root)))]
(-> file-data
(update :pages-index update-vals fix-container)
(update :components update-vals fix-container))))]
(-> file-data
(fix-orphan-shapes)
(remove-nested-roots)
(add-not-nested-roots)
(fix-orphan-copies)
(remap-refs)
(fix-copies-of-detached))))
(defn- absorb-components (defn- absorb-components
[file-data used-components library-data] [file-data used-components library-data]
(let [grid-gap 50 (let [grid-gap 50
@ -831,9 +1008,9 @@
(if (get-in libs-to-show [library-id (:id root)]) (if (get-in libs-to-show [library-id (:id root)])
libs-to-show libs-to-show
(-> libs-to-show (-> libs-to-show
(add-component library-id component-id) (add-component library-id component-id))))))
;; (find-used-components-cumulative page root) ;; (find-used-components-cumulative page root)
)))))
libs-to-show libs-to-show
components)) components))
libs-to-show libs-to-show

View file

@ -23,6 +23,7 @@
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
[app.main.data.workspace.changes :as dwc] [app.main.data.workspace.changes :as dwc]
[app.main.data.workspace.path.shortcuts] [app.main.data.workspace.path.shortcuts]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.shortcuts] [app.main.data.workspace.shortcuts]
[app.main.features :as features] [app.main.features :as features]
[app.main.repo :as rp] [app.main.repo :as rp]
@ -241,6 +242,10 @@
(prn (str (:name frame) " - " (:id frame)))) (prn (str (:name frame) " - " (:id frame))))
nil)) nil))
(defn ^:export select-by-id
[shape-id]
(st/emit! (dws/select-shape (uuid/uuid shape-id))))
(defn dump-tree' (defn dump-tree'
([state] (dump-tree' state false false false)) ([state] (dump-tree' state false false false))
([state show-ids] (dump-tree' state show-ids false false)) ([state show-ids] (dump-tree' state show-ids false false))