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

View File

@ -205,21 +205,6 @@
([libraries library-id component-id] ([libraries library-id component-id]
(get-in libraries [library-id :data :components 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 (defn get-component-shape
"Get the parent shape linked to a component for this shape, if any" "Get the parent shape linked to a component for this shape, if any"
[objects shape] [objects shape]

View File

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

View File

@ -102,6 +102,12 @@
:fill-opacity opacity :fill-opacity opacity
:fill-color-gradient gradient))))) :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 (defn detach-fill-color
[shape position] [shape position]
(-> shape (-> shape
@ -127,6 +133,12 @@
:stroke-opacity opacity :stroke-opacity opacity
:stroke-color-gradient gradient))))) :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 (defn detach-stroke-color
[shape position] [shape position]
(-> shape (-> shape
@ -152,6 +164,12 @@
:opacity opacity :opacity opacity
:gradient gradient))))) :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 (defn detach-shadow-color
[shape position] [shape position]
(-> shape (-> shape
@ -176,6 +194,11 @@
:color color :color color
:opacity opacity :opacity opacity
:gradient gradient))))) :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 (defn detach-grid-color
[shape position] [shape position]
@ -213,11 +236,87 @@
(= (:ref-file %) library-id)) (= (:ref-file %) library-id))
all-colors))) 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 (defn sync-shape-colors
"Look for usage of any color of the given library inside the shape, "Look for usage of any color of the given library inside the shape,
and, in this case, copy the library color into the shape." and, in this case, copy the library color into the shape."
[shape library-id library-colors] [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) (if (= (:ref-file shape-color) library-id)
(let [library-color (get library-colors (:ref-id shape-color))] (let [library-color (get library-colors (:ref-id shape-color))]
(if (some? library-color) (if (some? library-color)
@ -227,51 +326,7 @@
(:opacity library-color) (:opacity library-color)
(:gradient library-color)) (:gradient library-color))
(detach-fn shape position))) (detach-fn shape position)))
shape)) shape))]
sync-fill (fn [shape [position fill]] (process-shape-colors shape sync-color)))
(sync-color shape
position
(fill->shape-color fill)
set-fill-color
detach-fill-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) (ns app.common.types.component)
(defn instance-of? (defn instance-of?
[shape component] [shape file-id component]
(and (some? (:component-id shape)) (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 ::path (s/nilable string?))
(s/def ::container (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])) :opt-un [::type ::path]))
(defn make-container (defn make-container

View File

@ -13,12 +13,15 @@
[app.common.pages.helpers :as cph] [app.common.pages.helpers :as cph]
[app.common.spec :as us] [app.common.spec :as us]
[app.common.types.color :as ctc] [app.common.types.color :as ctc]
[app.common.types.colors-list :as ctcl]
[app.common.types.component :as ctk] [app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl] [app.common.types.components-list :as ctkl]
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
[app.common.types.page :as ctp] [app.common.types.page :as ctp]
[app.common.types.pages-list :as ctpl] [app.common.types.pages-list :as ctpl]
[app.common.types.shape-tree :as ctst] [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] [app.common.uuid :as uuid]
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
[cuerdas.core :as str])) [cuerdas.core :as str]))
@ -91,6 +94,10 @@
;; Helpers ;; Helpers
(defn file-data
[file]
(:data file))
(defn update-file-data (defn update-file-data
[file f] [file f]
(update file :data f)) (update file :data f))
@ -108,19 +115,51 @@
(ctpl/update-page file-data (:id container) f) (ctpl/update-page file-data (:id container) f)
(ctkl/update-component file-data (:id container) f))) (ctkl/update-component file-data (:id container) f)))
(defn find-instances ;; Asset helpers
"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]])))]
(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 (defn get-or-add-library-page
[file-data grid-gap] [file-data grid-gap]
@ -276,95 +315,20 @@
"Find all assets of a library that are used in the file, and "Find all assets of a library that are used in the file, and
move them to the file local library." move them to the file local library."
[file-data library-data] [file-data library-data]
(let [; Build a list of all components in the library used in the file (let [used-components (find-asset-type-usages file-data library-data :component)
; The list is in the form [[component [[container shapes] [container shapes]...]]...] used-colors (find-asset-type-usages file-data library-data :color)
used-components ; A vector of pair [component instances], where instances is non-empty used-typographies (find-asset-type-usages file-data library-data :typography)]
(mapcat (fn [component]
(let [instances (find-instances file-data component)]
(when (d/not-empty? instances)
[[component instances]])))
(ctkl/components-seq library-data))]
(if (empty? used-components) (cond-> file-data
file-data (d/not-empty? used-components)
(let [; Search for the library page. If not exists, create it. (absorb-components library-data used-components)
[file-data page-id start-pos]
(get-or-add-library-page file-data)
absorb-component (d/not-empty? used-colors)
(fn [file-data [component instances] position] (absorb-colors library-data used-colors)
(let [page (ctpl/get-page file-data page-id)
; Make a new main instance for the component (d/not-empty? used-typographies)
[main-instance-shape main-instance-shapes] (absorb-typographies library-data used-typographies))))
(ctn/instantiate-component page
component
(:id file-data)
position)
; 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 ;; 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 (ns app.common.types.typography
(:require (:require
[clojure.spec.alpha :as s])) [app.common.text :as txt]
[clojure.spec.alpha :as s]))
(s/def ::id uuid?) (s/def ::id uuid?)
(s/def ::name string?) (s/def ::name string?)
@ -35,4 +36,37 @@
::text-transform] ::text-transform]
:opt-un [::path])) :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 (ns app.common.test-helpers.files
(:require (:require
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.types.components-list :as ctkl] [app.common.types.components-list :as ctkl]
[app.common.types.container :as ctn] [app.common.types.colors-list :as ctcl]
[app.common.types.file :as ctf] [app.common.types.container :as ctn]
[app.common.types.pages-list :as ctpl] [app.common.types.file :as ctf]
[app.common.types.shape :as cts] [app.common.types.pages-list :as ctpl]
[app.common.types.shape-tree :as ctst] [app.common.types.shape :as cts]
[app.common.uuid :as uuid])) [app.common.types.shape-tree :as ctst]
[app.common.types.typographies-list :as ctyl]
[app.common.uuid :as uuid]))
(def ^:private idmap (atom {})) (def ^:private idmap (atom {}))
@ -108,3 +110,38 @@
% %
instance-shapes))))))) 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 (ns app.common.types.file-test
(:require (:require
[clojure.test :as t] ;; Uncomment to debug
[app.common.geom.point :as gpt] ;; [clojure.pprint :refer [pprint]]
[app.common.types.components-list :as ctkl] ;; [cuerdas.core :as str]
[app.common.types.file :as ctf] [clojure.test :as t]
[app.common.types.pages-list :as ctpl] [app.common.data :as d]
[app.common.types.shape :as cts] [app.common.geom.point :as gpt]
[app.common.types.shape-tree :as ctst] [app.common.text :as txt]
[app.common.uuid :as uuid] [app.common.types.colors-list :as ctcl]
[app.common.test-helpers.files :as thf] [app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl]
[app.common.data :as d] [app.common.types.container :as ctn]
[app.common.pages.helpers :as cph] [app.common.types.file :as ctf]
[cuerdas.core :as str] [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 (t/use-fixtures :each
{:before thf/reset-idmap!}) {:before thf/reset-idmap!})
(t/deftest test-absorb-assets (t/deftest test-absorb-components
(let [library-id (uuid/custom 1 1) (let [library-id (uuid/custom 1 1)
library-page-id (uuid/custom 2 2) library-page-id (uuid/custom 2 2)
file-id (uuid/custom 3 3) file-id (uuid/custom 3 3)
@ -52,7 +57,26 @@
absorbed-file (ctf/update-file-data absorbed-file (ctf/update-file-data
file 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") ;; (println "\n===== library")
;; (ctf/dump-tree (:data library) ;; (ctf/dump-tree (:data library)
@ -67,11 +91,118 @@
;; true) ;; true)
;; (println "\n===== absorbed file") ;; (println "\n===== absorbed file")
;; (println (str "\n<" (:name (first pages)) ">"))
;; (ctf/dump-tree (:data absorbed-file) ;; (ctf/dump-tree (:data absorbed-file)
;; file-page-id ;; (:id (first pages))
;; {} ;; {file-id absorbed-file}
;; true) ;; 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 (= (count pages) 2))
(t/is (= file-id (:id absorbed-file))))) (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.spec :as us]
[app.common.text :as txt] [app.common.text :as txt]
[app.common.types.color :as ctc] [app.common.types.color :as ctc]
[app.common.types.component :as ctk]
[app.common.types.container :as ctn] [app.common.types.container :as ctn]
[app.common.types.shape-tree :as ctst] [app.common.types.shape-tree :as ctst]
[app.common.types.typography :as cty]
[app.main.data.workspace.groups :as dwg] [app.main.data.workspace.groups :as dwg]
[app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.state-helpers :as wsh]
[cljs.spec.alpha :as s] [cljs.spec.alpha :as s]
@ -94,7 +96,7 @@
(let [position (gpt/add (gpt/point (:x main-instance-shape) (:y main-instance-shape)) (let [position (gpt/add (gpt/point (:x main-instance-shape) (:y main-instance-shape))
(gpt/point (+ (:width main-instance-shape) 50) 0)) (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 _] [new-component-shape new-component-shapes _]
(ctst/clone-object component-root (ctst/clone-object component-root
@ -243,13 +245,7 @@
(defmethod uses-assets? :typographies (defmethod uses-assets? :typographies
[_ shape library-id _] [_ shape library-id _]
(and (= (:type shape) :text) (cty/uses-library-typographies? shape library-id))
(->> 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))))))
(defmulti generate-sync-shape (defmulti generate-sync-shape
"Generate changes to synchronize one shape from all assets of the given type "Generate changes to synchronize one shape from all assets of the given type
@ -433,7 +429,7 @@
root-inst shape-inst root-inst shape-inst
root-main (when component root-main (when component
(cph/get-component-root component))] (ctk/get-component-root component))]
(if component (if component
(generate-sync-shape-direct-recursive changes (generate-sync-shape-direct-recursive changes
@ -557,7 +553,7 @@
initial-root? (:component-root? shape-inst) initial-root? (:component-root? shape-inst)
root-inst shape-inst root-inst shape-inst
root-main (cph/get-component-root component)] root-main (ctk/get-component-root component)]
(if component (if component
(generate-sync-shape-inverse-recursive changes (generate-sync-shape-inverse-recursive changes
@ -691,13 +687,13 @@
(reduce only-inst-cb changes children-inst) (reduce only-inst-cb changes children-inst)
:else :else
(if (cph/is-main-of? child-main child-inst) (if (ctk/is-main-of? child-main child-inst)
(recur (next children-inst) (recur (next children-inst)
(next children-main) (next children-main)
(both-cb changes child-inst child-main)) (both-cb changes child-inst child-main))
(let [child-inst' (d/seek #(cph/is-main-of? child-main %) children-inst) (let [child-inst' (d/seek #(ctk/is-main-of? child-main %) children-inst)
child-main' (d/seek #(cph/is-main-of? % child-inst) children-main)] child-main' (d/seek #(ctk/is-main-of? % child-inst) children-main)]
(cond (cond
(nil? child-inst') (nil? child-inst')
(recur children-inst (recur children-inst
@ -726,7 +722,7 @@
[changes component-shape index component container root-instance root-main omit-touched? set-remote-synced?] [changes component-shape index component container root-instance root-main omit-touched? set-remote-synced?]
(log/info :msg (str "ADD [P] " (:name component-shape))) (log/info :msg (str "ADD [P] " (:name component-shape)))
(let [component-parent-shape (ctn/get-shape component (:parent-id 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) (cph/get-children-with-self (:objects container)
(:id root-instance))) (:id root-instance)))
all-parents (into [(:id parent-shape)] all-parents (into [(:id parent-shape)]
@ -794,7 +790,7 @@
[changes shape index component page root-instance root-main] [changes shape index component page root-instance root-main]
(log/info :msg (str "ADD [C] " (:name shape))) (log/info :msg (str "ADD [C] " (:name shape)))
(let [parent-shape (ctn/get-shape page (:parent-id 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) (cph/get-children-with-self (:objects component)
(:id root-main))) (:id root-main)))
all-parents (into [(:id component-parent-shape)] all-parents (into [(:id component-parent-shape)]

View File

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

View File

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

View File

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

View File

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