mirror of
https://github.com/penpot/penpot.git
synced 2025-08-01 06:58:34 +02:00
✨ Integrate objects-map and introduce file feature flags
This commit is contained in:
parent
69f084e1df
commit
951b3eb4fe
21 changed files with 406 additions and 264 deletions
10
common/src/app/common/files/features.cljc
Normal file
10
common/src/app/common/files/features.cljc
Normal file
|
@ -0,0 +1,10 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.common.files.features)
|
||||
|
||||
(def ^:dynamic *current* #{})
|
||||
(def ^:dynamic *wrap-objects-fn* identity)
|
|
@ -347,27 +347,12 @@
|
|||
;; -- Components
|
||||
|
||||
(defmethod process-change :add-component
|
||||
[data {:keys [id name path main-instance-id main-instance-page shapes]}]
|
||||
(ctkl/add-component data
|
||||
id
|
||||
name
|
||||
path
|
||||
main-instance-id
|
||||
main-instance-page
|
||||
shapes))
|
||||
[data params]
|
||||
(ctkl/add-component data params))
|
||||
|
||||
(defmethod process-change :mod-component
|
||||
[data {:keys [id name path objects]}]
|
||||
(update-in data [:components id]
|
||||
#(cond-> %
|
||||
(some? name)
|
||||
(assoc :name name)
|
||||
|
||||
(some? path)
|
||||
(assoc :path path)
|
||||
|
||||
(some? objects)
|
||||
(assoc :objects objects))))
|
||||
[data params]
|
||||
(ctkl/mod-component data params))
|
||||
|
||||
(defmethod process-change :del-component
|
||||
[data {:keys [id skip-undelete?]}]
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.features :as ffeat]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
|
@ -50,10 +51,12 @@
|
|||
|
||||
(defn with-objects
|
||||
[changes objects]
|
||||
(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)))
|
||||
(let [fdata (binding [ffeat/*current* #{"components/v2"}]
|
||||
(ctf/make-file-data (uuid/next) uuid/zero))
|
||||
fdata (assoc-in fdata [:pages-index uuid/zero :objects] objects)]
|
||||
(vary-meta changes assoc
|
||||
::file-data fdata
|
||||
::applied-changes-count 0)))
|
||||
|
||||
(defn with-library-data
|
||||
[changes data]
|
||||
|
@ -268,7 +271,7 @@
|
|||
:page-id (::page-id (meta changes))
|
||||
:parent-id (:parent-id shape)
|
||||
:shapes [(:id shape)]
|
||||
:index idx})))]
|
||||
:index idx})))]
|
||||
|
||||
(-> changes
|
||||
(update :redo-changes conj set-parent-change)
|
||||
|
@ -592,7 +595,7 @@
|
|||
:main-instance-page main-instance-page
|
||||
:shapes new-shapes})
|
||||
(into (map mk-change) updated-shapes))))
|
||||
(update :undo-changes
|
||||
(update :undo-changes
|
||||
(fn [undo-changes]
|
||||
(-> undo-changes
|
||||
(d/preconj {:type :del-component
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
(defn instance-root?
|
||||
[shape]
|
||||
(some? (:component-id shape)))
|
||||
|
||||
|
||||
(defn instance-of?
|
||||
[shape file-id component-id]
|
||||
(and (some? (:component-id shape))
|
||||
|
|
|
@ -2,30 +2,52 @@
|
|||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
;; Copyright (c) KELEIDOS INC
|
||||
|
||||
(ns app.common.types.components-list
|
||||
(:require
|
||||
[app.common.data :as d]))
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.features :as feat]))
|
||||
|
||||
(defn components-seq
|
||||
[file-data]
|
||||
(vals (:components file-data)))
|
||||
|
||||
(defn add-component
|
||||
[file-data id name path main-instance-id main-instance-page shapes]
|
||||
(let [components-v2 (get-in file-data [:options :components-v2])]
|
||||
[file-data {:keys [id name path main-instance-id main-instance-page shapes]}]
|
||||
(let [components-v2 (dm/get-in file-data [:options :components-v2])
|
||||
wrap-object-fn feat/*wrap-objects-fn*]
|
||||
(cond-> file-data
|
||||
:always
|
||||
(assoc-in [:components id]
|
||||
{:id id
|
||||
:name name
|
||||
:path path
|
||||
:objects (d/index-by :id shapes)})
|
||||
:objects (->> shapes
|
||||
(d/index-by :id)
|
||||
(wrap-object-fn))})
|
||||
|
||||
components-v2
|
||||
(update-in [:components id] assoc :main-instance-id main-instance-id
|
||||
:main-instance-page main-instance-page))))
|
||||
(update-in [:components id] assoc
|
||||
:main-instance-id main-instance-id
|
||||
:main-instance-page main-instance-page))))
|
||||
|
||||
(defn mod-component
|
||||
[file-data {:keys [id name path objects]}]
|
||||
(let [wrap-objects-fn feat/*wrap-objects-fn*]
|
||||
(update-in file-data [:components id]
|
||||
(fn [component]
|
||||
(let [objects (some-> objects wrap-objects-fn)]
|
||||
(cond-> component
|
||||
(some? name)
|
||||
(assoc :name name)
|
||||
|
||||
(some? path)
|
||||
(assoc :path path)
|
||||
|
||||
(some? objects)
|
||||
(assoc :objects objects)))))))
|
||||
|
||||
(defn get-component
|
||||
[file-data component-id]
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
(ns app.common.types.container
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.spec :as us]
|
||||
|
@ -41,8 +42,8 @@
|
|||
(us/assert uuid? id)
|
||||
|
||||
(-> (if (= type :page)
|
||||
(get-in file [:pages-index id])
|
||||
(get-in file [:components id]))
|
||||
(dm/get-in file [:pages-index id])
|
||||
(dm/get-in file [:components id]))
|
||||
(assoc :type type)))
|
||||
|
||||
(defn get-shape
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
(ns app.common.types.file
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.features :as ffeat]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.pages.common :refer [file-version]]
|
||||
|
@ -65,16 +67,16 @@
|
|||
:pages-index {}})
|
||||
|
||||
(defn make-file-data
|
||||
([file-id components-v2]
|
||||
(make-file-data file-id (uuid/next) components-v2))
|
||||
([file-id]
|
||||
(make-file-data file-id (uuid/next)))
|
||||
|
||||
([file-id page-id components-v2]
|
||||
([file-id page-id]
|
||||
(let [page (ctp/make-empty-page page-id "Page-1")]
|
||||
(cond-> (-> empty-file-data
|
||||
(assoc :id file-id)
|
||||
(ctpl/add-page page))
|
||||
|
||||
components-v2
|
||||
(contains? ffeat/*current* "components/v2")
|
||||
(assoc-in [:options :components-v2] true)))))
|
||||
|
||||
;; Helpers
|
||||
|
@ -108,7 +110,7 @@
|
|||
([libraries component-id]
|
||||
(some #(ctkl/get-component (:data %) component-id) (vals libraries)))
|
||||
([libraries library-id component-id]
|
||||
(ctkl/get-component (get-in libraries [library-id :data]) component-id)))
|
||||
(ctkl/get-component (dm/get-in libraries [library-id :data]) component-id)))
|
||||
|
||||
(defn delete-component
|
||||
"Delete a component and store it to be able to be recovered later.
|
||||
|
@ -118,7 +120,7 @@
|
|||
(delete-component file-data component-id false))
|
||||
|
||||
([file-data component-id skip-undelete?]
|
||||
(let [components-v2 (get-in file-data [:options :components-v2])
|
||||
(let [components-v2 (dm/get-in file-data [:options :components-v2])
|
||||
|
||||
add-to-deleted-components
|
||||
(fn [file-data]
|
||||
|
@ -144,12 +146,12 @@
|
|||
(defn get-deleted-component
|
||||
"Retrieve a component that has been deleted but still is in the safe store."
|
||||
[file-data component-id]
|
||||
(get-in file-data [:deleted-components component-id]))
|
||||
(dm/get-in file-data [:deleted-components component-id]))
|
||||
|
||||
(defn restore-component
|
||||
"Recover a deleted component and put it again in place."
|
||||
[file-data component-id]
|
||||
(let [component (-> (get-in file-data [:deleted-components component-id])
|
||||
(let [component (-> (dm/get-in file-data [:deleted-components component-id])
|
||||
(dissoc :main-instance-x :main-instance-y))]
|
||||
(cond-> file-data
|
||||
(some? component)
|
||||
|
@ -242,7 +244,7 @@
|
|||
[file-data]
|
||||
(let [components (ctkl/components-seq file-data)]
|
||||
(if (or (empty? components)
|
||||
(get-in file-data [:options :components-v2]))
|
||||
(dm/get-in file-data [:options :components-v2]))
|
||||
(assoc-in file-data [:options :components-v2] true)
|
||||
(let [grid-gap 50
|
||||
|
||||
|
@ -342,12 +344,12 @@
|
|||
copy-component
|
||||
(fn [file-data]
|
||||
(ctkl/add-component file-data
|
||||
(:id component)
|
||||
(:name component)
|
||||
(:path component)
|
||||
(:id main-instance-shape)
|
||||
page-id
|
||||
(vals (:objects component))))
|
||||
{:id (:id component)
|
||||
:name (:name component)
|
||||
:path (:path component)
|
||||
:main-instance-id (:id main-instance-shape)
|
||||
:main-instance-page page-id
|
||||
:shapes (vals (:objects component))}))
|
||||
|
||||
; Change all existing instances to point to the local file
|
||||
remap-instances
|
||||
|
@ -500,10 +502,10 @@
|
|||
component-file (when component-file-id (get libraries component-file-id nil))
|
||||
component (when component-id
|
||||
(if component-file
|
||||
(get-in component-file [:data :components component-id])
|
||||
(dm/get-in component-file [:data :components component-id])
|
||||
(get components component-id)))
|
||||
component-shape (when (and component (:shape-ref shape))
|
||||
(get-in component [:objects (:shape-ref shape)]))]
|
||||
(dm/get-in component [:objects (:shape-ref shape)]))]
|
||||
(str/format " %s--> %s%s%s"
|
||||
(cond (:component-root? shape) "#"
|
||||
(:component-id shape) "@"
|
||||
|
@ -518,7 +520,7 @@
|
|||
component-file-id (:component-file shape)
|
||||
component-file (when component-file-id (get libraries component-file-id nil))
|
||||
component (if component-file
|
||||
(get-in component-file [:data :components component-id])
|
||||
(dm/get-in component-file [:data :components component-id])
|
||||
(get components component-id))]
|
||||
(str/format " (%s%s)"
|
||||
(when component-file (str/format "<%s> " (:name component-file)))
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
(ns app.common.types.page
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.files.features :as ffeat]
|
||||
[app.common.types.page.flow :as ctpf]
|
||||
[app.common.types.page.grid :as ctpg]
|
||||
[app.common.types.page.guide :as ctpu]
|
||||
|
@ -48,9 +49,11 @@
|
|||
|
||||
(defn make-empty-page
|
||||
[id name]
|
||||
(assoc empty-page-data
|
||||
:id id
|
||||
:name name))
|
||||
(let [wrap-fn ffeat/*wrap-objects-fn*]
|
||||
(-> empty-page-data
|
||||
(assoc :id id)
|
||||
(assoc :name name)
|
||||
(update :objects wrap-fn))))
|
||||
|
||||
;; --- Helpers for flow
|
||||
|
||||
|
|
|
@ -2,32 +2,29 @@
|
|||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.common.types.pages-list
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.pages.helpers :as cph]))
|
||||
|
||||
(defn get-page
|
||||
[file-data id]
|
||||
(get-in file-data [:pages-index id]))
|
||||
(dm/get-in file-data [:pages-index id]))
|
||||
|
||||
(defn add-page
|
||||
[file-data page]
|
||||
(let [index (:index page)
|
||||
page (dissoc page :index)
|
||||
|
||||
; It's legitimate to add a page that is already there,
|
||||
; for example in an idempotent changes operation.
|
||||
add-if-not-exists (fn [pages id]
|
||||
(cond
|
||||
(d/seek #(= % id) pages) pages
|
||||
(nil? index) (conj pages id)
|
||||
:else (cph/insert-at-index pages index [id])))]
|
||||
(-> file-data
|
||||
(update :pages add-if-not-exists (:id page))
|
||||
(update :pages-index assoc (:id page) page))))
|
||||
[file-data {:keys [id index] :as page}]
|
||||
(-> file-data
|
||||
;; It's legitimate to add a page that is already there, for
|
||||
;; example in an idempotent changes operation.
|
||||
(update :pages (fn [pages]
|
||||
(let [exists? (some (partial = id) pages)]
|
||||
(cond
|
||||
exists? pages
|
||||
(nil? index) (conj pages id)
|
||||
:else (cph/insert-at-index pages index [id])))))
|
||||
(update :pages-index assoc id (dissoc page :index))))
|
||||
|
||||
(defn pages-seq
|
||||
[file-data]
|
||||
|
@ -42,4 +39,3 @@
|
|||
(-> file-data
|
||||
(update :pages (fn [pages] (filterv #(not= % page-id) pages)))
|
||||
(update :pages-index dissoc page-id)))
|
||||
|
||||
|
|
|
@ -6,16 +6,22 @@
|
|||
|
||||
(ns app.common.pages-test
|
||||
(:require
|
||||
[clojure.test :as t]
|
||||
[clojure.pprint :refer [pprint]]
|
||||
[app.common.files.features :as ffeat]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.uuid :as uuid]))
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.pprint :refer [pprint]]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(defn- make-file-data
|
||||
[file-id page-id]
|
||||
(binding [ffeat/*current* #{"components/v2"}]
|
||||
(ctf/make-file-data file-id page-id)))
|
||||
|
||||
(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 true)]
|
||||
data (make-file-data file-id page-id)]
|
||||
(t/testing "Sets option single"
|
||||
(let [chg {:type :set-option
|
||||
:page-id page-id
|
||||
|
@ -81,7 +87,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 true)
|
||||
data (make-file-data file-id page-id)
|
||||
id-a (uuid/custom 2 1)
|
||||
id-b (uuid/custom 2 2)
|
||||
id-c (uuid/custom 2 3)]
|
||||
|
@ -135,7 +141,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 true)]
|
||||
data (make-file-data file-id page-id)]
|
||||
(t/testing "simple mod-obj"
|
||||
(let [chg {:type :mod-obj
|
||||
:page-id page-id
|
||||
|
@ -162,7 +168,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 true)
|
||||
data (make-file-data file-id page-id)
|
||||
data (-> data
|
||||
(assoc-in [:pages-index page-id :objects uuid/zero :shapes] [id])
|
||||
(assoc-in [:pages-index page-id :objects id]
|
||||
|
@ -206,7 +212,7 @@
|
|||
|
||||
file-id (uuid/custom 2 2)
|
||||
page-id (uuid/custom 1 1)
|
||||
data (ctf/make-file-data file-id page-id true)
|
||||
data (make-file-data file-id page-id)
|
||||
|
||||
data (update-in data [:pages-index page-id :objects]
|
||||
#(-> %
|
||||
|
@ -450,7 +456,7 @@
|
|||
:obj {:type :rect
|
||||
:name "Shape 3"}}
|
||||
]
|
||||
data (ctf/make-file-data file-id page-id true)
|
||||
data (make-file-data file-id page-id)
|
||||
data (cp/process-changes data changes)]
|
||||
|
||||
(t/testing "preserve order on multiple shape mov 1"
|
||||
|
@ -557,7 +563,7 @@
|
|||
:parent-id group-1-id
|
||||
:shapes [shape-1-id shape-2-id]}]
|
||||
|
||||
data (ctf/make-file-data file-id page-id true)
|
||||
data (make-file-data file-id page-id)
|
||||
data (cp/process-changes data changes)]
|
||||
|
||||
(t/testing "case 1"
|
||||
|
|
|
@ -6,16 +6,22 @@
|
|||
|
||||
(ns app.common.test-helpers.files
|
||||
(:require
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.colors-list :as ctcl]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.pages-list :as ctpl]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.typographies-list :as ctyl]
|
||||
[app.common.uuid :as uuid]))
|
||||
[app.common.files.features :as ffeat]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.types.colors-list :as ctcl]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.pages-list :as ctpl]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.common.types.typographies-list :as ctyl]
|
||||
[app.common.uuid :as uuid]))
|
||||
|
||||
(defn- make-file-data
|
||||
[file-id page-id]
|
||||
(binding [ffeat/*current* #{"components/v2"}]
|
||||
(ctf/make-file-data file-id page-id)))
|
||||
|
||||
(def ^:private idmap (atom {}))
|
||||
|
||||
|
@ -33,7 +39,7 @@
|
|||
([file-id page-id props]
|
||||
(merge {:id file-id
|
||||
:name (get props :name "File1")
|
||||
:data (ctf/make-file-data file-id page-id true)}
|
||||
:data (make-file-data file-id page-id)}
|
||||
props)))
|
||||
|
||||
(defn sample-shape
|
||||
|
@ -81,12 +87,12 @@
|
|||
#(reduce (fn [page shape] (ctst/set-shape page shape))
|
||||
%
|
||||
updated-shapes))
|
||||
(ctkl/add-component (:id component-shape)
|
||||
(:name component-shape)
|
||||
""
|
||||
shape-id
|
||||
page-id
|
||||
component-shapes))))))
|
||||
(ctkl/add-component {:id (:id component-shape)
|
||||
:name (:name component-shape)
|
||||
:path ""
|
||||
:main-instance-id shape-id
|
||||
:main-instance-page page-id
|
||||
:shapes component-shapes}))))))
|
||||
|
||||
(defn sample-instance
|
||||
[file label page-id library component-id]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue