mirror of
https://github.com/penpot/penpot.git
synced 2025-08-02 22:28:23 +02:00
🎉 Make components-v2 an optional feature
This commit is contained in:
parent
1ef37281e6
commit
a5bf1c03e7
40 changed files with 495 additions and 296 deletions
|
@ -465,15 +465,20 @@
|
|||
(defmulti components-changed (fn [_ change] (:type change)))
|
||||
|
||||
(defmethod components-changed :mod-obj
|
||||
[file-data {:keys [id page-id component-id operations]}]
|
||||
[file-data {:keys [id page-id _component-id operations]}]
|
||||
(when page-id
|
||||
(let [page (ctpl/get-page file-data page-id)
|
||||
shape-and-parents (map #(ctn/get-shape page %)
|
||||
(into [id] (cph/get-parent-ids (:objects page) id)))
|
||||
any-set? (some #(= (:type %) :set) operations)]
|
||||
(when any-set?
|
||||
need-sync? (fn [operation]
|
||||
; We need to trigger a sync if the shape has changed any
|
||||
; attribute that participates in components syncronization.
|
||||
(and (= (:type operation) :set)
|
||||
(component-sync-attrs (:attr operation))))
|
||||
any-sync? (some need-sync? operations)]
|
||||
(when any-sync?
|
||||
(into #{} (->> shape-and-parents
|
||||
(filter #(:main-instance? %))
|
||||
(filter #(:main-instance? %)) ; Select shapes that are main component instances
|
||||
(map :id)))))))
|
||||
|
||||
(defmethod components-changed :default
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
|
||||
(defn with-objects
|
||||
[changes objects]
|
||||
(let [file-data (-> (ctf/make-file-data (uuid/next) uuid/zero)
|
||||
(let [file-data (-> (ctf/make-file-data (uuid/next) uuid/zero true)
|
||||
(assoc-in [:pages-index uuid/zero :objects] objects))]
|
||||
(vary-meta changes assoc ::file-data file-data
|
||||
::applied-changes-count 0)))
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
[app.common.colors :as clr]
|
||||
[app.common.uuid :as uuid]))
|
||||
|
||||
(def file-version 20)
|
||||
(def file-version 19)
|
||||
(def default-color clr/gray-20)
|
||||
(def root uuid/zero)
|
||||
|
||||
|
|
|
@ -440,68 +440,5 @@
|
|||
(update :pages-index d/update-vals update-container)
|
||||
(update :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate 20
|
||||
[data]
|
||||
(let [components (ctkl/components-seq data)]
|
||||
(if (empty? components)
|
||||
data
|
||||
(let [grid-gap 50
|
||||
|
||||
[data page-id start-pos]
|
||||
(ctf/get-or-add-library-page data grid-gap)
|
||||
|
||||
add-main-instance
|
||||
(fn [data component position]
|
||||
(let [page (ctpl/get-page data page-id)
|
||||
|
||||
[new-shape new-shapes]
|
||||
(ctn/make-component-instance page
|
||||
component
|
||||
(:id data)
|
||||
position
|
||||
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
|
||||
(fn [component]
|
||||
(assoc component
|
||||
:main-instance-id (:id new-shape)
|
||||
:main-instance-page page-id))]
|
||||
|
||||
(-> data
|
||||
(ctpl/update-page page-id add-shapes)
|
||||
(ctkl/update-component (:id component) update-component))))
|
||||
|
||||
add-instance-grid
|
||||
(fn [data components]
|
||||
(let [position-seq (ctst/generate-shape-grid
|
||||
(map ctk/get-component-root components)
|
||||
start-pos
|
||||
grid-gap)]
|
||||
(loop [data data
|
||||
components-seq (seq components)
|
||||
position-seq position-seq]
|
||||
(let [component (first components-seq)
|
||||
position (first position-seq)]
|
||||
(if (nil? component)
|
||||
data
|
||||
(recur (add-main-instance data component position)
|
||||
(rest components-seq)
|
||||
(rest position-seq)))))))]
|
||||
|
||||
(add-instance-grid data (sort-by :name components))))))
|
||||
|
||||
;; TODO: pending to do a migration for delete already not used fill
|
||||
;; and stroke props. This should be done for >1.14.x version.
|
||||
|
|
|
@ -14,13 +14,19 @@
|
|||
|
||||
(defn add-component
|
||||
[file-data id name path main-instance-id main-instance-page shapes]
|
||||
(assoc-in file-data [:components id]
|
||||
{:id id
|
||||
:name name
|
||||
:path path
|
||||
:main-instance-id main-instance-id
|
||||
:main-instance-page main-instance-page
|
||||
:objects (d/index-by :id shapes)}))
|
||||
(let [components-v2 (get-in file-data [:options :components-v2])]
|
||||
(cond-> file-data
|
||||
:always
|
||||
(assoc-in [:components id]
|
||||
{:id id
|
||||
:name name
|
||||
:path path
|
||||
:objects (d/index-by :id shapes)})
|
||||
|
||||
components-v2
|
||||
(update-in [:components id] #(assoc %
|
||||
:main-instance-id main-instance-id
|
||||
:main-instance-page main-instance-page)))))
|
||||
|
||||
(defn get-component
|
||||
[file-data component-id]
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
"Clone the shape and all children. Generate new ids and detach
|
||||
from parent and frame. Update the original shapes to have links
|
||||
to the new ones."
|
||||
[shape objects file-id]
|
||||
[shape objects file-id components-v2]
|
||||
(assert (nil? (:component-id shape)))
|
||||
(assert (nil? (:component-file shape)))
|
||||
(assert (nil? (:shape-ref shape)))
|
||||
|
@ -94,8 +94,10 @@
|
|||
(nil? (:parent-id new-shape))
|
||||
(assoc :component-id (:id new-shape)
|
||||
:component-file file-id
|
||||
:component-root? true
|
||||
:main-instance? true)
|
||||
:component-root? true)
|
||||
|
||||
components-v2
|
||||
(assoc :main-instance? true)
|
||||
|
||||
(some? (:parent-id new-shape))
|
||||
(dissoc :component-root?)))]
|
||||
|
@ -103,6 +105,9 @@
|
|||
(ctst/clone-object shape nil objects update-new-shape update-original-shape)))
|
||||
|
||||
(defn make-component-instance
|
||||
"Clone the shapes of the component, generating new names and ids, and linking
|
||||
each new shape to the corresponding one of the component. Place the new instance
|
||||
coordinates in the given position."
|
||||
[container component component-file-id position main-instance?]
|
||||
(let [component-shape (get-shape component (:id component))
|
||||
|
||||
|
|
|
@ -83,14 +83,17 @@
|
|||
:pages-index {}})
|
||||
|
||||
(defn make-file-data
|
||||
([file-id]
|
||||
(make-file-data file-id (uuid/next)))
|
||||
([file-id components-v2]
|
||||
(make-file-data file-id (uuid/next) components-v2))
|
||||
|
||||
([file-id page-id]
|
||||
([file-id page-id components-v2]
|
||||
(let [page (ctp/make-empty-page page-id "Page-1")]
|
||||
(-> empty-file-data
|
||||
(assoc :id file-id)
|
||||
(ctpl/add-page page)))))
|
||||
(cond-> (-> empty-file-data
|
||||
(assoc :id file-id)
|
||||
(ctpl/add-page page))
|
||||
|
||||
components-v2
|
||||
(assoc-in [:options :components-v2] true)))))
|
||||
|
||||
;; Helpers
|
||||
|
||||
|
@ -162,9 +165,9 @@
|
|||
assets-seq)))
|
||||
|
||||
(defn get-or-add-library-page
|
||||
[file-data grid-gap]
|
||||
"If exists a page named 'Library page', get the id and calculate the position to start
|
||||
adding new components. If not, create it and start at (0, 0)."
|
||||
[file-data grid-gap]
|
||||
(let [library-page (d/seek #(= (:name %) "Library page") (ctpl/pages-seq file-data))]
|
||||
(if (some? library-page)
|
||||
(let [compare-pos (fn [pos shape]
|
||||
|
@ -180,6 +183,74 @@
|
|||
(let [library-page (ctp/make-empty-page (uuid/next) "Library page")]
|
||||
[(ctpl/add-page file-data library-page) (:id library-page) (gpt/point 0 0)]))))
|
||||
|
||||
(defn migrate-to-components-v2
|
||||
"If there is any component in the file library, add a new 'Library Page' and generate
|
||||
main instances for all components there. Mark the file with the :comonents-v2 option."
|
||||
[file-data]
|
||||
(let [components (ctkl/components-seq file-data)]
|
||||
(if (or (empty? components)
|
||||
(get-in file-data [:options :components-v2]))
|
||||
(assoc-in file-data [:options :components-v2] true)
|
||||
(let [grid-gap 50
|
||||
|
||||
[file-data page-id start-pos]
|
||||
(get-or-add-library-page file-data grid-gap)
|
||||
|
||||
add-main-instance
|
||||
(fn [file-data component position]
|
||||
(let [page (ctpl/get-page file-data page-id)
|
||||
|
||||
[new-shape new-shapes]
|
||||
(ctn/make-component-instance page
|
||||
component
|
||||
(:id file-data)
|
||||
position
|
||||
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
|
||||
(fn [component]
|
||||
(assoc component
|
||||
:main-instance-id (:id new-shape)
|
||||
:main-instance-page page-id))]
|
||||
|
||||
(-> file-data
|
||||
(ctpl/update-page page-id add-shapes)
|
||||
(ctkl/update-component (:id component) update-component))))
|
||||
|
||||
add-instance-grid
|
||||
(fn [file-data components]
|
||||
(let [position-seq (ctst/generate-shape-grid
|
||||
(map ctk/get-component-root components)
|
||||
start-pos
|
||||
grid-gap)]
|
||||
(loop [file-data file-data
|
||||
components-seq (seq components)
|
||||
position-seq position-seq]
|
||||
(let [component (first components-seq)
|
||||
position (first position-seq)]
|
||||
(if (nil? component)
|
||||
file-data
|
||||
(recur (add-main-instance file-data component position)
|
||||
(rest components-seq)
|
||||
(rest position-seq)))))))]
|
||||
|
||||
(-> file-data
|
||||
(add-instance-grid (sort-by :name components))
|
||||
(assoc-in [:options :components-v2] true))))))
|
||||
|
||||
(defn- absorb-components
|
||||
[file-data library-data used-components]
|
||||
(let [grid-gap 50
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
(t/deftest process-change-set-option
|
||||
(let [file-id (uuid/custom 2 2)
|
||||
page-id (uuid/custom 1 1)
|
||||
data (ctf/make-file-data file-id page-id)]
|
||||
data (ctf/make-file-data file-id page-id true)]
|
||||
(t/testing "Sets option single"
|
||||
(let [chg {:type :set-option
|
||||
:page-id page-id
|
||||
|
@ -81,7 +81,7 @@
|
|||
(t/deftest process-change-add-obj
|
||||
(let [file-id (uuid/custom 2 2)
|
||||
page-id (uuid/custom 1 1)
|
||||
data (ctf/make-file-data file-id page-id)
|
||||
data (ctf/make-file-data file-id page-id true)
|
||||
id-a (uuid/custom 2 1)
|
||||
id-b (uuid/custom 2 2)
|
||||
id-c (uuid/custom 2 3)]
|
||||
|
@ -135,7 +135,7 @@
|
|||
(t/deftest process-change-mod-obj
|
||||
(let [file-id (uuid/custom 2 2)
|
||||
page-id (uuid/custom 1 1)
|
||||
data (ctf/make-file-data file-id page-id)]
|
||||
data (ctf/make-file-data file-id page-id true)]
|
||||
(t/testing "simple mod-obj"
|
||||
(let [chg {:type :mod-obj
|
||||
:page-id page-id
|
||||
|
@ -162,7 +162,7 @@
|
|||
(let [file-id (uuid/custom 2 2)
|
||||
page-id (uuid/custom 1 1)
|
||||
id (uuid/custom 2 1)
|
||||
data (ctf/make-file-data file-id page-id)
|
||||
data (ctf/make-file-data file-id page-id true)
|
||||
data (-> data
|
||||
(assoc-in [:pages-index page-id :objects uuid/zero :shapes] [id])
|
||||
(assoc-in [:pages-index page-id :objects id]
|
||||
|
@ -206,7 +206,7 @@
|
|||
|
||||
file-id (uuid/custom 2 2)
|
||||
page-id (uuid/custom 1 1)
|
||||
data (ctf/make-file-data file-id page-id)
|
||||
data (ctf/make-file-data file-id page-id true)
|
||||
|
||||
data (update-in data [:pages-index page-id :objects]
|
||||
#(-> %
|
||||
|
@ -450,7 +450,7 @@
|
|||
:obj {:type :rect
|
||||
:name "Shape 3"}}
|
||||
]
|
||||
data (ctf/make-file-data file-id page-id)
|
||||
data (ctf/make-file-data file-id page-id true)
|
||||
data (cp/process-changes data changes)]
|
||||
|
||||
(t/testing "preserve order on multiple shape mov 1"
|
||||
|
@ -557,7 +557,7 @@
|
|||
:parent-id group-1-id
|
||||
:shapes [shape-1-id shape-2-id]}]
|
||||
|
||||
data (ctf/make-file-data file-id page-id)
|
||||
data (ctf/make-file-data file-id page-id true)
|
||||
data (cp/process-changes data changes)]
|
||||
|
||||
(t/testing "case 1"
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
(ns app.common.test-helpers.components
|
||||
(:require
|
||||
[cljs.test :as t :include-macros true]
|
||||
[cljs.pprint :refer [pprint]]
|
||||
[clojure.test :as t]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.container :as ctn]))
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
([file-id page-id props]
|
||||
(merge {:id file-id
|
||||
:name (get props :name "File1")
|
||||
:data (ctf/make-file-data file-id page-id)}
|
||||
:data (ctf/make-file-data file-id page-id true)}
|
||||
props)))
|
||||
|
||||
(defn sample-shape
|
||||
|
@ -68,9 +68,10 @@
|
|||
(let [page (ctpl/get-page file-data page-id)
|
||||
|
||||
[component-shape component-shapes updated-shapes]
|
||||
(ctn/make-component-shape (ctn/get-shape page shape-id)
|
||||
(ctn/make-component-shape (ctn/get-shape page shape-id true)
|
||||
(:objects page)
|
||||
(:id file))]
|
||||
(:id file)
|
||||
true)]
|
||||
|
||||
(swap! idmap assoc label (:id component-shape))
|
||||
(-> file-data
|
||||
|
|
|
@ -110,6 +110,7 @@
|
|||
|
||||
(t/is (= (:name p-group) "Group1"))
|
||||
(t/is (ctk/instance-of? p-group file-id (:id component1)))
|
||||
(t/is (not (:main-instance? p-group)))
|
||||
(t/is (not (ctk/is-main-instance? (:id p-group) file-page-id component1)))
|
||||
(t/is (ctk/is-main-of? c-group1 p-group))
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue