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

🎉 Absorb colors and typographies

This commit is contained in:
Andrés Moya 2022-07-01 16:51:49 +02:00
parent 7da159d52a
commit 43e0b5cfa5
18 changed files with 644 additions and 223 deletions

View file

@ -17,10 +17,12 @@
[app.common.spec :as us]
[app.common.pages.changes-spec :as pcs]
[app.common.types.components-list :as ctkl]
[app.common.types.colors-list :as ctcl]
[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.types.shape-tree :as ctst]
[app.common.types.typographies-list :as ctyl]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Specific helpers
@ -309,7 +311,7 @@
(defmethod process-change :add-color
[data {:keys [color]}]
(update data :colors assoc (:id color) color))
(ctcl/add-color data color))
(defmethod process-change :mod-color
[data {:keys [color]}]
@ -375,7 +377,7 @@
(defmethod process-change :add-typography
[data {:keys [typography]}]
(update data :typographies assoc (:id typography) typography))
(ctyl/add-typography data typography))
(defmethod process-change :mod-typography
[data {:keys [typography]}]

View file

@ -205,21 +205,6 @@
([libraries library-id component-id]
(get-in libraries [library-id :data :components component-id])))
(defn is-main-of?
[shape-main shape-inst]
(and (:shape-ref shape-inst)
(or (= (:shape-ref shape-inst) (:id shape-main))
(= (:shape-ref shape-inst) (:shape-ref shape-main)))))
(defn is-main-instance?
[shape-id page-id component]
(and (= shape-id (:main-instance-id component))
(= page-id (:main-instance-page component))))
(defn get-component-root
[component]
(get-in component [:objects (:id component)]))
(defn get-component-shape
"Get the parent shape linked to a component for this shape, if any"
[objects shape]

View file

@ -15,6 +15,7 @@
[app.common.math :as mth]
[app.common.pages :as cp]
[app.common.pages.helpers :as cph]
[app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl]
[app.common.types.container :as ctn]
[app.common.types.file :as ctf]
@ -485,7 +486,7 @@
add-instance-grid
(fn [data components]
(let [position-seq (ctst/generate-shape-grid
(map cph/get-component-root components)
(map ctk/get-component-root components)
start-pos
grid-gap)]
(loop [data data

View file

@ -102,6 +102,12 @@
:fill-opacity opacity
:fill-color-gradient gradient)))))
(defn attach-fill-color
[shape position ref-id ref-file]
(-> shape
(assoc-in [:fills position :fill-color-ref-id] ref-id)
(assoc-in [:fills position :fill-color-ref-file] ref-file)))
(defn detach-fill-color
[shape position]
(-> shape
@ -127,6 +133,12 @@
:stroke-opacity opacity
:stroke-color-gradient gradient)))))
(defn attach-stroke-color
[shape position ref-id ref-file]
(-> shape
(assoc-in [:strokes position :stroke-color-ref-id] ref-id)
(assoc-in [:strokes position :stroke-color-ref-file] ref-file)))
(defn detach-stroke-color
[shape position]
(-> shape
@ -152,6 +164,12 @@
:opacity opacity
:gradient gradient)))))
(defn attach-shadow-color
[shape position ref-id ref-file]
(-> shape
(assoc-in [:shadow position :color :id] ref-id)
(assoc-in [:shadow position :color :file-id] ref-file)))
(defn detach-shadow-color
[shape position]
(-> shape
@ -176,6 +194,11 @@
:color color
:opacity opacity
:gradient gradient)))))
(defn attach-grid-color
[shape position ref-id ref-file]
(-> shape
(assoc-in [:grids position :params :color :id] ref-id)
(assoc-in [:grids position :params :color :file-id] ref-file)))
(defn detach-grid-color
[shape position]
@ -213,11 +236,87 @@
(= (:ref-file %) library-id))
all-colors)))
(defn uses-library-color?
"Check if the shape uses the given library color."
[shape library-id color]
(let [all-colors (get-all-colors shape)]
(some #(and (= (:ref-id %) (:id color))
(= (:ref-file %) library-id))
all-colors)))
(defn- process-shape-colors
"Execute an update function on all colors of a shape."
[shape func]
(let [process-fill (fn [shape [position fill]]
(func shape
position
(fill->shape-color fill)
set-fill-color
attach-fill-color
detach-fill-color))
process-stroke (fn [shape [position stroke]]
(func shape
position
(stroke->shape-color stroke)
set-stroke-color
attach-stroke-color
detach-stroke-color))
process-shadow (fn [shape [position shadow]]
(func shape
position
(shadow->shape-color shadow)
set-shadow-color
attach-shadow-color
detach-shadow-color))
process-grid (fn [shape [position grid]]
(func shape
position
(grid->shape-color grid)
set-grid-color
attach-grid-color
detach-grid-color))
process-text-node (fn [node]
(as-> node $
(reduce process-fill $ (d/enumerate (:fills $)))
(reduce process-stroke $ (d/enumerate (:strokes $)))))
process-text (fn [shape]
(let [content (:content shape)
new-content (txt/transform-nodes process-text-node content)]
(if (not= content new-content)
(assoc shape :content new-content)
shape)))]
(as-> shape $
(reduce process-fill $ (d/enumerate (:fills $)))
(reduce process-stroke $ (d/enumerate (:strokes $)))
(reduce process-shadow $ (d/enumerate (:shadow $)))
(reduce process-grid $ (d/enumerate (:grids $)))
(process-text $))))
(defn remap-colors
"Change the shape so that any use of the given color now points to
the given library."
[shape library-id color]
(let [remap-color (fn [shape position shape-color _ attach-fn _]
(if (= (:ref-id shape-color) (:id color))
(attach-fn shape
position
(:id color)
library-id)
shape))]
(process-shape-colors shape remap-color)))
(defn sync-shape-colors
"Look for usage of any color of the given library inside the shape,
and, in this case, copy the library color into the shape."
[shape library-id library-colors]
(let [sync-color (fn [shape position shape-color set-fn detach-fn]
(let [sync-color (fn [shape position shape-color set-fn _ detach-fn]
(if (= (:ref-file shape-color) library-id)
(let [library-color (get library-colors (:ref-id shape-color))]
(if (some? library-color)
@ -227,51 +326,7 @@
(:opacity library-color)
(:gradient library-color))
(detach-fn shape position)))
shape))
shape))]
sync-fill (fn [shape [position fill]]
(sync-color shape
position
(fill->shape-color fill)
set-fill-color
detach-fill-color))
(process-shape-colors shape sync-color)))
sync-stroke (fn [shape [position stroke]]
(sync-color shape
position
(stroke->shape-color stroke)
set-stroke-color
detach-stroke-color))
sync-shadow (fn [shape [position shadow]]
(sync-color shape
position
(shadow->shape-color shadow)
set-shadow-color
detach-shadow-color))
sync-grid (fn [shape [position grid]]
(sync-color shape
position
(grid->shape-color grid)
set-grid-color
detach-grid-color))
sync-text-node (fn [node]
(as-> node $
(reduce sync-fill $ (d/enumerate (:fills $)))
(reduce sync-stroke $ (d/enumerate (:strokes $)))))
sync-text (fn [shape]
(let [content (:content shape)
new-content (txt/transform-nodes sync-text-node content)]
(if (not= content new-content)
(assoc shape :content new-content)
shape)))]
(as-> shape $
(reduce sync-fill $ (d/enumerate (:fills $)))
(reduce sync-stroke $ (d/enumerate (:strokes $)))
(reduce sync-shadow $ (d/enumerate (:shadow $)))
(reduce sync-grid $ (d/enumerate (:grids $)))
(sync-text $))))

View file

@ -0,0 +1,26 @@
;; 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.colors-list
(:require
[app.common.data :as d]))
(defn colors-seq
[file-data]
(vals (:colors file-data)))
(defn add-color
[file-data color]
(update file-data :colors assoc (:id color) color))
(defn get-color
[file-data color-id]
(get-in file-data [:colors color-id]))
(defn update-color
[file-data color-id f]
(update-in file-data [:colors color-id] f))

View file

@ -7,7 +7,24 @@
(ns app.common.types.component)
(defn instance-of?
[shape component]
[shape file-id component]
(and (some? (:component-id shape))
(= (:component-id shape) (:id component))))
(some? (:component-file shape))
(= (:component-id shape) (:id component))
(= (:component-file shape) file-id)))
(defn is-main-of?
[shape-main shape-inst]
(and (:shape-ref shape-inst)
(or (= (:shape-ref shape-inst) (:id shape-main))
(= (:shape-ref shape-inst) (:shape-ref shape-main)))))
(defn is-main-instance?
[shape-id page-id component]
(and (= shape-id (:main-instance-id component))
(= page-id (:main-instance-page component))))
(defn get-component-root
[component]
(get-in component [:objects (:id component)]))

View file

@ -18,7 +18,8 @@
(s/def ::path (s/nilable string?))
(s/def ::container
(s/keys :req-un [::id ::name ::ctst/objects]
;; (s/keys :req-un [::id ::name ::ctst/objects]
(s/keys :req-un [::id ::name]
:opt-un [::type ::path]))
(defn make-container

View file

@ -13,12 +13,15 @@
[app.common.pages.helpers :as cph]
[app.common.spec :as us]
[app.common.types.color :as ctc]
[app.common.types.colors-list :as ctcl]
[app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl]
[app.common.types.container :as ctn]
[app.common.types.page :as ctp]
[app.common.types.pages-list :as ctpl]
[app.common.types.shape-tree :as ctst]
[app.common.types.typography :as cty]
[app.common.types.typographies-list :as ctyl]
[app.common.uuid :as uuid]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]))
@ -91,6 +94,10 @@
;; Helpers
(defn file-data
[file]
(:data file))
(defn update-file-data
[file f]
(update file :data f))
@ -108,19 +115,51 @@
(ctpl/update-page file-data (:id container) f)
(ctkl/update-component file-data (:id container) f)))
(defn find-instances
"Find all uses of a component in a file (may be in pages or in the components
of the local library).
Returns a vector [[container shapes] [container shapes]...]"
[file-data component]
(let [find-instances-in-container
(fn [container component]
(let [instances (filter #(ctk/instance-of? % component) (ctn/shapes-seq container))]
(when (d/not-empty? instances)
[[container instances]])))]
;; Asset helpers
(mapcat #(find-instances-in-container % component) (containers-seq file-data))))
(defmulti uses-asset?
"Checks if a shape uses the given asset."
(fn [asset-type _ _ _] asset-type))
(defmethod uses-asset? :component
[_ shape library-id component]
(ctk/instance-of? shape library-id component))
(defmethod uses-asset? :color
[_ shape library-id color]
(ctc/uses-library-color? shape library-id color))
(defmethod uses-asset? :typography
[_ shape library-id typography]
(cty/uses-library-typography? shape library-id typography))
(defn find-asset-type-usages
"Find all usages of an asset in a file (may be in pages or in the components
of the local library).
Returns a list ((asset ((container shapes) (container shapes)...))...)"
[file-data library-data asset-type]
(let [assets-seq (case asset-type
:component (ctkl/components-seq library-data)
:color (ctcl/colors-seq library-data)
:typography (ctyl/typographies-seq library-data))
find-usages-in-container
(fn [container asset]
(let [instances (filter #(uses-asset? asset-type % (:id library-data) asset)
(ctn/shapes-seq container))]
(when (d/not-empty? instances)
[[container instances]])))
find-asset-usages
(fn [file-data library-id asset-type asset]
(mapcat #(find-usages-in-container % asset) (containers-seq file-data)))]
(mapcat (fn [asset]
(let [instances (find-asset-usages file-data (:id library-data) asset-type asset)]
(when (d/not-empty? instances)
[[asset instances]])))
assets-seq)))
(defn get-or-add-library-page
[file-data grid-gap]
@ -276,95 +315,20 @@
"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 [; Build a list of all components in the library used in the file
; The list is in the form [[component [[container shapes] [container shapes]...]]...]
used-components ; A vector of pair [component instances], where instances is non-empty
(mapcat (fn [component]
(let [instances (find-instances file-data component)]
(when (d/not-empty? instances)
[[component instances]])))
(ctkl/components-seq library-data))]
(let [used-components (find-asset-type-usages file-data library-data :component)
used-colors (find-asset-type-usages file-data library-data :color)
used-typographies (find-asset-type-usages file-data library-data :typography)]
(if (empty? used-components)
file-data
(let [; Search for the library page. If not exists, create it.
[file-data page-id start-pos]
(get-or-add-library-page file-data)
(cond-> file-data
(d/not-empty? used-components)
(absorb-components library-data used-components)
absorb-component
(fn [file-data [component instances] position]
(let [page (ctpl/get-page file-data page-id)
(d/not-empty? used-colors)
(absorb-colors library-data used-colors)
; Make a new main instance for the component
[main-instance-shape main-instance-shapes]
(ctn/instantiate-component page
component
(:id file-data)
position)
(d/not-empty? used-typographies)
(absorb-typographies library-data used-typographies))))
; Add all shapes of the main instance to the library page
add-main-instance-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
main-instance-shapes))
; Copy the component in the file local library
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))))
; Change all existing instances to point to the local file
redirect-instances
(fn [file-data [container shapes]]
(let [redirect-instance #(assoc % :component-file (:id file-data))]
(update-container file-data
container
#(reduce (fn [container shape]
(ctn/update-shape container
(:id shape)
redirect-instance))
%
shapes))))]
(as-> file-data $
(ctpl/update-page $ page-id add-main-instance-shapes)
(copy-component $)
(reduce redirect-instances $ instances))))
; Absorb all used components into the local library. Position
; the main instances in a grid in the library page.
add-component-grid
(fn [data used-components]
(let [position-seq (ctst/generate-shape-grid
(map #(cph/get-component-root (first %)) used-components)
start-pos
50)]
(loop [data data
components-seq (seq used-components)
position-seq position-seq]
(let [used-component (first components-seq)
position (first position-seq)]
(if (nil? used-component)
data
(recur (absorb-component data used-component position)
(rest components-seq)
(rest position-seq)))))))]
(add-component-grid file-data (sort-by #(:name (first %)) used-components))))))
;; Debug helpers

View file

@ -0,0 +1,26 @@
;; 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.typographies-list
(:require
[app.common.data :as d]))
(defn typographies-seq
[file-data]
(vals (:typographies file-data)))
(defn add-typography
[file-data typography]
(update file-data :typographies assoc (:id typography) typography))
(defn get-typography
[file-data typography-id]
(get-in file-data [:typographies typography-id]))
(defn update-typography
[file-data typography-id f]
(update-in file-data [:typographies typography-id] f))

View file

@ -6,7 +6,8 @@
(ns app.common.types.typography
(:require
[clojure.spec.alpha :as s]))
[app.common.text :as txt]
[clojure.spec.alpha :as s]))
(s/def ::id uuid?)
(s/def ::name string?)
@ -35,4 +36,37 @@
::text-transform]
:opt-un [::path]))
(defn uses-library-typographies?
"Check if the shape uses any typography in the given library."
[shape library-id]
(and (= (:type shape) :text)
(->> shape
:content
;; Check if any node in the content has a reference for the library
(txt/node-seq
#(and (some? (:typography-ref-id %))
(= (:typography-ref-file %) library-id))))))
(defn uses-library-typography?
"Check if the shape uses the given library typography."
[shape library-id typography]
(and (= (:type shape) :text)
(->> shape
:content
;; Check if any node in the content has a reference for the library
(txt/node-seq
#(and (= (:typography-ref-id %) (:id typography))
(= (:typography-ref-file %) library-id))))))
(defn remap-typographies
"Change the shape so that any use of the given typography now points to
the given library."
[shape library-id typography]
(let [remap-typography #(assoc % :typography-ref-file library-id)]
(update shape :content
(fn [content]
(txt/transform-nodes #(= (:typography-ref-id %) (:id typography))
remap-typography
content)))))

View file

@ -0,0 +1,150 @@
(ns app.common.test-helpers.components
(:require
[cljs.test :as t :include-macros true]
[cljs.pprint :refer [pprint]]
[app.common.pages.helpers :as cph]
[app.common.types.component :as ctk]
[app.common.types.container :as ctn]))
;; ---- Helpers to manage libraries and synchronization
(defn check-instance-root
[shape]
(t/is (some? (:shape-ref shape)))
(t/is (some? (:component-id shape)))
(t/is (= (:component-root? shape) true)))
(defn check-instance-subroot
[shape]
(t/is (some? (:shape-ref shape)))
(t/is (some? (:component-id shape)))
(t/is (nil? (:component-root? shape))))
(defn check-instance-child
[shape]
(t/is (some? (:shape-ref shape)))
(t/is (nil? (:component-id shape)))
(t/is (nil? (:component-file shape)))
(t/is (nil? (:component-root? shape))))
(defn check-instance-inner
[shape]
(if (some? (:component-id shape))
(check-instance-subroot shape)
(check-instance-child shape)))
(defn check-noninstance
[shape]
(t/is (nil? (:shape-ref shape)))
(t/is (nil? (:component-id shape)))
(t/is (nil? (:component-file shape)))
(t/is (nil? (:component-root? shape)))
(t/is (nil? (:remote-synced? shape)))
(t/is (nil? (:touched shape))))
(defn check-from-file
[shape file]
(t/is (= (:component-file shape)
(:id file))))
(defn resolve-instance
"Get the shape with the given id and all its children, and
verify that they are a well constructed instance tree."
[page root-inst-id]
(let [root-inst (ctn/get-shape page root-inst-id)
shapes-inst (cph/get-children-with-self (:objects page)
root-inst-id)]
(check-instance-root (first shapes-inst))
(run! check-instance-inner (rest shapes-inst))
shapes-inst))
(defn resolve-noninstance
"Get the shape with the given id and all its children, and
verify that they are not a component instance."
[page root-inst-id]
(let [root-inst (ctn/get-shape page root-inst-id)
shapes-inst (cph/get-children-with-self (:objects page)
root-inst-id)]
(run! check-noninstance shapes-inst)
shapes-inst))
(defn resolve-instance-and-main
"Get the shape with the given id and all its children, and also
the main component and all its shapes."
[page root-inst-id libraries]
(let [root-inst (ctn/get-shape page root-inst-id)
component (cph/get-component libraries (:component-id root-inst))
shapes-inst (cph/get-children-with-self (:objects page) root-inst-id)
shapes-main (cph/get-children-with-self (:objects component) (:shape-ref root-inst))
unique-refs (into #{} (map :shape-ref) shapes-inst)
main-exists? (fn [shape]
(let [component-shape
(cph/get-component-shape (:objects page) shape)
component
(cph/get-component libraries (:component-id component-shape))
main-shape
(ctn/get-shape component (:shape-ref shape))]
(t/is (some? main-shape))))]
;; Validate that the instance tree is well constructed
(check-instance-root (first shapes-inst))
(run! check-instance-inner (rest shapes-inst))
(t/is (= (count shapes-inst)
(count shapes-main)
(count unique-refs)))
(run! main-exists? shapes-inst)
[shapes-inst shapes-main component]))
(defn resolve-instance-and-main-allow-dangling
"Get the shape with the given id and all its children, and also
the main component and all its shapes. Allows shapes with the
corresponding component shape missing."
[page root-inst-id libraries]
(let [root-inst (ctn/get-shape page root-inst-id)
component (cph/get-component libraries (:component-id root-inst))
shapes-inst (cph/get-children-with-self (:objects page) root-inst-id)
shapes-main (cph/get-children-with-self (:objects component) (:shape-ref root-inst))
unique-refs (into #{} (map :shape-ref) shapes-inst)
main-exists? (fn [shape]
(let [component-shape
(cph/get-component-shape (:objects page) shape)
component
(cph/get-component libraries (:component-id component-shape))
main-shape
(ctn/get-shape component (:shape-ref shape))]
(t/is (some? main-shape))))]
;; Validate that the instance tree is well constructed
(check-instance-root (first shapes-inst))
[shapes-inst shapes-main component]))
(defn resolve-component
"Get the component with the given id and all its shapes."
[page component-id libraries]
(let [component (cph/get-component libraries component-id)
root-main (ctk/get-component-root component)
shapes-main (cph/get-children-with-self (:objects component) (:id root-main))]
;; Validate that the component tree is well constructed
(run! check-noninstance shapes-main)
[shapes-main component]))

View file

@ -6,14 +6,16 @@
(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]))
[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]))
(def ^:private idmap (atom {}))
@ -108,3 +110,38 @@
%
instance-shapes)))))))
(defn sample-color
[file label props]
(ctf/update-file-data
file
(fn [file-data]
(let [id (uuid/next)
props (merge {:id id
:name "Color-1"
:color "#000000"
:opacity 1}
props)]
(swap! idmap assoc label id)
(ctcl/add-color file-data props)))))
(defn sample-typography
[file label props]
(ctf/update-file-data
file
(fn [file-data]
(let [id (uuid/next)
props (merge {:id id
:name "Typography-1"
:font-id "sourcesanspro"
:font-family "sourcesanspro"
:font-size "14"
:font-style "normal"
:font-variant-id "regular"
:font-weight "400"
:line-height "1.2"
:letter-spacing "0"
:text-transform "none"}
props)]
(swap! idmap assoc label id)
(ctyl/add-typography file-data props)))))

View file

@ -6,25 +6,30 @@
(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]
))
;; Uncomment to debug
;; [clojure.pprint :refer [pprint]]
;; [cuerdas.core :as str]
[clojure.test :as t]
[app.common.data :as d]
[app.common.geom.point :as gpt]
[app.common.text :as txt]
[app.common.types.colors-list :as ctcl]
[app.common.types.component :as ctk]
[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]
[app.common.test-helpers.files :as thf]
[app.common.test-helpers.components :as thk]))
(t/use-fixtures :each
{:before thf/reset-idmap!})
(t/deftest test-absorb-assets
(t/deftest test-absorb-components
(let [library-id (uuid/custom 1 1)
library-page-id (uuid/custom 2 2)
file-id (uuid/custom 3 3)
@ -52,7 +57,26 @@
absorbed-file (ctf/update-file-data
file
#(ctf/absorb-assets % (:data library)))]
#(ctf/absorb-assets % (:data library)))
pages (ctpl/pages-seq (ctf/file-data absorbed-file))
components (ctkl/components-seq (ctf/file-data absorbed-file))
shapes-1 (ctn/shapes-seq (first pages))
shapes-2 (ctn/shapes-seq (second pages))
[[p-group p-shape] [c-group1 c-shape1] component1]
(thk/resolve-instance-and-main
(first pages)
(:id (second shapes-1))
{file-id absorbed-file})
[[lp-group lp-shape] [c-group2 c-shape2] component2]
(thk/resolve-instance-and-main
(second pages)
(:id (second shapes-2))
{file-id absorbed-file})]
;; Uncomment to debug
;; (println "\n===== library")
;; (ctf/dump-tree (:data library)
@ -67,11 +91,118 @@
;; true)
;; (println "\n===== absorbed file")
;; (println (str "\n<" (:name (first pages)) ">"))
;; (ctf/dump-tree (:data absorbed-file)
;; file-page-id
;; {}
;; true)
;; (:id (first pages))
;; {file-id absorbed-file}
;; false)
;; (println (str "\n<" (:name (second pages)) ">"))
;; (ctf/dump-tree (:data absorbed-file)
;; (:id (second pages))
;; {file-id absorbed-file}
;; false)
(t/is (= library-id (:id library)))
(t/is (= file-id (:id absorbed-file)))))
(t/is (= (count pages) 2))
(t/is (= (:name (first pages)) "Page-1"))
(t/is (= (:name (second pages)) "Library page"))
(t/is (= (count components) 1))
(t/is (= (:name p-group) "Group1"))
(t/is (ctk/instance-of? p-group file-id component1))
(t/is (not (ctk/is-main-instance? (:id p-group) file-page-id component1)))
(t/is (ctk/is-main-of? c-group1 p-group))
(t/is (= (:name p-shape) "Rect1"))
(t/is (ctk/is-main-of? c-shape1 p-shape))))
(t/deftest test-absorb-colors
(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-color :color1 {:name "Test color"
:color "#abcdef"}))
file (-> (thf/sample-file file-id file-page-id)
(thf/sample-shape :shape1
:rect
file-page-id
{:name "Rect1"
:fills [{:fill-color "#abcdef"
:fill-opacity 1
:fill-color-ref-id (thf/id :color1)
:fill-color-ref-file library-id}]}))
absorbed-file (ctf/update-file-data
file
#(ctf/absorb-assets % (:data library)))
colors (ctcl/colors-seq (ctf/file-data absorbed-file))
page (ctpl/get-page (ctf/file-data absorbed-file) file-page-id)
shape1 (ctn/get-shape page (thf/id :shape1))
fill (first (:fills shape1))]
(t/is (= (count colors) 1))
(t/is (= (:id (first colors)) (thf/id :color1)))
(t/is (= (:name (first colors)) "Test color"))
(t/is (= (:color (first colors)) "#abcdef"))
(t/is (= (:fill-color fill) "#abcdef"))
(t/is (= (:fill-color-ref-id fill) (thf/id :color1)))
(t/is (= (:fill-color-ref-file fill) file-id))))
(t/deftest test-absorb-typographies
(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-typography :typography1 {:name "Test typography"}))
file (-> (thf/sample-file file-id file-page-id)
(thf/sample-shape :shape1
:text
file-page-id
{:name "Text1"
:content {:type "root"
:children [{:type "paragraph-set"
:children [{:type "paragraph"
:key "67uep"
:children [{:text "Example text"
:typography-ref-id (thf/id :typography1)
:typography-ref-file library-id
:line-height "1.2"
:font-style "normal"
:text-transform "none"
:text-align "left"
:font-id "sourcesanspro"
:font-family "sourcesanspro"
:font-size "14"
:font-weight "400"
:font-variant-id "regular"
:text-decoration "none"
:letter-spacing "0"
:fills [{:fill-color "#000000"
:fill-opacity 1}]}]
}]}]}}))
absorbed-file (ctf/update-file-data
file
#(ctf/absorb-assets % (:data library)))
typographies (ctyl/typographies-seq (ctf/file-data absorbed-file))
page (ctpl/get-page (ctf/file-data absorbed-file) file-page-id)
shape1 (ctn/get-shape page (thf/id :shape1))
text-node (d/seek #(some? (:text %)) (txt/node-seq (:content shape1)))]
(t/is (= (count typographies) 1))
(t/is (= (:id (first typographies)) (thf/id :typography1)))
(t/is (= (:name (first typographies)) "Test typography"))
(t/is (= (:typography-ref-id text-node) (thf/id :typography1)))
(t/is (= (:typography-ref-file text-node) file-id))))

View file

@ -16,8 +16,10 @@
[app.common.spec :as us]
[app.common.text :as txt]
[app.common.types.color :as ctc]
[app.common.types.component :as ctk]
[app.common.types.container :as ctn]
[app.common.types.shape-tree :as ctst]
[app.common.types.typography :as cty]
[app.main.data.workspace.groups :as dwg]
[app.main.data.workspace.state-helpers :as wsh]
[cljs.spec.alpha :as s]
@ -94,7 +96,7 @@
(let [position (gpt/add (gpt/point (:x main-instance-shape) (:y main-instance-shape))
(gpt/point (+ (:width main-instance-shape) 50) 0))
component-root (cph/get-component-root component)
component-root (ctk/get-component-root component)
[new-component-shape new-component-shapes _]
(ctst/clone-object component-root
@ -243,13 +245,7 @@
(defmethod uses-assets? :typographies
[_ shape library-id _]
(and (= (:type shape) :text)
(->> shape
:content
;; Check if any node in the content has a reference for the library
(txt/node-seq
#(and (some? (:typography-ref-id %))
(= (:typography-ref-file %) library-id))))))
(cty/uses-library-typographies? shape library-id))
(defmulti generate-sync-shape
"Generate changes to synchronize one shape from all assets of the given type
@ -433,7 +429,7 @@
root-inst shape-inst
root-main (when component
(cph/get-component-root component))]
(ctk/get-component-root component))]
(if component
(generate-sync-shape-direct-recursive changes
@ -557,7 +553,7 @@
initial-root? (:component-root? shape-inst)
root-inst shape-inst
root-main (cph/get-component-root component)]
root-main (ctk/get-component-root component)]
(if component
(generate-sync-shape-inverse-recursive changes
@ -691,13 +687,13 @@
(reduce only-inst-cb changes children-inst)
:else
(if (cph/is-main-of? child-main child-inst)
(if (ctk/is-main-of? child-main child-inst)
(recur (next children-inst)
(next children-main)
(both-cb changes child-inst child-main))
(let [child-inst' (d/seek #(cph/is-main-of? child-main %) children-inst)
child-main' (d/seek #(cph/is-main-of? % child-inst) children-main)]
(let [child-inst' (d/seek #(ctk/is-main-of? child-main %) children-inst)
child-main' (d/seek #(ctk/is-main-of? % child-inst) children-main)]
(cond
(nil? child-inst')
(recur children-inst
@ -726,7 +722,7 @@
[changes component-shape index component container root-instance root-main omit-touched? set-remote-synced?]
(log/info :msg (str "ADD [P] " (:name component-shape)))
(let [component-parent-shape (ctn/get-shape component (:parent-id component-shape))
parent-shape (d/seek #(cph/is-main-of? component-parent-shape %)
parent-shape (d/seek #(ctk/is-main-of? component-parent-shape %)
(cph/get-children-with-self (:objects container)
(:id root-instance)))
all-parents (into [(:id parent-shape)]
@ -794,7 +790,7 @@
[changes shape index component page root-instance root-main]
(log/info :msg (str "ADD [C] " (:name shape)))
(let [parent-shape (ctn/get-shape page (:parent-id shape))
component-parent-shape (d/seek #(cph/is-main-of? % parent-shape)
component-parent-shape (d/seek #(ctk/is-main-of? % parent-shape)
(cph/get-children-with-self (:objects component)
(:id root-main)))
all-parents (into [(:id component-parent-shape)]

View file

@ -13,6 +13,7 @@
[app.common.pages.changes-builder :as pcb]
[app.common.pages.helpers :as cph]
[app.common.spec :as us]
[app.common.types.component :as ctk]
[app.common.types.page :as ctp]
[app.common.types.shape :as cts]
[app.common.types.shape.interactions :as ctsi]
@ -230,7 +231,7 @@
main-instance?
(when component
(cph/is-main-instance? (:id shape) (:id page) component))]
(ctk/is-main-instance? (:id shape) (:id page) component))]
(if main-instance?
(conj components (:component-id shape))

View file

@ -9,6 +9,7 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.pages.helpers :as cph]
[app.common.types.component :as ctk]
[app.common.uuid :as uuid]
[app.main.data.workspace :as dw]
[app.main.data.workspace.collapse :as dwc]
@ -106,7 +107,7 @@
component (when (and (:component-id item) (:component-file item))
(cph/get-component libraries (:component-file item) (:component-id item)))
main-instance? (when component
(cph/is-main-instance? (:id item) (:id page) component))
(ctk/is-main-instance? (:id item) (:id page) component))
toggle-collapse
(mf/use-fn

View file

@ -7,6 +7,7 @@
(ns app.main.ui.workspace.sidebar.options.menus.component
(:require
[app.common.pages.helpers :as cph]
[app.common.types.component :as ctk]
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]
[app.main.data.workspace.libraries :as dwl]
@ -35,7 +36,7 @@
component (when (and component-id library-id)
(cph/get-component libraries library-id component-id))
main-instance? (cph/is-main-instance? id current-page-id component)
main-instance? (ctk/is-main-instance? id current-page-id component)
on-menu-click
(mf/use-callback

View file

@ -2,16 +2,9 @@
(:require
[cljs.test :as t :include-macros true]
[cljs.pprint :refer [pprint]]
[beicon.core :as rx]
[potok.core :as ptk]
[app.common.uuid :as uuid]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.pages.helpers :as cph]
[app.common.types.component :as ctk]
[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]
[app.test-helpers.pages :as thp]))
;; ---- Helpers to manage libraries and synchronization
@ -156,7 +149,7 @@
(let [page (thp/current-page state)
libs (wsh/get-libraries state)
component (cph/get-component libs component-id)
root-main (cph/get-component-root component)
root-main (ctk/get-component-root component)
shapes-main (cph/get-children-with-self (:objects component) (:id root-main))]
;; Validate that the component tree is well constructed