mirror of
https://github.com/penpot/penpot.git
synced 2026-08-05 20:38:38 +00:00
🐛 Keep svg-raw children as uuids on binfile import (#10837)
Importing a .penpot file left every svg-raw subtree broken: the parent's :shapes vector came back holding plain strings instead of uuids, so the child ids no longer resolved against the page objects map. The next persisted change touching that page then failed referential integrity validation with :child-not-found, surfaced to the client as an HTTP 400 :referential-integrity error, which in practice bricks the file. An svg-raw shape can be a container: importing an SVG builds a tree of svg-raw shapes, and cfh/group-like-shape? explicitly treats an svg-raw with children as group-like. But schema:svg-raw-attrs was an empty map. Frame, group and bool all declare :shapes as a vector of uuid; svg-raw did not, so the JSON decoder used by binfile had no type information for those ids and left them as strings. Declare :shapes on schema:svg-raw-attrs, optional because a leaf svg-raw shape has no children, so the child ids decode back to uuids. Closes #10496. Signed-off-by: Filip Sajdak <filip.sajdak@siili.com> Co-authored-by: Andrey Antukh <niwi@niwi.nz>
This commit is contained in:
parent
7ae57a035f
commit
648c8e2152
@ -10,6 +10,7 @@
|
||||
[app.binfile.common :as bfc]
|
||||
[app.binfile.v3 :as v3]
|
||||
[app.common.features :as cfeat]
|
||||
[app.common.files.validate :as cfv]
|
||||
[app.common.pprint :as pp]
|
||||
[app.common.thumbnails :as thc]
|
||||
[app.common.types.shape :as cts]
|
||||
@ -86,6 +87,102 @@
|
||||
|
||||
(dissoc file :data)))
|
||||
|
||||
(def ^:private svg-raw-page-id (uuid/custom 1 1))
|
||||
(def ^:private svg-raw-root-id (uuid/custom 3 1))
|
||||
(def ^:private svg-raw-child-id (uuid/custom 3 2))
|
||||
|
||||
(defn- prepare-svg-raw-file
|
||||
"A file containing an svg-raw subtree (an svg-raw parent with an
|
||||
svg-raw child), which is what importing an SVG produces."
|
||||
[profile]
|
||||
(let [page-id svg-raw-page-id
|
||||
root-id svg-raw-root-id
|
||||
child-id svg-raw-child-id
|
||||
|
||||
file (th/create-file* 1 {:profile-id (:id profile)
|
||||
:project-id (:default-project-id profile)
|
||||
:is-shared false})]
|
||||
(update-file!
|
||||
:file-id (:id file)
|
||||
:profile-id (:id profile)
|
||||
:revn 0
|
||||
:vern 0
|
||||
:changes
|
||||
[{:type :add-page
|
||||
:name "page 1"
|
||||
:id page-id}])
|
||||
|
||||
(update-file!
|
||||
:file-id (:id file)
|
||||
:profile-id (:id profile)
|
||||
:revn 0
|
||||
:vern 0
|
||||
:changes
|
||||
[{:type :add-obj
|
||||
:page-id page-id
|
||||
:id root-id
|
||||
:parent-id uuid/zero
|
||||
:frame-id uuid/zero
|
||||
:components-v2 true
|
||||
:obj (cts/setup-shape
|
||||
{:id root-id
|
||||
:name "svg-root"
|
||||
:frame-id uuid/zero
|
||||
:parent-id uuid/zero
|
||||
:type :svg-raw
|
||||
:content {:tag :svg :attrs {} :content []}})}
|
||||
{:type :add-obj
|
||||
:page-id page-id
|
||||
:id child-id
|
||||
:parent-id root-id
|
||||
:frame-id uuid/zero
|
||||
:components-v2 true
|
||||
:obj (cts/setup-shape
|
||||
{:id child-id
|
||||
:name "svg-text"
|
||||
:frame-id uuid/zero
|
||||
:parent-id root-id
|
||||
:type :svg-raw
|
||||
:content {:tag :text :attrs {} :content []}})}])
|
||||
|
||||
(dissoc file :data)))
|
||||
|
||||
(t/deftest import-binfile-v3-preserves-svg-raw-children
|
||||
(let [profile (th/create-profile* 1)
|
||||
file (prepare-svg-raw-file profile)
|
||||
output (tmp/tempfile :suffix ".zip")]
|
||||
|
||||
(v3/export-files!
|
||||
(-> th/*system*
|
||||
(assoc ::bfc/ids #{(:id file)})
|
||||
(assoc ::bfc/embed-assets false)
|
||||
(assoc ::bfc/include-libraries false))
|
||||
(io/output-stream output))
|
||||
|
||||
(let [result (-> th/*system*
|
||||
(assoc ::bfc/project-id (:default-project-id profile))
|
||||
(assoc ::bfc/profile-id (:id profile))
|
||||
(assoc ::bfc/input output)
|
||||
(v3/import-files!))
|
||||
imported (:result (th/command! {::th/type :get-file
|
||||
::rpc/profile-id (:id profile)
|
||||
:id (first result)
|
||||
:components-v2 true}))
|
||||
root (get-in imported [:data :pages-index svg-raw-page-id
|
||||
:objects svg-raw-root-id])]
|
||||
|
||||
(t/is (= (count result) 1))
|
||||
|
||||
;; The child ids of an svg-raw shape must survive the JSON round
|
||||
;; trip as uuids; when they came back as plain strings they no
|
||||
;; longer resolved against the objects map.
|
||||
(t/is (every? uuid? (:shapes root)))
|
||||
(t/is (= [svg-raw-child-id] (vec (:shapes root))))
|
||||
|
||||
;; ...so the imported file passes referential integrity instead
|
||||
;; of failing with :child-not-found on the next update-file.
|
||||
(t/is (nil? (cfv/validate-file imported []))))))
|
||||
|
||||
(t/deftest export-binfile-v3
|
||||
(let [profile (th/create-profile* 1)
|
||||
file (prepare-simple-file profile)
|
||||
|
||||
@ -259,7 +259,14 @@
|
||||
[:map {:title "CircleAttrs"}])
|
||||
|
||||
(def ^:private schema:svg-raw-attrs
|
||||
[:map {:title "SvgRawAttrs"}])
|
||||
[:map {:title "SvgRawAttrs"}
|
||||
;; An svg-raw shape can be a container: importing an SVG builds a
|
||||
;; tree of svg-raw shapes, and `cfh/group-like-shape?` treats an
|
||||
;; svg-raw with children as group-like. Declaring `:shapes` here
|
||||
;; keeps the child ids typed as uuid, so a JSON round trip (binfile
|
||||
;; export/import) decodes them back to uuids instead of leaving
|
||||
;; strings that no longer resolve against the objects map.
|
||||
[:shapes {:optional true} [:vector {:gen/max 10} ::sm/uuid]]])
|
||||
|
||||
(def schema:image-attrs
|
||||
[:map {:title "ImageAttrs"}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user