0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-04-12 23:11:23 -05:00

🎉 Scaffolding to write unit tests of common types

This commit is contained in:
Andrés Moya 2022-06-23 17:43:43 +02:00
parent 165cdd871f
commit 54e0071c9c
35 changed files with 724 additions and 228 deletions

View file

@ -11,6 +11,7 @@
[app.common.pages :as cp]
[app.common.pages.migrations :as pmg]
[app.common.spec :as us]
[app.common.types.file :as ctf]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
@ -69,7 +70,7 @@
:or {is-shared false revn 0}
:as params}]
(let [id (or id (:id data) (uuid/next))
data (or data (cp/make-file-data id))
data (or data (ctf/make-file-data id))
file (db/insert! conn :file
(d/without-nils
{:id id

View file

@ -12,8 +12,8 @@
[app.common.geom.shapes :as gsh]
[app.common.pages.helpers :as cph]
[app.common.pages.migrations :as pmg]
[app.common.types.shape-tree :as ctt]
[app.common.spec :as us]
[app.common.types.shape-tree :as ctt]
[app.db :as db]
[app.db.sql :as sql]
[app.rpc.helpers :as rpch]

View file

@ -12,7 +12,6 @@
(:require
[app.common.data :as d]
[app.common.logging :as l]
[app.common.pages.helpers :as cph]
[app.common.pages.migrations :as pmg]
[app.common.types.shape-tree :as ctt]
[app.db :as db]

View file

@ -12,10 +12,10 @@
[app.common.geom.shapes :as gsh]
[app.common.pages.changes :as ch]
[app.common.pages.changes-spec :as pcs]
[app.common.pages.init :as init]
[app.common.types.page :as ctp]
[app.common.spec :as us]
[app.common.types.file :as ctf]
[app.common.types.page :as ctp]
[app.common.types.shape :as cts]
[app.common.uuid :as uuid]
[cuerdas.core :as str]))
@ -169,7 +169,7 @@
([id name]
{:id id
:name name
:data (-> init/empty-file-data
:data (-> ctf/empty-file-data
(assoc :id id))
;; We keep the changes so we can send them to the backend
@ -209,7 +209,7 @@
(defn add-artboard [file data]
(assert (nil? (:current-component-id file)))
(let [obj (-> (init/make-minimal-shape :frame)
(let [obj (-> (cts/make-minimal-shape :frame)
(merge data)
(check-name file :frame)
(setup-selrect)
@ -233,9 +233,9 @@
(defn add-group [file data]
(let [frame-id (:current-frame-id file)
selrect init/empty-selrect
selrect cts/empty-selrect
name (:name data)
obj (-> (init/make-minimal-group frame-id selrect name)
obj (-> (cts/make-minimal-group frame-id selrect name)
(merge data)
(check-name file :group)
(d/without-nils))]
@ -347,7 +347,7 @@
(update :parent-stack pop))))
(defn create-shape [file type data]
(let [obj (-> (init/make-minimal-shape type)
(let [obj (-> (cts/make-minimal-shape type)
(merge data)
(check-name file :type)
(setup-selrect)
@ -515,10 +515,10 @@
(defn start-component
[file data]
(let [selrect init/empty-selrect
(let [selrect cts/empty-selrect
name (:name data)
path (:path data)
obj (-> (init/make-minimal-group nil selrect name)
obj (-> (cts/make-minimal-group nil selrect name)
(merge data)
(check-name file :group)
(d/without-nils))]

View file

@ -12,7 +12,7 @@
[app.common.pages.common :as common]
[app.common.pages.focus :as focus]
[app.common.pages.indices :as indices]
[app.common.pages.init :as init]))
[app.common.types.file :as ctf]))
;; Common
(dm/export common/root)
@ -36,11 +36,5 @@
(dm/export changes/process-changes)
;; Initialization
(dm/export init/default-frame-attrs)
(dm/export init/default-shape-attrs)
(dm/export init/make-file-data)
(dm/export init/make-minimal-shape)
(dm/export init/make-minimal-group)
(dm/export init/empty-file-data)
(dm/export init/setup-shape)
(dm/export init/setup-rect-selrect)
(dm/export ctf/make-file-data)
(dm/export ctf/empty-file-data)

View file

@ -14,9 +14,9 @@
[app.common.math :as mth]
[app.common.pages.common :refer [component-sync-attrs]]
[app.common.pages.helpers :as cph]
[app.common.pages.init :as init]
[app.common.spec :as us]
[app.common.pages.changes-spec :as pcs]
[app.common.types.components-list :as ctkl]
[app.common.types.page :as ctp]
[app.common.types.pages-list :as ctpl]
[app.common.types.shape :as cts]
@ -346,13 +346,13 @@
(defmethod process-change :add-component
[data {:keys [id name path main-instance-id main-instance-page shapes]}]
(assoc-in 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)}))
(ctkl/add-component data
id
name
path
main-instance-id
main-instance-page
shapes))
(defmethod process-change :mod-component
[data {:keys [id name path objects]}]

View file

@ -16,6 +16,7 @@
[app.common.math :as mth]
[app.common.pages :as cp]
[app.common.pages.helpers :as cph]
[app.common.types.file :as ctf]
[app.common.uuid :as uuid]))
;; Auxiliary functions to help create a set of changes (undo + redo)
@ -49,7 +50,7 @@
(defn with-objects
[changes objects]
(let [file-data (-> (cp/make-file-data (uuid/next) uuid/zero)
(let [file-data (-> (ctf/make-file-data (uuid/next) uuid/zero)
(assoc-in [:pages-index uuid/zero :objects] objects))]
(vary-meta changes assoc ::file-data file-data
::applied-changes-count 0)))

View file

@ -8,7 +8,6 @@
(:require
[app.common.data :as d]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.geom.shapes.path :as gsp]
[app.common.geom.shapes.text :as gsht]
@ -16,9 +15,10 @@
[app.common.math :as mth]
[app.common.pages :as cp]
[app.common.pages.helpers :as cph]
[app.common.types.container :as ctc]
[app.common.types.container :as ctn]
[app.common.types.page :as ctp]
[app.common.types.pages-list :as ctpl]
[app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst]
[app.common.uuid :as uuid]
[cuerdas.core :as str]))
@ -89,7 +89,7 @@
(fix-empty-points [shape]
(let [shape (cond-> shape
(empty? (:selrect shape)) (cp/setup-rect-selrect))]
(empty? (:selrect shape)) (cts/setup-rect-selrect))]
(cond-> shape
(empty? (:points shape))
(assoc :points (gsh/rect->points (:selrect shape))))))
@ -456,7 +456,7 @@
(let [page (ctpl/get-page data page-id)
[new-shape new-shapes]
(ctc/instantiate-component page
(ctn/instantiate-component page
component
(:id data)
position)

View file

@ -0,0 +1,28 @@
;; 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) UXBOX Labs SL
(ns app.common.types.components-list
(:require
[app.common.data :as d]))
(defn components-seq
[file-data]
(vals (:components file-data)))
(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)}))
(defn get-component
[file-data component-id]
(get-in file-data [:components component-id]))

View file

@ -9,7 +9,7 @@
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.spec :as us]
[app.common.types.shape-tree :as ctt]
[app.common.types.shape-tree :as ctst]
[clojure.spec.alpha :as s]))
(s/def ::type #{:page :component})
@ -18,9 +18,32 @@
(s/def ::path (s/nilable string?))
(s/def ::container
(s/keys :req-un [::id ::name ::ctt/objects]
(s/keys :req-un [::id ::name ::ctst/objects]
:opt-un [::type ::path]))
(defn make-container
[page-or-component type]
(assoc page-or-component :type type))
(defn page?
[container]
(= (:type container) :page))
(defn component?
[container]
(= (:type container) :component))
(defn get-container
[file type id]
(us/assert map? file)
(us/assert ::type type)
(us/assert uuid? id)
(-> (if (= type :page)
(get-in file [:pages-index id])
(get-in file [:components id]))
(assoc :type type)))
(defn get-shape
[container shape-id]
(us/assert ::container container)
@ -29,21 +52,65 @@
(get :objects)
(get shape-id)))
(defn shapes-seq
[container]
(vals (:objects container)))
(defn make-component-shape
"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]
(assert (nil? (:component-id shape)))
(assert (nil? (:component-file shape)))
(assert (nil? (:shape-ref shape)))
(let [;; Ensure that the component root is not an instance and
;; it's no longer tied to a frame.
update-new-shape (fn [new-shape _original-shape]
(cond-> new-shape
true
(-> (assoc :frame-id nil)
(dissoc :component-root?))
(nil? (:parent-id new-shape))
(dissoc :component-id
:component-file
:shape-ref)))
;; Make the original shape an instance of the new component.
;; If one of the original shape children already was a component
;; instance, maintain this instanceness untouched.
update-original-shape (fn [original-shape new-shape]
(cond-> original-shape
(nil? (:shape-ref original-shape))
(-> (assoc :shape-ref (:id new-shape))
(dissoc :touched))
(nil? (:parent-id new-shape))
(assoc :component-id (:id new-shape)
:component-file file-id
:component-root? true)
(some? (:parent-id new-shape))
(dissoc :component-root?)))]
(ctst/clone-object shape nil objects update-new-shape update-original-shape)))
(defn instantiate-component
[container component component-file position]
[container component component-file-id position]
(let [component-shape (get-shape component (:id component))
orig-pos (gpt/point (:x component-shape) (:y component-shape))
delta (gpt/subtract position orig-pos)
objects (:objects container)
unames (volatile! (ctt/retrieve-used-names objects))
unames (volatile! (ctst/retrieve-used-names objects))
frame-id (ctt/frame-id-by-position objects (gpt/add orig-pos delta))
frame-id (ctst/frame-id-by-position objects (gpt/add orig-pos delta))
update-new-shape
(fn [new-shape original-shape]
(let [new-name (ctt/generate-unique-name @unames (:name new-shape))]
(let [new-name (ctst/generate-unique-name @unames (:name new-shape))]
(when (nil? (:parent-id original-shape))
(vswap! unames conj new-name))
@ -62,7 +129,7 @@
(nil? (:parent-id original-shape))
(assoc :component-id (:id original-shape)
:component-file component-file
:component-file component-file-id
:component-root? true
:name new-name)
@ -70,10 +137,10 @@
(dissoc :component-root?))))
[new-shape new-shapes _]
(ctt/clone-object component-shape
nil
(get component :objects)
update-new-shape)]
(ctst/clone-object component-shape
nil
(get component :objects)
update-new-shape)]
[new-shape new-shapes]))

View file

@ -6,10 +6,20 @@
(ns app.common.types.file
(:require
[app.common.data :as d]
[app.common.pages.common :refer [file-version]]
[app.common.pages.helpers :as cph]
[app.common.spec :as us]
[app.common.types.color :as ctc]
[app.common.types.components-list :as ctkl]
[app.common.types.container :as ctn]
[app.common.types.page :as ctp]
[clojure.spec.alpha :as s]))
[app.common.types.pages-list :as ctpl]
[app.common.uuid :as uuid]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]))
;; Specs
(s/def :internal.media-object/name string?)
(s/def :internal.media-object/width ::us/safe-integer)
@ -57,3 +67,158 @@
::recent-colors
::typographies
::media]))
;; Initialization
(def empty-file-data
{:version file-version
:pages []
:pages-index {}})
(defn make-file-data
([file-id]
(make-file-data file-id (uuid/next)))
([file-id page-id]
(let [page (ctp/make-empty-page page-id "Page-1")]
(-> empty-file-data
(assoc :id file-id)
(ctpl/add-page page)))))
;; Helpers
(defn update-file-data
[file f]
(update file :data f))
(defn containers-seq
"Generate a sequence of all pages and all components, wrapped as containers"
[file-data]
(concat (map #(ctn/make-container % :page) (ctpl/pages-seq file-data))
(map #(ctn/make-container % :component) (ctkl/components-seq file-data))))
(defn absorb-assets
"Find all assets of a library that are used in the file, and
move them to the file local library."
[file-data library-data]
(let [library-page-id (uuid/next)
add-library-page
(fn [file-data]
(let [page (ctp/make-empty-page library-page-id "Library page")]
(-> file-data
(ctpl/add-page page))))
find-instances-in-container
(fn [container component]
(let [instances (filter #(= (:component-id %) (:id component))
(ctn/shapes-seq container))]
(when (d/not-empty? instances)
[[container instances]])))
find-instances
(fn [file-data component]
(mapcat #(find-instances-in-container % component) (containers-seq file-data)))
absorb-component
(fn [file-data _component]
;; TODO: complete this
file-data)
used-components
(mapcat (fn [component]
(let [instances (find-instances file-data component)]
(when instances
[[component instances]])))
(ctkl/components-seq library-data))]
(if (empty? used-components)
file-data
(as-> file-data $
(add-library-page $)
(reduce absorb-component
$
used-components)))))
;; Debug helpers
(defn dump-tree
([file-data page-id libraries]
(dump-tree file-data page-id libraries false false))
([file-data page-id libraries show-ids]
(dump-tree file-data page-id libraries show-ids false))
([file-data page-id libraries show-ids show-touched]
(let [page (ctpl/get-page file-data page-id)
objects (:objects page)
components (:components file-data)
root (d/seek #(nil? (:parent-id %)) (vals objects))]
(letfn [(show-shape [shape-id level objects]
(let [shape (get objects shape-id)]
(println (str/pad (str (str/repeat " " level)
(:name shape)
(when (seq (:touched shape)) "*")
(when show-ids (str/format " <%s>" (:id shape))))
{:length 20
:type :right})
(show-component shape objects))
(when show-touched
(when (seq (:touched shape))
(println (str (str/repeat " " level)
" "
(str (:touched shape)))))
(when (:remote-synced? shape)
(println (str (str/repeat " " level)
" (remote-synced)"))))
(when (:shapes shape)
(dorun (for [shape-id (:shapes shape)]
(show-shape shape-id (inc level) objects))))))
(show-component [shape objects]
(if (nil? (:shape-ref shape))
""
(let [root-shape (cph/get-component-shape objects shape)
component-id (when root-shape (:component-id root-shape))
component-file-id (when root-shape (:component-file root-shape))
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])
(get components component-id)))
component-shape (when (and component (:shape-ref shape))
(get-in component [:objects (:shape-ref shape)]))]
(str/format " %s--> %s%s%s"
(cond (:component-root? shape) "#"
(:component-id shape) "@"
:else "-")
(when component-file (str/format "<%s> " (:name component-file)))
(or (:name component-shape) "?")
(if (or (:component-root? shape)
(nil? (:component-id shape))
true)
""
(let [component-id (:component-id shape)
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])
(get components component-id))]
(str/format " (%s%s)"
(when component-file (str/format "<%s> " (:name component-file)))
(:name component))))))))]
(println "[Page]")
(show-shape (:id root) 0 objects)
(dorun (for [component (vals components)]
(do
(println)
(println (str/format "[%s]" (:name component))
(when show-ids
(str/format " (main: %s/%s)"
(:main-instance-page component)
(:main-instance-id component))))
(show-shape (:id component) 0 (:objects component)))))))))

View file

@ -24,3 +24,11 @@
(update :pages conj-if-not-exists (:id page))
(update :pages-index assoc (:id page) page))))
(defn pages-seq
[file-data]
(vals (:pages-index file-data)))
(defn update-page
[file-data page-id f]
(update-in file-data [:pages-index page-id] f))

View file

@ -6,8 +6,13 @@
(ns app.common.types.shape
(:require
[app.common.colors :as clr]
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.pages.common :refer [default-color]]
[app.common.spec :as us]
[app.common.types.color :as ctc]
[app.common.types.shape.blur :as ctsb]
@ -16,6 +21,7 @@
[app.common.types.shape.layout :as ctsl]
[app.common.types.shape.radius :as ctsr]
[app.common.types.shape.shadow :as ctss]
[app.common.uuid :as uuid]
[clojure.set :as set]
[clojure.spec.alpha :as s]))
@ -316,3 +322,155 @@
(s/and (s/multi-spec shape-spec :type)
#(contains? % :type)
#(contains? % :name)))
;; --- Initialization
(def default-shape-attrs
{})
(def default-frame-attrs
{:frame-id uuid/zero
:fills [{:fill-color clr/white
:fill-opacity 1}]
:strokes []
:shapes []
:hide-fill-on-export false})
(def ^:private minimal-shapes
[{:type :rect
:name "Rect-1"
:fills [{:fill-color default-color
:fill-opacity 1}]
:strokes []
:rx 0
:ry 0}
{:type :image
:rx 0
:ry 0
:fills []
:strokes []}
{:type :circle
:name "Circle-1"
:fills [{:fill-color default-color
:fill-opacity 1}]
:strokes []}
{:type :path
:name "Path-1"
:fills []
:strokes [{:stroke-style :solid
:stroke-alignment :center
:stroke-width 2
:stroke-color clr/black
:stroke-opacity 1}]}
{:type :frame
:name "Board-1"
:fills [{:fill-color clr/white
:fill-opacity 1}]
:strokes []
:stroke-style :none
:stroke-alignment :center
:stroke-width 0
:stroke-color clr/black
:stroke-opacity 0
:rx 0
:ry 0}
{:type :text
:name "Text-1"
:content nil}
{:type :svg-raw}])
(def empty-selrect
{:x 0 :y 0
:x1 0 :y1 0
:x2 0.01 :y2 0.01
:width 0.01 :height 0.01})
(defn make-minimal-shape
[type]
(let [type (cond (= type :curve) :path
:else type)
shape (d/seek #(= type (:type %)) minimal-shapes)]
(when-not shape
(ex/raise :type :assertion
:code :shape-type-not-implemented
:context {:type type}))
(cond-> shape
:always
(assoc :id (uuid/next))
(not= :path (:type shape))
(assoc :x 0
:y 0
:width 0.01
:height 0.01
:selrect {:x 0
:y 0
:x1 0
:y1 0
:x2 0.01
:y2 0.01
:width 0.01
:height 0.01}))))
(defn make-minimal-group
[frame-id rect group-name]
{:id (uuid/next)
:type :group
:name group-name
:shapes []
:frame-id frame-id
:x (:x rect)
:y (:y rect)
:width (:width rect)
:height (:height rect)})
(defn setup-rect-selrect
"Initializes the selrect and points for a shape."
[shape]
(let [selrect (gsh/rect->selrect shape)
points (gsh/rect->points shape)]
(-> shape
(assoc :selrect selrect
:points points))))
(defn- setup-rect
"A specialized function for setup rect-like shapes."
[shape {:keys [x y width height]}]
(-> shape
(assoc :x x :y y :width width :height height)
(setup-rect-selrect)))
(defn- setup-image
[{:keys [metadata] :as shape} props]
(-> (setup-rect shape props)
(assoc
:proportion (/ (:width metadata)
(:height metadata))
:proportion-lock true)))
(defn setup-shape
"A function that initializes the geometric data of
the shape. The props must have :x :y :width :height."
([props]
(setup-shape {:type :rect} props))
([shape props]
(case (:type shape)
:image (setup-image shape props)
(setup-rect shape props))))
(defn make-shape
"Make a non group shape, ready to use."
[type geom-props attrs]
(-> (make-minimal-shape type)
(setup-shape geom-props)
(merge attrs)))

View file

@ -65,6 +65,11 @@
(update container :objects update-objects parent-id)))
(defn set-shape
"Replace a shape in the tree with a new one"
[container shape]
(assoc-in container [:objects (:id shape)] shape))
(defn get-frames
"Retrieves all frame objects as vector"
[objects]
@ -149,7 +154,6 @@
[base index-base-a index-base-b]))
(defn is-shape-over-shape?
[objects base-shape-id over-shape-id {:keys [top-frames?]}]

View file

@ -10,7 +10,7 @@
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.math :as mth :refer [close?]]
[app.common.pages :refer [make-minimal-shape]]
[app.common.types.shape :as cts]
[clojure.test :as t]))
(def default-path
@ -41,7 +41,7 @@
(defn create-test-shape
([type] (create-test-shape type {}))
([type params]
(-> (make-minimal-shape type)
(-> (cts/make-minimal-shape type)
(merge params)
(cond->
(= type :path) (add-path-data)

View file

@ -9,12 +9,13 @@
[clojure.test :as t]
[clojure.pprint :refer [pprint]]
[app.common.pages :as cp]
[app.common.types.file :as ctf]
[app.common.uuid :as uuid]))
(t/deftest process-change-set-option
(let [file-id (uuid/custom 2 2)
page-id (uuid/custom 1 1)
data (cp/make-file-data file-id page-id)]
data (ctf/make-file-data file-id page-id)]
(t/testing "Sets option single"
(let [chg {:type :set-option
:page-id page-id
@ -80,7 +81,7 @@
(t/deftest process-change-add-obj
(let [file-id (uuid/custom 2 2)
page-id (uuid/custom 1 1)
data (cp/make-file-data file-id page-id)
data (ctf/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)]
@ -134,7 +135,7 @@
(t/deftest process-change-mod-obj
(let [file-id (uuid/custom 2 2)
page-id (uuid/custom 1 1)
data (cp/make-file-data file-id page-id)]
data (ctf/make-file-data file-id page-id)]
(t/testing "simple mod-obj"
(let [chg {:type :mod-obj
:page-id page-id
@ -161,7 +162,7 @@
(let [file-id (uuid/custom 2 2)
page-id (uuid/custom 1 1)
id (uuid/custom 2 1)
data (cp/make-file-data file-id page-id)
data (ctf/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]
@ -205,7 +206,7 @@
file-id (uuid/custom 2 2)
page-id (uuid/custom 1 1)
data (cp/make-file-data file-id page-id)
data (ctf/make-file-data file-id page-id)
data (update-in data [:pages-index page-id :objects]
#(-> %
@ -449,7 +450,7 @@
:obj {:type :rect
:name "Shape 3"}}
]
data (cp/make-file-data file-id page-id)
data (ctf/make-file-data file-id page-id)
data (cp/process-changes data changes)]
(t/testing "preserve order on multiple shape mov 1"
@ -556,7 +557,7 @@
:parent-id group-1-id
:shapes [shape-1-id shape-2-id]}]
data (cp/make-file-data file-id page-id)
data (ctf/make-file-data file-id page-id)
data (cp/process-changes data changes)]
(t/testing "case 1"

View file

@ -0,0 +1,110 @@
;; 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) UXBOX Labs SL
(ns app.common.test-helpers.files
(:require
[app.common.geom.point :as gpt]
[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.uuid :as uuid]))
(def ^:private idmap (atom {}))
(defn reset-idmap! []
(reset! idmap {}))
(defn id
[label]
(get @idmap label))
(defn sample-file
([file-id page-id] (sample-file file-id page-id nil))
([file-id page-id props]
(merge {:id file-id
:name (get props :name "File1")
:data (ctf/make-file-data file-id page-id)}
props)))
(defn sample-shape
[file label type page-id props]
(ctf/update-file-data
file
(fn [file-data]
(let [frame-id (get props :frame-id uuid/zero)
parent-id (get props :parent-id uuid/zero)
shape (if (= type :group)
(cts/make-minimal-group frame-id
{:x 0 :y 0 :width 1 :height 1}
(get props :name "Group1"))
(cts/make-shape type
{:x 0 :y 0 :width 1 :height 1}
props))]
(swap! idmap assoc label (:id shape))
(ctpl/update-page file-data
page-id
#(ctst/add-shape (:id shape)
shape
%
frame-id
parent-id
0
true))))))
(defn sample-component
[file label page-id shape-id]
(ctf/update-file-data
file
(fn [file-data]
(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)
(:objects page)
(:id file))]
(swap! idmap assoc label (:id component-shape))
(-> file-data
(ctpl/update-page page-id
#(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))))))
(defn sample-instance
[file label page-id library component-id]
(ctf/update-file-data
file
(fn [file-data]
(let [[instance-shape instance-shapes]
(ctn/instantiate-component (ctpl/get-page file-data page-id)
(ctkl/get-component (:data library) component-id)
(:id library)
(gpt/point 0 0))]
(swap! idmap assoc label (:id instance-shape))
(-> file-data
(ctpl/update-page page-id
#(reduce (fn [page shape]
(ctst/add-shape (:id shape)
shape
page
uuid/zero
(:parent-id shape)
0
true))
%
instance-shapes)))))))

View file

@ -0,0 +1,79 @@
;; 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) UXBOX Labs SL
(ns app.common.types.file-test
(:require
[clojure.test :as t]
[app.common.geom.point :as gpt]
[app.common.types.components-list :as ctkl]
[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.uuid :as uuid]
[app.common.test-helpers.files :as thf]
[app.common.data :as d]
[app.common.pages.helpers :as cph]
[cuerdas.core :as str]
))
(t/use-fixtures :each
{:before thf/reset-idmap!})
(t/deftest test-absorb-assets
(let [library-id (uuid/custom 1 1)
library-page-id (uuid/custom 2 2)
file-id (uuid/custom 3 3)
file-page-id (uuid/custom 4 4)
library (-> (thf/sample-file library-id library-page-id {:is-shared true})
(thf/sample-shape :group1
:group
library-page-id
{:name "Group1"})
(thf/sample-shape :shape1
:rect
library-page-id
{:name "Rect1"
:parent-id (thf/id :group1)})
(thf/sample-component :component1
library-page-id
(thf/id :group1)))
file (-> (thf/sample-file file-id file-page-id)
(thf/sample-instance :instance1
file-page-id
library
(thf/id :component1)))
absorbed-file (ctf/update-file-data
file
#(ctf/absorb-assets % (:data library)))]
(println "\n===== library")
(ctf/dump-tree (:data library)
library-page-id
{}
true)
(println "\n===== file")
(ctf/dump-tree (:data file)
file-page-id
{library-id {:id library-id
:name "Library 1"
:data library}}
true)
(println "\n===== absorbed file")
(ctf/dump-tree (:data absorbed-file)
file-page-id
{}
true)
(t/is (= library-id (:id library)))
(t/is (= file-id (:id absorbed-file)))))

View file

@ -4,20 +4,20 @@
;;
;; Copyright (c) UXBOX Labs SL
(ns app.common.spec-interactions-test
(ns app.common.types.shape.spec-interactions-test
(:require
[clojure.test :as t]
[clojure.pprint :refer [pprint]]
[app.common.exceptions :as ex]
[app.common.pages.init :as cpi]
[app.common.types.shape :as cts]
[app.common.types.shape.interactions :as ctsi]
[app.common.uuid :as uuid]
[app.common.geom.point :as gpt]))
(t/deftest set-event-type
(let [interaction ctsi/default-interaction
shape (cpi/make-minimal-shape :rect)
frame (cpi/make-minimal-shape :frame)]
shape (cts/make-minimal-shape :rect)
frame (cts/make-minimal-shape :frame)]
(t/testing "Set event type unchanged"
(let [new-interaction
@ -148,7 +148,7 @@
(t/deftest option-delay
(let [frame (cpi/make-minimal-shape :frame)
(let [frame (cts/make-minimal-shape :frame)
i1 ctsi/default-interaction
i2 (ctsi/set-event-type i1 :after-delay frame)]
@ -211,10 +211,10 @@
(t/deftest option-overlay-opts
(let [base-frame (-> (cpi/make-minimal-shape :frame)
(let [base-frame (-> (cts/make-minimal-shape :frame)
(assoc-in [:selrect :width] 100)
(assoc-in [:selrect :height] 100))
overlay-frame (-> (cpi/make-minimal-shape :frame)
overlay-frame (-> (cts/make-minimal-shape :frame)
(assoc-in [:selrect :width] 30)
(assoc-in [:selrect :height] 20))
objects {(:id base-frame) base-frame
@ -542,12 +542,12 @@
(t/deftest remap-interactions
(let [frame1 (cpi/make-minimal-shape :frame)
frame2 (cpi/make-minimal-shape :frame)
frame3 (cpi/make-minimal-shape :frame)
frame4 (cpi/make-minimal-shape :frame)
frame5 (cpi/make-minimal-shape :frame)
frame6 (cpi/make-minimal-shape :frame)
(let [frame1 (cts/make-minimal-shape :frame)
frame2 (cts/make-minimal-shape :frame)
frame3 (cts/make-minimal-shape :frame)
frame4 (cts/make-minimal-shape :frame)
frame5 (cts/make-minimal-shape :frame)
frame6 (cts/make-minimal-shape :frame)
objects {(:id frame3) frame3
(:id frame4) frame4

View file

@ -14,7 +14,6 @@
[app.common.geom.proportions :as gpr]
[app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[app.common.pages :as cp]
[app.common.pages.changes-builder :as pcb]
[app.common.pages.helpers :as cph]
[app.common.spec :as us]
@ -1591,7 +1590,7 @@
page-id (:current-page-id state)
frame-id (-> (wsh/lookup-page-objects state page-id)
(ctst/frame-id-by-position @ms/mouse-position))
shape (cp/setup-rect-selrect
shape (cts/setup-rect-selrect
{:id id
:type :text
:name "Text"
@ -1681,12 +1680,12 @@
(let [srect (gsh/selection-rect selected-objs)
frame-id (get-in objects [(first selected) :frame-id])
parent-id (get-in objects [(first selected) :parent-id])
shape (-> (cp/make-minimal-shape :frame)
shape (-> (cts/make-minimal-shape :frame)
(merge {:x (:x srect) :y (:y srect) :width (:width srect) :height (:height srect)})
(assoc :frame-id frame-id :parent-id parent-id)
(cond-> (not= frame-id uuid/zero)
(assoc :fills [] :hide-in-viewer true))
(cp/setup-rect-selrect))]
(cts/setup-rect-selrect))]
(rx/of
(dwu/start-undo-transaction)
(dwsh/add-shape shape)

View file

@ -7,15 +7,6 @@
(ns app.main.data.workspace.common
(:require
[app.common.logging :as log]
[app.common.pages :as cp]
[app.common.pages.changes-builder :as pcb]
[app.common.pages.helpers :as cph]
[app.common.spec :as us]
[app.common.types.page :as ctp]
[app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctt]
[app.common.types.shape.interactions :as ctsi]
[app.common.uuid :as uuid]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.undo :as dwu]
[app.main.worker :as uw]

View file

@ -7,7 +7,7 @@
(ns app.main.data.workspace.drawing
"Drawing interactions."
(:require
[app.common.pages :as cp]
[app.common.types.shape :as cts]
[app.common.uuid :as uuid]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.drawing.box :as box]
@ -91,7 +91,7 @@
(ptk/reify ::handle-drawing
ptk/UpdateEvent
(update [_ state]
(let [data (cp/make-minimal-shape type)]
(let [data (cts/make-minimal-shape type)]
(update-in state [:workspace-drawing :object] merge data)))
ptk/WatchEvent

View file

@ -9,8 +9,8 @@
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[app.common.pages :as cp]
[app.common.pages.helpers :as cph]
[app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctt]
[app.common.uuid :as uuid]
[app.main.data.workspace.drawing.common :as common]
@ -70,7 +70,7 @@
shape (get-in state [:workspace-drawing :object])
shape (-> shape
(cp/setup-shape {:x (:x initial)
(cts/setup-shape {:x (:x initial)
:y (:y initial)
:width 0.01
:height 0.01})

View file

@ -9,8 +9,9 @@
[app.common.geom.matrix :as gmt]
[app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[app.common.pages :as cp]
[app.common.pages.helpers :as cph]
[app.common.types.shape :as cts]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.undo :as dwu]
@ -55,7 +56,7 @@
(assoc :height 17 :width 4 :grow-type :auto-width)
click-draw?
(cp/setup-rect-selrect)
(cts/setup-rect-selrect)
:always
(-> (gsh/transform-shape)

View file

@ -8,9 +8,9 @@
(:require
[app.common.data :as d]
[app.common.geom.shapes :as gsh]
[app.common.pages :as cp]
[app.common.pages.changes-builder :as pcb]
[app.common.pages.helpers :as cph]
[app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctt]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.selection :as dws]
@ -75,8 +75,8 @@
(ctt/generate-unique-name base-name)))
selrect (gsh/selection-rect shapes)
group (-> (cp/make-minimal-group frame-id selrect gname)
(cp/setup-shape selrect)
group (-> (cts/make-minimal-group frame-id selrect gname)
(cts/setup-shape selrect)
(assoc :shapes (mapv :id shapes)
:parent-id parent-id
:frame-id frame-id

View file

@ -56,46 +56,6 @@
;; ---- Components and instances creation ----
(defn make-component-shape
"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]
(assert (nil? (:component-id shape)))
(assert (nil? (:component-file shape)))
(assert (nil? (:shape-ref shape)))
(let [;; Ensure that the component root is not an instance and
;; it's no longer tied to a frame.
update-new-shape (fn [new-shape _original-shape]
(cond-> new-shape
true
(-> (assoc :frame-id nil)
(dissoc :component-root?))
(nil? (:parent-id new-shape))
(dissoc :component-id
:component-file
:shape-ref)))
;; Make the original shape an instance of the new component.
;; If one of the original shape children already was a component
;; instance, maintain this instanceness untouched.
update-original-shape (fn [original-shape new-shape]
(cond-> original-shape
(nil? (:shape-ref original-shape))
(-> (assoc :shape-ref (:id new-shape))
(dissoc :touched))
(nil? (:parent-id new-shape))
(assoc :component-id (:id new-shape)
:component-file file-id
:component-root? true)
(some? (:parent-id new-shape))
(dissoc :component-root?)))]
(ctst/clone-object shape nil objects update-new-shape update-original-shape)))
(defn generate-add-component
"If there is exactly one id, and it's a group, use it as root. Otherwise,
create a group that contains all ids. Then, make a component with it,
@ -115,7 +75,7 @@
(dwg/prepare-create-group it objects page-id shapes name true))
[new-shape new-shapes updated-shapes]
(make-component-shape group objects file-id)
(ctn/make-component-shape group objects file-id)
changes (-> changes
(pcb/add-component (:id new-shape)

View file

@ -11,9 +11,9 @@
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.pages :as cp]
[app.common.pages.changes-builder :as pcb]
[app.common.spec :refer [max-safe-int min-safe-int]]
[app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctt]
[app.common.uuid :as uuid]
[app.main.data.workspace.changes :as dch]
@ -182,7 +182,7 @@
(assoc :svg-attrs attrs)
(assoc :svg-viewbox (-> (select-keys svg-data [:width :height])
(assoc :x offset-x :y offset-y)))
(cp/setup-rect-selrect))))
(cts/setup-rect-selrect))))
(defn create-svg-root [frame-id svg-data]
(let [{:keys [name x y width height offset-x offset-y]} svg-data]
@ -194,7 +194,7 @@
:height height
:x (+ x offset-x)
:y (+ y offset-y)}
(cp/setup-rect-selrect)
(cts/setup-rect-selrect)
(assoc :svg-attrs (-> (:attrs svg-data)
(dissoc :viewBox :xmlns)
(d/without-keys usvg/inheritable-props))))))
@ -214,7 +214,7 @@
(assoc :svg-attrs (d/without-keys attrs usvg/inheritable-props))
(assoc :svg-viewbox (-> (select-keys svg-data [:width :height])
(assoc :x offset-x :y offset-y)))
(cp/setup-rect-selrect))))
(cts/setup-rect-selrect))))
(defn create-path-shape [name frame-id svg-data {:keys [attrs] :as data}]
(when (and (contains? attrs :d) (seq (:d attrs)))

View file

@ -11,7 +11,7 @@
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.pages :as cp]
[app.common.types.shape :as cts]
[app.main.data.workspace :as dw]
[app.main.refs :as refs]
[app.main.store :as st]
@ -343,7 +343,7 @@
#(->> shapes
(map gsh/transform-shape)
(gsh/selection-rect)
(cp/setup-shape)))
(cts/setup-shape)))
on-resize
(fn [current-position _initial-position event]
(when (dom/left-mouse? event)
@ -371,7 +371,7 @@
#(->> shapes
(map gsh/transform-shape)
(gsh/selection-rect)
(cp/setup-shape)))]
(cts/setup-shape)))]
[:& controls-selection
{:shape shape

View file

@ -14,8 +14,8 @@
[app.common.geom.shapes.path :as gpa]
[app.common.logging :as log]
[app.common.media :as cm]
[app.common.pages :as cp]
[app.common.text :as ct]
[app.common.types.file :as ctf]
[app.common.uuid :as uuid]
[app.main.repo :as rp]
[app.util.http :as http]
@ -133,7 +133,7 @@
:name (:name context)
:is-shared (:shared context)
:project-id (:project-id context)
:data (-> cp/empty-file-data (assoc :id file-id))})))
:data (-> ctf/empty-file-data (assoc :id file-id))})))
(defn link-file-libraries
"Create a new file on the back-end"

View file

@ -8,8 +8,8 @@
(:require
[app.common.data :as d]
[app.common.logging :as l]
[app.common.pages.helpers :as cph]
[app.common.transit :as t]
[app.common.types.file :as ctf]
[app.common.uuid :as uuid]
[app.main.data.dashboard.shortcuts]
[app.main.data.viewer.shortcuts]
@ -211,77 +211,9 @@
([state show-ids] (dump-tree' state show-ids false))
([state show-ids show-touched]
(let [page-id (get state :current-page-id)
objects (get-in state [:workspace-data :pages-index page-id :objects])
components (get-in state [:workspace-data :components])
libraries (get state :workspace-libraries)
root (d/seek #(nil? (:parent-id %)) (vals objects))]
(letfn [(show-shape [shape-id level objects]
(let [shape (get objects shape-id)]
(println (str/pad (str (str/repeat " " level)
(:name shape)
(when (seq (:touched shape)) "*")
(when show-ids (str/format " <%s>" (:id shape))))
{:length 20
:type :right})
(show-component shape objects))
(when show-touched
(when (seq (:touched shape))
(println (str (str/repeat " " level)
" "
(str (:touched shape)))))
(when (:remote-synced? shape)
(println (str (str/repeat " " level)
" (remote-synced)"))))
(when (:shapes shape)
(dorun (for [shape-id (:shapes shape)]
(show-shape shape-id (inc level) objects))))))
(show-component [shape objects]
(if (nil? (:shape-ref shape))
""
(let [root-shape (cph/get-component-shape objects shape)
component-id (when root-shape (:component-id root-shape))
component-file-id (when root-shape (:component-file root-shape))
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])
(get components component-id)))
component-shape (when (and component (:shape-ref shape))
(get-in component [:objects (:shape-ref shape)]))]
(str/format " %s--> %s%s%s"
(cond (:component-root? shape) "#"
(:component-id shape) "@"
:else "-")
(when component-file (str/format "<%s> " (:name component-file)))
(or (:name component-shape) "?")
(if (or (:component-root? shape)
(nil? (:component-id shape))
true)
""
(let [component-id (:component-id shape)
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])
(get components component-id))]
(str/format " (%s%s)"
(when component-file (str/format "<%s> " (:name component-file)))
(:name component))))))))]
(println "[Page]")
(show-shape (:id root) 0 objects)
(dorun (for [component (vals components)]
(do
(println)
(println (str/format "[%s]" (:name component))
(when show-ids
(str/format " (main: %s/%s)"
(:main-instance-page component)
(:main-instance-id component))))
(show-shape (:id component) 0 (:objects component)))))))))
file-data (get state :workspace-data)
libraries (get state :workspace-libraries)]
(ctf/dump-tree file-data page-id libraries show-ids show-touched))))
(defn ^:export dump-tree
([] (dump-tree' @st/state))

View file

@ -3,7 +3,7 @@
[app.common.data :as d]
[app.common.geom.point :as gpt]
[app.common.pages.helpers :as cph]
[app.common.types.container :as ctc]
[app.common.types.container :as ctn]
[app.main.data.workspace :as dw]
[app.main.data.workspace.groups :as dwg]
[app.main.data.workspace.libraries :as dwl]
@ -521,7 +521,7 @@
;
(let [page (thp/current-page new-state)
shape1 (thp/get-shape new-state :shape1)
parent1 (ctc/get-shape page (:parent-id shape1))
parent1 (ctn/get-shape page (:parent-id shape1))
[[group shape1 shape2]
[c-group c-shape1 c-shape2]

View file

@ -4,7 +4,7 @@
[app.common.data :as d]
[app.common.geom.point :as gpt]
[app.common.pages.helpers :as cph]
[app.common.types.container :as ctc]
[app.common.types.container :as ctn]
[app.main.data.workspace :as dw]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.shapes :as dwsh]
@ -1353,7 +1353,7 @@
instance1 (thp/get-shape state :instance1)
instance2 (thp/get-shape state :instance2)
shape2 (ctc/get-shape (wsh/lookup-page state)
shape2 (ctn/get-shape (wsh/lookup-page state)
(first (:shapes instance2)))
update-fn1 (fn [shape]

View file

@ -8,7 +8,7 @@
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.pages.helpers :as cph]
[app.common.types.container :as ctc]
[app.common.types.container :as ctn]
[app.main.data.workspace :as dw]
[app.main.data.workspace.libraries-helpers :as dwlh]
[app.main.data.workspace.state-helpers :as wsh]
@ -60,7 +60,7 @@
verify that they are a well constructed instance tree."
[state root-inst-id]
(let [page (thp/current-page state)
root-inst (ctc/get-shape page root-inst-id)
root-inst (ctn/get-shape page root-inst-id)
shapes-inst (cph/get-children-with-self (:objects page)
root-inst-id)]
(is-instance-root (first shapes-inst))
@ -73,7 +73,7 @@
verify that they are not a component instance."
[state root-inst-id]
(let [page (thp/current-page state)
root-inst (ctc/get-shape page root-inst-id)
root-inst (ctn/get-shape page root-inst-id)
shapes-inst (cph/get-children-with-self (:objects page)
root-inst-id)]
(run! is-noninstance shapes-inst)
@ -85,7 +85,7 @@
the main component and all its shapes."
[state root-inst-id]
(let [page (thp/current-page state)
root-inst (ctc/get-shape page root-inst-id)
root-inst (ctn/get-shape page root-inst-id)
libs (wsh/get-libraries state)
component (cph/get-component libs (:component-id root-inst))
@ -103,7 +103,7 @@
(cph/get-component libs (:component-id component-shape))
main-shape
(ctc/get-shape component (:shape-ref shape))]
(ctn/get-shape component (:shape-ref shape))]
(t/is (some? main-shape))))]
@ -123,7 +123,7 @@
corresponding component shape missing."
[state root-inst-id]
(let [page (thp/current-page state)
root-inst (ctc/get-shape page root-inst-id)
root-inst (ctn/get-shape page root-inst-id)
libs (wsh/get-libraries state)
component (cph/get-component libs (:component-id root-inst))
@ -141,7 +141,7 @@
(cph/get-component libs (:component-id component-shape))
main-shape
(ctc/get-shape component (:shape-ref shape))]
(ctn/get-shape component (:shape-ref shape))]
(t/is (some? main-shape))))]

View file

@ -9,6 +9,7 @@
[app.common.geom.shapes :as gsh]
[app.common.pages :as cp]
[app.common.pages.helpers :as cph]
[app.common.types.shape :as cts]
[app.main.data.workspace :as dw]
[app.main.data.workspace.groups :as dwg]
[app.main.data.workspace.layout :as layout]
@ -69,9 +70,7 @@
([state label type props]
(let [page (current-page state)
frame (cph/get-frame (:objects page))
shape (-> (cp/make-minimal-shape type)
(cp/setup-shape {:x 0 :y 0 :width 1 :height 1})
(merge props))]
shape (cts/make-shape type {:x 0 :y 0 :width 1 :height 1} props)]
(swap! idmap assoc label (:id shape))
(update state :workspace-data
cp/process-changes

View file

@ -9,7 +9,6 @@
[app.common.uuid :as uuid]
[cljs.test :as t :include-macros true]
[cljs.pprint :refer [pprint]]
[app.common.pages.init :as init]
[app.common.file-builder :as fb]
[app.util.snap-data :as sd]))