From 7da159d52aedf33e394f96338e702ae7925775b9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9s=20Moya?= <andres.moya@kaleidos.net>
Date: Wed, 29 Jun 2022 15:23:29 +0200
Subject: [PATCH] :tada: Absorb components when deleting or unpublishing a
 library

---
 backend/src/app/rpc/mutations/files.clj       |  45 ++-
 common/src/app/common/pages/migrations.cljc   | 121 ++++---
 common/src/app/common/types/component.cljc    |  13 +
 .../src/app/common/types/components_list.cljc |   4 +
 common/src/app/common/types/container.cljc    |   4 +
 common/src/app/common/types/file.cljc         | 312 +++++++++++++++---
 common/src/app/common/types/shape_tree.cljc   |   9 +-
 common/test/app/common/types/file_test.cljc   |  32 +-
 .../src/app/main/data/workspace/shapes.cljs   |  24 +-
 .../sidebar/options/menus/component.cljs      |  21 +-
 10 files changed, 436 insertions(+), 149 deletions(-)
 create mode 100644 common/src/app/common/types/component.cljc

diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj
index b3ee8cb3b..ccdacf86a 100644
--- a/backend/src/app/rpc/mutations/files.clj
+++ b/backend/src/app/rpc/mutations/files.clj
@@ -111,16 +111,29 @@
 ;; --- Mutation: Set File shared
 
 (declare set-file-shared)
+(declare unlink-files)
+(declare absorb-library)
 
 (s/def ::set-file-shared
   (s/keys :req-un [::profile-id ::id ::is-shared]))
 
 (sv/defmethod ::set-file-shared
-  [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}]
+  [{:keys [pool] :as cfg} {:keys [id profile-id is-shared] :as params}]
   (db/with-atomic [conn pool]
     (files/check-edition-permissions! conn profile-id id)
+    (when-not is-shared
+        (absorb-library conn params)
+        (unlink-files conn params))
     (set-file-shared conn params)))
 
+(def sql:unlink-files
+  "delete from file_library_rel
+    where library_file_id = ?")
+
+(defn- unlink-files
+  [conn {:keys [id] :as params}]
+  (db/exec-one! conn [sql:unlink-files id]))
+
 (defn- set-file-shared
   [conn {:keys [id is-shared] :as params}]
   (db/update! conn :file
@@ -138,6 +151,7 @@
   [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}]
   (db/with-atomic [conn pool]
     (files/check-edition-permissions! conn profile-id id)
+    (absorb-library conn params)
     (mark-file-deleted conn params)))
 
 (defn mark-file-deleted
@@ -147,6 +161,35 @@
               {:id id})
   nil)
 
+(def sql:find-files
+  "select file_id 
+    from file_library_rel
+   where library_file_id=?")
+
+(defn absorb-library
+  "Find all files using a shared library, and absorb all library assets
+  into the file local libraries"
+  [conn {:keys [id] :as params}]
+  (let [library (->> (db/get-by-id conn :file id)
+                     (files/decode-row)
+                     (pmg/migrate-file))]
+    (when (:is-shared library)
+      (let [process-file
+            (fn [row]
+              (let [ts (dt/now)
+                    file (->> (db/get-by-id conn :file (:file-id row))
+                              (files/decode-row)
+                              (pmg/migrate-file))
+                    updated-data (ctf/absorb-assets (:data file) (:data library))]
+
+                (db/update! conn :file
+                            {:revn (inc (:revn file))
+                             :data (blob/encode updated-data)
+                             :modified-at ts}
+                            {:id (:id file)})))]
+
+        (dorun (->> (db/exec! conn [sql:find-files id])
+                    (map process-file)))))))
 
 ;; --- Mutation: Link file to library
 
diff --git a/common/src/app/common/pages/migrations.cljc b/common/src/app/common/pages/migrations.cljc
index 601671bfd..cb186933f 100644
--- a/common/src/app/common/pages/migrations.cljc
+++ b/common/src/app/common/pages/migrations.cljc
@@ -15,7 +15,9 @@
    [app.common.math :as mth]
    [app.common.pages :as cp]
    [app.common.pages.helpers :as cph]
+   [app.common.types.components-list :as ctkl]
    [app.common.types.container :as ctn]
+   [app.common.types.file :as ctf]
    [app.common.types.page :as ctp]
    [app.common.types.pages-list :as ctpl]
    [app.common.types.shape :as cts]
@@ -439,70 +441,65 @@
 
 (defmethod migrate 20
   [data]
-  (let [page-id (uuid/next)
-
-        components (->> (:components data)
-                        vals
-                        (sort-by :name))
-
-        add-library-page
-        (fn [data]
-          (let [page (ctp/make-empty-page page-id "Library page")]
-            (-> data
-                (ctpl/add-page page))))
-
-        add-main-instance
-        (fn [data component position]
-          (let [page (ctpl/get-page data page-id)
-
-                [new-shape new-shapes]
-                (ctn/instantiate-component page
-                                           component
-                                           (:id data)
-                                           position)
-
-                add-shape
-                (fn [data shape]
-                  (update-in data [:pages-index page-id]
-                             #(ctst/add-shape (:id shape)
-                                              shape
-                                              %
-                                              (: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.
-
-                update-component
-                (fn [component]
-                  (assoc component
-                         :main-instance-id (:id new-shape)
-                         :main-instance-page page-id))]
-
-            (as-> data $
-              (reduce add-shape $ new-shapes)
-              (update-in $ [:components (:id component)] update-component))))
-
-        add-instance-grid
-        (fn [data components]
-          (let [position-seq (ctst/generate-shape-grid
-                               (map cph/get-component-root components)
-                               50)]
-            (loop [data           data
-                   components-seq (seq components)
-                   position-seq   position-seq]
-              (let [component (first components-seq)
-                    position  (first position-seq)]
-                (if (nil? component)
-                  data
-                  (recur (add-main-instance data component position)
-                         (rest components-seq)
-                         (rest position-seq)))))))]
-
+  (let [components (ctkl/components-seq data)]
     (if (empty? components)
       data
-      (-> data
-          (add-library-page)
-          (add-instance-grid components)))))
+      (let [grid-gap 50
+
+            [data page-id start-pos]
+            (ctf/get-or-add-library-page data grid-gap)
+
+            add-main-instance
+            (fn [data component position]
+              (let [page (ctpl/get-page data page-id)
+
+                    [new-shape new-shapes]
+                    (ctn/instantiate-component page
+                                               component
+                                               (:id data)
+                                               position)
+
+                    add-shapes
+                    (fn [page]
+                      (reduce (fn [page shape]
+                                (ctst/add-shape (:id shape)
+                                                shape
+                                                page
+                                                (:frame-id shape)
+                                                (:parent-id shape)
+                                                nil     ; <- As shapes are ordered, we can safely add each
+                                                true))  ;    one at the end of the parent's children list.
+                              page
+                              new-shapes))
+
+                    update-component
+                    (fn [component]
+                      (assoc component
+                             :main-instance-id (:id new-shape)
+                             :main-instance-page page-id))]
+
+                (-> data
+                    (ctpl/update-page page-id add-shapes)
+                    (ctkl/update-component (:id component) update-component))))
+
+            add-instance-grid
+            (fn [data components]
+              (let [position-seq (ctst/generate-shape-grid
+                                   (map cph/get-component-root components)
+                                   start-pos
+                                   grid-gap)]
+                (loop [data           data
+                       components-seq (seq components)
+                       position-seq   position-seq]
+                  (let [component (first components-seq)
+                        position  (first position-seq)]
+                    (if (nil? component)
+                      data
+                      (recur (add-main-instance data component position)
+                             (rest components-seq)
+                             (rest position-seq)))))))]
+
+        (add-instance-grid data (sort-by :name components))))))
 
 ;; TODO: pending to do a migration for delete already not used fill
 ;; and stroke props. This should be done for >1.14.x version.
diff --git a/common/src/app/common/types/component.cljc b/common/src/app/common/types/component.cljc
new file mode 100644
index 000000000..5dbe23865
--- /dev/null
+++ b/common/src/app/common/types/component.cljc
@@ -0,0 +1,13 @@
+;; 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.component)
+ 
+(defn instance-of?
+  [shape component]
+  (and (some? (:component-id shape))
+       (= (:component-id shape) (:id component))))
+
diff --git a/common/src/app/common/types/components_list.cljc b/common/src/app/common/types/components_list.cljc
index 6f5643e67..6fb2b0872 100644
--- a/common/src/app/common/types/components_list.cljc
+++ b/common/src/app/common/types/components_list.cljc
@@ -26,3 +26,7 @@
   [file-data component-id]
   (get-in file-data [:components component-id]))
 
+(defn update-component
+  [file-data component-id f]
+  (update-in file-data [:components component-id] f))
+
diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc
index 7f7e8d985..63a598504 100644
--- a/common/src/app/common/types/container.cljc
+++ b/common/src/app/common/types/container.cljc
@@ -56,6 +56,10 @@
   [container]
   (vals (:objects container)))
 
+(defn update-shape
+  [container shape-id f]
+  (update-in container [:objects shape-id] f))
+
 (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
diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc
index b999f3ccd..e9543453d 100644
--- a/common/src/app/common/types/file.cljc
+++ b/common/src/app/common/types/file.cljc
@@ -6,18 +6,22 @@
 
 (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]
-   [app.common.types.pages-list :as ctpl]
-   [app.common.uuid :as uuid]
-   [clojure.spec.alpha :as s]
-   [cuerdas.core :as str]))
+    [app.common.data :as d]
+    [app.common.geom.point :as gpt]
+    [app.common.geom.shapes :as gsh]
+    [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.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.uuid :as uuid]
+    [clojure.spec.alpha :as s]
+    [cuerdas.core :as str]))
 
 ;; Specs
 
@@ -97,48 +101,270 @@
   (concat (map #(ctn/make-container % :page) (ctpl/pages-seq file-data))
           (map #(ctn/make-container % :component) (ctkl/components-seq file-data))))
 
+(defn update-container
+  "Update a container inside the file, it can be a page or a component"
+  [file-data container f]
+  (if (ctn/page? container)
+    (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]])))]
+
+    (mapcat #(find-instances-in-container % component) (containers-seq file-data))))
+
+(defn get-or-add-library-page
+  [file-data grid-gap]
+  "If exists a page named 'Library page', get the id and calculate the position to start
+  adding new components. If not, create it and start at (0, 0)."
+  (let [library-page (d/seek #(= (:name %) "Library page") (ctpl/pages-seq file-data))]
+    (if (some? library-page)
+      (let [compare-pos (fn [pos shape]
+                          (let [bounds (gsh/bounding-box shape)]
+                            (gpt/point (min (:x pos) (get bounds :x 0))
+                                       (max (:y pos) (+ (get bounds :y 0)
+                                                        (get bounds :height 0)
+                                                        grid-gap)))))
+            position (reduce compare-pos
+                             (gpt/point 0 0)
+                             (ctn/shapes-seq library-page))]
+        [file-data (:id library-page) position])
+      (let [library-page (ctp/make-empty-page (uuid/next) "Library page")]
+        [(ctpl/add-page file-data library-page) (:id library-page) (gpt/point 0 0)]))))
+
+(defn- absorb-components
+  [file-data library-data used-components]
+  (let [grid-gap 50
+
+        ; Search for the library page. If not exists, create it.
+        [file-data page-id start-pos]
+        (get-or-add-library-page file-data grid-gap)
+
+        absorb-component
+        (fn [file-data [component instances] position]
+          (let [page (ctpl/get-page file-data page-id)
+
+                ; Make a new main instance for the component
+                [main-instance-shape main-instance-shapes]
+                (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
+                remap-instances
+                (fn [file-data [container shapes]]
+                  (let [remap-instance #(assoc % :component-file (:id file-data))]
+                    (update-container file-data
+                                      container
+                                      #(reduce (fn [container shape]
+                                                 (ctn/update-shape container
+                                                                   (:id shape)
+                                                                   remap-instance))
+                                               %
+                                               shapes))))]
+
+            (as-> file-data $
+              (ctpl/update-page $ page-id add-main-instance-shapes)
+              (copy-component $)
+              (reduce remap-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 #(ctk/get-component-root (first %)) used-components)
+                               start-pos
+                               grid-gap)]
+            (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))))
+
+(defn- absorb-colors
+  [file-data library-data used-colors]
+  (let [absorb-color
+        (fn [file-data [color usages]]
+          (let [remap-shape #(ctc/remap-colors % (:id file-data) color)
+
+                remap-shapes
+                (fn [file-data [container shapes]]
+                  (update-container file-data
+                                    container
+                                    #(reduce (fn [container shape]
+                                               (ctn/update-shape container
+                                                                 (:id shape)
+                                                                 remap-shape))
+                                             %
+                                             shapes)))]
+          (as-> file-data $
+            (ctcl/add-color $ color)
+            (reduce remap-shapes $ usages))))]
+
+    (reduce absorb-color
+            file-data
+            used-colors)))
+
+(defn- absorb-typographies
+  [file-data library-data used-typographies]
+  (let [absorb-typography
+        (fn [file-data [typography usages]]
+          (let [remap-shape #(cty/remap-typographies % (:id file-data) typography)
+
+                remap-shapes
+                (fn [file-data [container shapes]]
+                  (update-container file-data
+                                    container
+                                    #(reduce (fn [container shape]
+                                               (ctn/update-shape container
+                                                                 (:id shape)
+                                                                 remap-shape))
+                                             %
+                                             shapes)))]
+          (as-> file-data $
+            (ctyl/add-typography $ typography)
+            (reduce remap-shapes $ usages))))]
+
+    (reduce absorb-typography
+            file-data
+            used-typographies)))
+
 (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
+  (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 instances
+                    (when (d/not-empty? 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)))))
+      (let [; Search for the library page. If not exists, create it.
+            [file-data page-id start-pos]
+            (get-or-add-library-page file-data)
+
+            absorb-component
+            (fn [file-data [component instances] position]
+              (let [page (ctpl/get-page file-data page-id)
+
+                    ; Make a new main instance for the component
+                    [main-instance-shape main-instance-shapes]
+                    (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
 
diff --git a/common/src/app/common/types/shape_tree.cljc b/common/src/app/common/types/shape_tree.cljc
index c7294699a..b03054e24 100644
--- a/common/src/app/common/types/shape_tree.cljc
+++ b/common/src/app/common/types/shape_tree.cljc
@@ -326,7 +326,7 @@
 (defn generate-shape-grid
   "Generate a sequence of positions that lays out the list of
   shapes in a grid of equal-sized rows and columns."
-  [shapes gap]
+  [shapes start-pos gap]
   (let [shapes-bounds (map gsh/bounding-box shapes)
 
         grid-size   (mth/ceil (mth/sqrt (count shapes)))
@@ -339,11 +339,12 @@
                    (let [counter (inc (:counter (meta position)))
                          row     (quot counter grid-size)
                          column  (mod counter grid-size)
-                         new-pos (gpt/point (* column column-size)
-                                            (* row row-size))]
+                         new-pos (gpt/add start-pos
+                                          (gpt/point (* column column-size)
+                                                     (* row row-size)))]
                      (with-meta new-pos
                                 {:counter counter})))]
     (iterate next-pos
-             (with-meta (gpt/point 0 0)
+             (with-meta start-pos
                         {:counter 0}))))
 
diff --git a/common/test/app/common/types/file_test.cljc b/common/test/app/common/types/file_test.cljc
index 0049732ab..76764c69d 100644
--- a/common/test/app/common/types/file_test.cljc
+++ b/common/test/app/common/types/file_test.cljc
@@ -54,25 +54,23 @@
                         file
                         #(ctf/absorb-assets % (:data library)))]
 
-    (println "\n===== library")
-    (ctf/dump-tree (:data library)
-                   library-page-id
-                   {}
-                   true)
+    ;; (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===== file")
+    ;; (ctf/dump-tree (:data file)
+    ;;                file-page-id
+    ;;                {library-id library}
+    ;;                true)
 
-    (println "\n===== absorbed file")
-    (ctf/dump-tree (:data absorbed-file)
-                   file-page-id
-                   {}
-                   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)))))
diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs
index 237ce6029..880db210c 100644
--- a/frontend/src/app/main/data/workspace/shapes.cljs
+++ b/frontend/src/app/main/data/workspace/shapes.cljs
@@ -13,10 +13,10 @@
    [app.common.pages.changes-builder :as pcb]
    [app.common.pages.helpers :as cph]
    [app.common.spec :as us]
-   [app.common.types.page :as csp]
-   [app.common.types.shape :as spec.shape]
-   [app.common.types.shape.interactions :as csi]
-   [app.common.types.shape-tree :as ctt]
+   [app.common.types.page :as ctp]
+   [app.common.types.shape :as cts]
+   [app.common.types.shape.interactions :as ctsi]
+   [app.common.types.shape-tree :as ctst]
    [app.common.uuid :as uuid]
    [app.main.data.workspace.changes :as dch]
    [app.main.data.workspace.edition :as dwe]
@@ -28,14 +28,14 @@
    [cljs.spec.alpha :as s]
    [potok.core :as ptk]))
 
-(s/def ::shape-attrs ::spec.shape/shape-attrs)
+(s/def ::shape-attrs ::cts/shape-attrs)
 
 (defn get-shape-layer-position
   [objects selected attrs]
 
   ;; Calculate the frame over which we're drawing
   (let [position @ms/mouse-position
-        frame-id (:frame-id attrs (cph/frame-id-by-position objects position))
+        frame-id (:frame-id attrs (ctst/frame-id-by-position objects position))
         shape (when-not (empty? selected)
                 (cph/get-base-shape objects selected))]
 
@@ -52,8 +52,8 @@
 (defn make-new-shape
   [attrs objects selected]
   (let [default-attrs (if (= :frame (:type attrs))
-                        cp/default-frame-attrs
-                        cp/default-shape-attrs)
+                        cts/default-frame-attrs
+                        cts/default-shape-attrs)
 
         selected-non-frames
         (into #{} (comp (map (d/getf objects))
@@ -117,7 +117,7 @@
             to-move-shapes
             (into []
                   (map (d/getf objects))
-                  (reverse (cph/sort-z-index objects shapes)))
+                  (reverse (ctst/sort-z-index objects shapes)))
 
             changes
             (when (d/not-empty? to-move-shapes)
@@ -289,10 +289,10 @@
             y (:y data (- vbc-y (/ height 2)))
             page-id (:current-page-id state)
             frame-id (-> (wsh/lookup-page-objects state page-id)
-                         (cph/frame-id-by-position {:x frame-x :y frame-y}))
-            shape (-> (cp/make-minimal-shape type)
+                         (ctst/frame-id-by-position {:x frame-x :y frame-y}))
+            shape (-> (cts/make-minimal-shape type)
                       (merge data)
                       (merge {:x x :y y})
                       (assoc :frame-id frame-id)
-                      (cp/setup-rect-selrect))]
+                      (cts/setup-rect-selrect))]
         (rx/of (add-shape shape))))))
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs
index 3e19ec5f9..0e6debc1a 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs
@@ -6,16 +6,17 @@
 
 (ns app.main.ui.workspace.sidebar.options.menus.component
   (:require
-   [app.main.data.modal :as modal]
-   [app.main.data.workspace :as dw]
-   [app.main.data.workspace.libraries :as dwl]
-   [app.main.store :as st]
-   [app.main.ui.components.context-menu :refer [context-menu]]
-   [app.main.ui.context :as ctx]
-   [app.main.ui.icons :as i]
-   [app.util.dom :as dom]
-   [app.util.i18n :as i18n :refer [tr]]
-   [rumext.alpha :as mf]))
+    [app.common.pages.helpers :as cph]
+    [app.main.data.modal :as modal]
+    [app.main.data.workspace :as dw]
+    [app.main.data.workspace.libraries :as dwl]
+    [app.main.store :as st]
+    [app.main.ui.components.context-menu :refer [context-menu]]
+    [app.main.ui.context :as ctx]
+    [app.main.ui.icons :as i]
+    [app.util.dom :as dom]
+    [app.util.i18n :as i18n :refer [tr]]
+    [rumext.alpha :as mf]))
 
 (def component-attrs [:component-id :component-file :shape-ref])