From 5dc7bc213fec6c58b54765e6f2cf079962527d9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Mon, 6 Jun 2022 15:32:11 +0200 Subject: [PATCH 01/14] :tada: Add the concept of 'main instance' --- common/src/app/common/pages/changes.cljc | 4 +- .../src/app/common/pages/changes_builder.cljc | 6 ++- common/src/app/common/pages/helpers.cljc | 5 +++ .../resources/images/icons/component-copy.svg | 3 ++ .../src/app/main/data/workspace/common.cljs | 1 - .../app/main/data/workspace/libraries.cljs | 8 +++- .../data/workspace/libraries_helpers.cljs | 4 +- .../src/app/main/data/workspace/shapes.cljs | 41 ++++++++++++++--- .../app/main/ui/components/shape_icon.cljs | 6 ++- frontend/src/app/main/ui/context.cljs | 3 +- frontend/src/app/main/ui/icons.cljs | 1 + frontend/src/app/main/ui/workspace.cljs | 44 +++++++++++-------- .../app/main/ui/workspace/sidebar/layers.cljs | 31 ++++++++----- .../sidebar/options/menus/component.cljs | 10 ++++- 14 files changed, 122 insertions(+), 45 deletions(-) create mode 100644 frontend/resources/images/icons/component-copy.svg diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc index 36a8a576ca..6e0c741151 100644 --- a/common/src/app/common/pages/changes.cljc +++ b/common/src/app/common/pages/changes.cljc @@ -392,11 +392,13 @@ ;; -- Components (defmethod process-change :add-component - [data {:keys [id name path shapes]}] + [data {:keys [id name path main-instance-id main-instance-page shapes]}] (assoc-in data [:components id] {:id id :name name :path path + :main-instance-id main-instance-id + :main-instance-page main-instance-page :objects (d/index-by :id shapes)})) (defmethod process-change :mod-component diff --git a/common/src/app/common/pages/changes_builder.cljc b/common/src/app/common/pages/changes_builder.cljc index 25808b1362..13e9d710f2 100644 --- a/common/src/app/common/pages/changes_builder.cljc +++ b/common/src/app/common/pages/changes_builder.cljc @@ -532,7 +532,7 @@ (apply-changes-local)))) (defn add-component - [changes id path name new-shapes updated-shapes] + [changes id path name new-shapes updated-shapes main-instance-id main-instance-page] (assert-page-id changes) (assert-objects changes) (let [page-id (::page-id (meta changes)) @@ -566,6 +566,8 @@ :id id :path path :name name + :main-instance-id main-instance-id + :main-instance-page main-instance-page :shapes new-shapes}) (into (map mk-change) updated-shapes)))) (update :undo-changes @@ -611,5 +613,7 @@ :id id :name (:name prev-component) :path (:path prev-component) + :main-instance-id (:main-instance-id prev-component) + :main-instance-page (:main-instance-page prev-component) :shapes (vals (:objects prev-component))})))) diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index 044d973461..52fa4748d2 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -362,6 +362,11 @@ (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)])) diff --git a/frontend/resources/images/icons/component-copy.svg b/frontend/resources/images/icons/component-copy.svg new file mode 100644 index 0000000000..ab45fb4a82 --- /dev/null +++ b/frontend/resources/images/icons/component-copy.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs index d9671bfedf..6f9ac6d350 100644 --- a/frontend/src/app/main/data/workspace/common.cljs +++ b/frontend/src/app/main/data/workspace/common.cljs @@ -108,4 +108,3 @@ :undo-changes [] :origin it :save-undo? false}))))))))))) - diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 7663a0585e..6e4fa70ca2 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -354,16 +354,20 @@ unames (into #{} (map :name) all-components) new-name (un/generate-unique-name unames (:name component)) - [new-shape new-shapes _updated-shapes] + [new-shape new-shapes _updated-shapes main-instance main-instance-page] (dwlh/duplicate-component component) + _ (prn "OJOOOOOOOOOOOOOOO falta calcular main-instance") + changes (-> (pcb/empty-changes it nil) ;; no objects are changed (pcb/with-objects nil) ;; in the current page (pcb/add-component (:id new-shape) (:path component) new-name new-shapes - []))] + [] + (:id main-instance) + (:id main-instance-page)))] (rx/of (dch/commit-changes changes)))))) diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index a1e50c89f2..1c74d9d1ee 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -121,7 +121,9 @@ path name new-shapes - updated-shapes))] + updated-shapes + (:id group) + page-id))] [group new-shape changes]))) (defn duplicate-component diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index 28adef987d..77d526f4b7 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -138,13 +138,17 @@ (ptk/reify ::delete-shapes ptk/WatchEvent (watch [it state _] - (let [page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) - page (wsh/lookup-page state page-id) + (let [file-id (:current-file-id state) + page-id (:current-page-id state) + file (wsh/get-file state file-id) + page (wsh/lookup-page state page-id) + objects (wsh/lookup-page-objects state page-id) ids (cph/clean-loops objects ids) lookup (d/getf objects) + local-library {file-id {:data file}} + groups-to-unmask (reduce (fn [group-ids id] ;; When the shape to delete is the mask of a masked group, @@ -164,7 +168,7 @@ ;; If any of the deleted shapes is the destination of ;; some interaction, this must be deleted, too. (let [interactions (:interactions shape)] - (some #(and (csi/has-destination %) + (some #(and (ctsi/has-destination %) (contains? ids (:destination %))) interactions))) (vals objects)) @@ -215,9 +219,29 @@ ;; Any parent whose children are all deleted, must be deleted too. (into (d/ordered-set) (find-all-empty-parents #{})) + components-to-delete + (reduce (fn [components id] + (let [shape (get objects id) + + component + (when (and (:component-id shape) (:component-file shape)) + ;; Only local components may have main instances + (cph/get-component local-library (:component-file shape) (:component-id shape))) + + main-instance? + (when component + (cph/is-main-instance? (:id shape) (:id page) component))] + + (if main-instance? + (conj components (:component-id shape)) + components))) + [] + (into ids all-children)) + changes (-> (pcb/empty-changes it page-id) (pcb/with-page page) (pcb/with-objects objects) + (pcb/with-library-data file) (pcb/set-page-option :guides guides) (pcb/remove-objects all-children) (pcb/remove-objects ids) @@ -231,13 +255,18 @@ (d/update-when shape :interactions (fn [interactions] (into [] - (remove #(and (csi/has-destination %) + (remove #(and (ctsi/has-destination %) (contains? ids (:destination %)))) interactions))))) (cond-> (seq starting-flows) (pcb/update-page-option :flows (fn [flows] (->> (map :id starting-flows) - (reduce csp/remove-flow flows))))))] + (reduce ctp/remove-flow flows)))))) + + changes (reduce (fn [changes component-id] + (pcb/delete-component changes component-id)) + changes + components-to-delete)] (rx/of (dch/commit-changes changes) (dwsl/update-layout-positions all-parents)))))) diff --git a/frontend/src/app/main/ui/components/shape_icon.cljs b/frontend/src/app/main/ui/components/shape_icon.cljs index 0a54feeae5..21b5e1e329 100644 --- a/frontend/src/app/main/ui/components/shape_icon.cljs +++ b/frontend/src/app/main/ui/components/shape_icon.cljs @@ -11,7 +11,7 @@ (mf/defc element-icon - [{:keys [shape] :as props}] + [{:keys [shape main-instance?] :as props}] (case (:type shape) :frame i/artboard :image i/image @@ -21,7 +21,9 @@ :rect i/box :text i/text :group (if (some? (:component-id shape)) - i/component + (if main-instance? + i/component + i/component-copy) (if (:masked-group? shape) i/mask i/folder)) diff --git a/frontend/src/app/main/ui/context.cljs b/frontend/src/app/main/ui/context.cljs index 34a3518c92..7f2f191ac7 100644 --- a/frontend/src/app/main/ui/context.cljs +++ b/frontend/src/app/main/ui/context.cljs @@ -21,6 +21,7 @@ (def current-project-id (mf/create-context nil)) (def current-page-id (mf/create-context nil)) (def current-file-id (mf/create-context nil)) +(def libraries (mf/create-context nil)) (def scroll-ctx (mf/create-context nil)) (def active-frames-ctx (mf/create-context nil)) -(def render-thumbnails (mf/create-context nil)) +(def render-thumbnails (mf/create-context nil)) diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs index b8232b043e..ae5d38542b 100644 --- a/frontend/src/app/main/ui/icons.cljs +++ b/frontend/src/app/main/ui/icons.cljs @@ -53,6 +53,7 @@ (def close (icon-xref :close)) (def code (icon-xref :code)) (def component (icon-xref :component)) +(def component-copy (icon-xref :component-copy)) (def copy (icon-xref :copy)) (def curve (icon-xref :curve)) (def cross (icon-xref :cross)) diff --git a/frontend/src/app/main/ui/workspace.cljs b/frontend/src/app/main/ui/workspace.cljs index 17b1fce61c..6068c1799a 100644 --- a/frontend/src/app/main/ui/workspace.cljs +++ b/frontend/src/app/main/ui/workspace.cljs @@ -114,10 +114,13 @@ (mf/defc workspace {::mf/wrap [mf/memo]} [{:keys [project-id file-id page-id layout-name] :as props}] - (let [file (mf/deref refs/workspace-file) - project (mf/deref refs/workspace-project) - layout (mf/deref refs/workspace-layout) - wglobal (mf/deref refs/workspace-global) + (let [file (mf/deref refs/workspace-file) + project (mf/deref refs/workspace-project) + layout (mf/deref refs/workspace-layout) + wglobal (mf/deref refs/workspace-global) + wglobal (mf/deref refs/workspace-global) + libraries (mf/deref refs/workspace-libraries) + local-library (mf/deref refs/workspace-local-library) background-color (:background-color wglobal)] @@ -145,23 +148,26 @@ [:& (mf/provider ctx/current-team-id) {:value (:team-id project)} [:& (mf/provider ctx/current-project-id) {:value (:id project)} [:& (mf/provider ctx/current-page-id) {:value page-id} - [:section#workspace {:style {:background-color background-color}} - (when (not (:hide-ui layout)) - [:& header {:file file - :page-id page-id - :project project - :layout layout}]) + [:& (mf/provider ctx/libraries) {:value (assoc libraries + (:id local-library) + {:data local-library})} + [:section#workspace {:style {:background-color background-color}} + (when (not (:hide-ui layout)) + [:& header {:file file + :page-id page-id + :project project + :layout layout}]) - [:& context-menu] + [:& context-menu] - (if (and (and file project) - (:initialized file)) - [:& workspace-page {:key (dm/str "page-" page-id) - :page-id page-id - :file file - :wglobal wglobal - :layout layout}] - [:& workspace-loader])]]]]])) + (if (and (and file project) + (:initialized file)) + [:& workspace-page {:key (dm/str "page-" page-id) + :page-id page-id + :file file + :wglobal wglobal + :layout layout}] + [:& workspace-loader])]]]]]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs index 32b19d0fa3..27a9fe4685 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs @@ -15,6 +15,7 @@ [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.shape-icon :as si] + [app.main.ui.context :as ctx] [app.main.ui.hooks :as hooks] [app.main.ui.icons :as i] [app.util.dom :as dom] @@ -85,7 +86,7 @@ (when (seq (:touched shape)) " *")]))) (mf/defc layer-item - [{:keys [index item selected objects] :as props}] + [{:keys [index page item selected objects] :as props}] (let [id (:id item) blocked? (:blocked item) hidden? (:hidden item) @@ -101,6 +102,12 @@ container? (or (cph/frame-shape? item) (cph/group-shape? item)) + libraries (mf/use-ctx ctx/libraries) + 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)) + toggle-collapse (mf/use-fn (mf/deps expanded?) @@ -244,7 +251,8 @@ [:div {:on-double-click #(do (dom/stop-propagation %) (dom/prevent-default %) (st/emit! dw/zoom-to-selected-shape))} - [:& si/element-icon {:shape item}]] + [:& si/element-icon {:shape item + :main-instance? main-instance?}]] [:& layer-name {:shape item :name-ref ref :on-start-edit #(reset! disable-drag true) @@ -268,7 +276,8 @@ (for [[index id] (reverse (d/enumerate (:shapes item)))] (when-let [item (get objects id)] [:& layer-item - {:item item + {:page page + :item item :selected selected :index index :objects objects @@ -289,8 +298,9 @@ {::mf/wrap [#(mf/memo % =) #(mf/throttle % 200)]} [{:keys [objects] :as props}] - (let [selected (mf/deref refs/selected-shapes) - selected (hooks/use-equal-memo selected) + (let [page (mf/deref refs/workspace-page) + selected (mf/deref refs/selected-shapes) + selected (hooks/use-equal-memo selected) root (get objects uuid/zero)] [:ul.element-list [:& hooks/sortable-container {} @@ -304,7 +314,8 @@ :objects objects :key id}] [:& layer-item - {:item obj + {:page page + :item obj :selected selected :index index :objects objects @@ -314,14 +325,16 @@ {::mf/wrap [#(mf/memo % =) #(mf/throttle % 200)]} [{:keys [objects] :as props}] - (let [selected (mf/deref refs/selected-shapes) + (let [page (mf/deref refs/workspace-page) + selected (mf/deref refs/selected-shapes) selected (hooks/use-equal-memo selected) root (get objects uuid/zero)] [:ul.element-list (for [[index id] (d/enumerate (:shapes root))] (when-let [obj (get objects id)] [:& layer-item - {:item obj + {:page page + :item obj :selected selected :index index :objects objects @@ -444,7 +457,6 @@ (take (:num-items @filter-state)) filtered-objects-total)))) - handle-show-more (fn [] (when (<= (:num-items @filter-state) (count filtered-objects-total)) @@ -542,7 +554,6 @@ (when last-hidden-frame (dom/add-class! last-hidden-frame "sticky"))))] - [:div#layers.tool-window (if (d/not-empty? focus) [:div.tool-window-bar 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 0a28635ced..3e19ec5f91 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 @@ -22,6 +22,8 @@ (mf/defc component-menu [{:keys [ids values shape-name] :as props}] (let [current-file-id (mf/use-ctx ctx/current-file-id) + current-page-id (mf/use-ctx ctx/current-page-id) + libraries (mf/use-ctx ctx/libraries) id (first ids) local (mf/use-state {:menu-open false}) @@ -30,6 +32,10 @@ library-id (:component-file values) show? (some? component-id) + 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) + on-menu-click (mf/use-callback (fn [event] @@ -69,7 +75,9 @@ [:span (tr "workspace.options.component")]] [:div.element-set-content [:div.row-flex.component-row - i/component + (if main-instance? + i/component + i/component-copy) shape-name [:div.row-actions {:on-click on-menu-click} From bdcbe46d0d00418dcee77984722d51bc7194bae0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 28 Jun 2022 11:05:45 +0200 Subject: [PATCH 02/14] :recycle: Move component instantiation to new types module --- backend/src/app/rpc/queries/files.clj | 5 +- backend/src/app/tasks/file_gc.clj | 3 +- common/src/app/common/file_builder.cljc | 1 + common/src/app/common/pages/focus.cljc | 3 +- common/src/app/common/pages/helpers.cljc | 235 ------------- common/src/app/common/spec.cljc | 1 + common/src/app/common/types/container.cljc | 79 +++++ common/src/app/common/types/page.cljc | 6 - common/src/app/common/types/pages_list.cljc | 26 ++ common/src/app/common/types/shape_tree.cljc | 318 ++++++++++++++++++ frontend/src/app/main/data/viewer.cljs | 5 +- frontend/src/app/main/data/workspace.cljs | 20 +- .../src/app/main/data/workspace/bool.cljs | 6 +- .../src/app/main/data/workspace/changes.cljs | 3 +- .../src/app/main/data/workspace/common.cljs | 9 + .../app/main/data/workspace/drawing/box.cljs | 3 +- .../main/data/workspace/drawing/curve.cljs | 4 +- .../src/app/main/data/workspace/groups.cljs | 6 +- .../app/main/data/workspace/interactions.cljs | 6 +- .../app/main/data/workspace/libraries.cljs | 7 +- .../data/workspace/libraries_helpers.cljs | 77 ++--- .../app/main/data/workspace/path/drawing.cljs | 4 +- .../app/main/data/workspace/selection.cljs | 10 +- .../src/app/main/data/workspace/shapes.cljs | 6 +- .../app/main/data/workspace/svg_upload.cljs | 11 +- .../app/main/data/workspace/transforms.cljs | 3 +- frontend/src/app/main/refs.cljs | 3 +- frontend/src/app/main/render.cljs | 3 +- .../sidebar/options/menus/interactions.cljs | 3 +- .../ui/workspace/viewport/frame_grid.cljs | 4 +- .../main/ui/workspace/viewport/guides.cljs | 3 +- .../app/main/ui/workspace/viewport/hooks.cljs | 7 +- .../main/ui/workspace/viewport/widgets.cljs | 4 +- frontend/src/app/util/geom/snap_points.cljs | 5 +- frontend/src/app/util/names.cljs | 38 --- frontend/src/app/util/snap_data.cljs | 5 +- frontend/test/app/components_basic_test.cljs | 3 +- frontend/test/app/components_sync_test.cljs | 3 +- frontend/test/app/test_helpers/libraries.cljs | 13 +- 39 files changed, 542 insertions(+), 409 deletions(-) create mode 100644 common/src/app/common/types/container.cljc create mode 100644 common/src/app/common/types/pages_list.cljc create mode 100644 common/src/app/common/types/shape_tree.cljc delete mode 100644 frontend/src/app/util/names.cljs diff --git a/backend/src/app/rpc/queries/files.clj b/backend/src/app/rpc/queries/files.clj index 18ec92848d..7adc19fbdb 100644 --- a/backend/src/app/rpc/queries/files.clj +++ b/backend/src/app/rpc/queries/files.clj @@ -12,6 +12,7 @@ [app.common.geom.shapes :as gsh] [app.common.pages.helpers :as cph] [app.common.pages.migrations :as pmg] + [app.common.types.shape-tree :as ctt] [app.common.spec :as us] [app.db :as db] [app.db.sql :as sql] @@ -304,7 +305,7 @@ (get-thumbnail-frame [data] (d/seek :use-for-thumbnail? (for [page (-> data :pages-index vals) - frame (-> page :objects cph/get-frames)] + frame (-> page :objects ctt/get-frames)] (assoc frame :page-id (:id page))))) ;; function responsible to filter objects data structure of @@ -355,7 +356,7 @@ (-> data :pages first)) page (dm/get-in data [:pages-index page-id]) - frame-ids (if (some? frame) (list frame-id) (map :id (cph/get-frames (:objects page)))) + frame-ids (if (some? frame) (list frame-id) (map :id (ctt/get-frames (:objects page)))) obj-ids (map #(str page-id %) frame-ids) thumbs (retrieve-object-thumbnails cfg id obj-ids)] diff --git a/backend/src/app/tasks/file_gc.clj b/backend/src/app/tasks/file_gc.clj index 29ad2eeb84..1f9d5042e7 100644 --- a/backend/src/app/tasks/file_gc.clj +++ b/backend/src/app/tasks/file_gc.clj @@ -14,6 +14,7 @@ [app.common.logging :as l] [app.common.pages.helpers :as cph] [app.common.pages.migrations :as pmg] + [app.common.types.shape-tree :as ctt] [app.db :as db] [app.util.blob :as blob] [app.util.time :as dt] @@ -128,7 +129,7 @@ get-objects-ids (fn [{:keys [id objects]}] - (->> (cph/get-frames objects) + (->> (ctt/get-frames objects) (map #(str id (:id %))))) using (into #{} diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index 882991a1f0..5fcc64c0fd 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -14,6 +14,7 @@ [app.common.pages.changes-spec :as pcs] [app.common.pages.init :as init] [app.common.spec :as us] + [app.common.types.page :as ctp] [app.common.uuid :as uuid] [cuerdas.core :as str])) diff --git a/common/src/app/common/pages/focus.cljc b/common/src/app/common/pages/focus.cljc index a7ca0f4951..57a048b2ef 100644 --- a/common/src/app/common/pages/focus.cljc +++ b/common/src/app/common/pages/focus.cljc @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.pages.helpers :as cph] + [app.common.types.shape-tree :as ctt] [app.common.uuid :as uuid])) (defn focus-objects @@ -21,7 +22,7 @@ (cond-> objects (some? ids-with-children) (-> (select-keys ids-with-children) - (assoc-in [uuid/zero :shapes] (cph/sort-z-index objects focus)))))) + (assoc-in [uuid/zero :shapes] (ctt/sort-z-index objects focus)))))) (defn filter-not-focus [objects focus ids] diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index 52fa4748d2..5984cd46ae 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -8,10 +8,7 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] - [app.common.geom.shapes :as gsh] - [app.common.math :as mth] [app.common.spec :as us] - [app.common.types.page :as ctp] [app.common.uuid :as uuid] [cuerdas.core :as str])) @@ -62,14 +59,6 @@ (and (not (frame-shape? shape)) (= (:frame-id shape) uuid/zero))) -(defn get-shape - [container shape-id] - (us/assert ::ctp/container container) - (us/assert ::us/uuid shape-id) - (-> container - (get :objects) - (get shape-id))) - (defn get-children-ids [objects id] (if-let [shapes (-> (get objects id) :shapes (some-> vec))] @@ -158,146 +147,6 @@ (:shapes) (keep lookup))))) -(defn get-frames - "Retrieves all frame objects as vector" - [objects] - (or (-> objects meta ::index-frames) - (let [lookup (d/getf objects) - xform (comp (remove #(= uuid/zero %)) - (keep lookup) - (filter frame-shape?))] - (->> (keys objects) - (into [] xform))))) - -(defn get-frames-ids - "Retrieves all frame ids as vector" - [objects] - (->> (get-frames objects) - (mapv :id))) - -(defn get-nested-frames - [objects frame-id] - (into #{} - (comp (filter frame-shape?) - (map :id)) - (get-children objects frame-id))) - -(defn get-root-frames-ids - "Retrieves all frame objects as vector. It is not implemented in - function of `get-immediate-children` for performance reasons. This - function is executed in the render hot path." - [objects] - (let [add-frame - (fn [result shape] - (cond-> result - (frame-shape? shape) - (conj (:id shape))))] - (reduce-objects objects (complement frame-shape?) add-frame []))) - -(defn get-root-objects - "Get all the objects under the root object" - [objects] - (let [add-shape - (fn [result shape] - (conj result shape))] - (reduce-objects objects (complement frame-shape?) add-shape []))) - -(defn get-root-shapes - "Get all shapes that are not frames" - [objects] - (let [add-shape - (fn [result shape] - (cond-> result - (not (frame-shape? shape)) - (conj shape)))] - (reduce-objects objects (complement frame-shape?) add-shape []))) - -(defn get-root-shapes-ids - [objects] - (->> (get-root-shapes objects) - (mapv :id))) - -(defn get-base - [objects id-a id-b] - - (let [parents-a (reverse (get-parents-seq objects id-a)) - parents-b (reverse (get-parents-seq objects id-b)) - - [base base-child-a base-child-b] - (loop [parents-a (rest parents-a) - parents-b (rest parents-b) - base uuid/zero] - (cond - (not= (first parents-a) (first parents-b)) - [base (first parents-a) (first parents-b)] - - (or (empty? parents-a) (empty? parents-b)) - [uuid/zero (first parents-a) (first parents-b)] - - :else - (recur (rest parents-a) (rest parents-b) (first parents-a)))) - - index-base-a (when base-child-a (get-position-on-parent objects base-child-a)) - index-base-b (when base-child-b (get-position-on-parent objects base-child-b))] - - [base index-base-a index-base-b])) - -(defn is-shape-over-shape? - [objects base-shape-id over-shape-id {:keys [top-frames?]}] - - (let [[base index-a index-b] (get-base objects base-shape-id over-shape-id)] - (cond - (= base base-shape-id) - (and (not top-frames?) - (frame-shape? objects base-shape-id) - (root-frame? objects base-shape-id)) - - (= base over-shape-id) - (or top-frames? - (not (frame-shape? objects over-shape-id)) - (not (root-frame? objects over-shape-id))) - - :else - (< index-a index-b)))) - -(defn sort-z-index - ([objects ids] - (sort-z-index objects ids nil)) - - ([objects ids {:keys [bottom-frames?] :as options}] - (letfn [(comp [id-a id-b] - (let [type-a (dm/get-in objects [id-a :type]) - type-b (dm/get-in objects [id-b :type])] - (cond - (and bottom-frames? (= :frame type-a) (not= :frame type-b)) - 1 - - (and bottom-frames? (not= :frame type-a) (= :frame type-b)) - -1 - - (= id-a id-b) - 0 - - (is-shape-over-shape? objects id-a id-b options) - 1 - - :else - -1)))] - (sort comp ids)))) - -(defn frame-id-by-position - [objects position] - (let [top-frame - (->> (get-frames-ids objects) - (sort-z-index objects) - (d/seek #(and position (gsh/has-point? (get objects %) position))))] - (or top-frame uuid/zero))) - -(defn frame-by-position - [objects position] - (let [frame-id (frame-id-by-position objects position)] - (get objects frame-id))) - (declare indexed-shapes) (defn get-base-shape @@ -467,57 +316,6 @@ (reduce add-element (d/ordered-set) ids))) -(defn clone-object - "Gets a copy of the object and all its children, with new ids - and with the parent-children links correctly set. Admits functions - to make more transformations to the cloned objects and the - original ones. - - Returns the cloned object, the list of all new objects (including - the cloned one), and possibly a list of original objects modified." - - ([object parent-id objects update-new-object] - (clone-object object parent-id objects update-new-object identity)) - - ([object parent-id objects update-new-object update-original-object] - (let [new-id (uuid/next)] - (loop [child-ids (seq (:shapes object)) - new-direct-children [] - new-children [] - updated-children []] - - (if (empty? child-ids) - (let [new-object (cond-> object - true - (assoc :id new-id - :parent-id parent-id) - - (some? (:shapes object)) - (assoc :shapes (mapv :id new-direct-children))) - - new-object (update-new-object new-object object) - new-objects (into [new-object] new-children) - - updated-object (update-original-object object new-object) - updated-objects (if (identical? object updated-object) - updated-children - (into [updated-object] updated-children))] - - [new-object new-objects updated-objects]) - - (let [child-id (first child-ids) - child (get objects child-id) - _ (us/assert some? child) - - [new-child new-child-objects updated-child-objects] - (clone-object child new-id objects update-new-object update-original-object)] - - (recur - (next child-ids) - (into new-direct-children [new-child]) - (into new-children new-child-objects) - (into updated-children updated-child-objects)))))))) - (defn indexed-shapes "Retrieves a list with the indexes for each element in the layer tree. This will be used for shift+selection." @@ -700,36 +498,3 @@ :id)) -(defn get-viewer-frames - ([objects] - (get-viewer-frames objects nil)) - - ([objects {:keys [all-frames?]}] - (into [] - (comp (map (d/getf objects)) - (if all-frames? - (map identity) - (remove :hide-in-viewer))) - (sort-z-index objects (get-frames-ids objects) {:top-frames? true})))) - -(defn start-page-index - [objects] - (with-meta objects {::index-frames (get-frames (with-meta objects nil))})) - -(defn update-page-index - [objects] - (with-meta objects {::index-frames (get-frames (with-meta objects nil))})) - -(defn start-object-indices - [file] - (letfn [(process-index [page-index page-id] - (update-in page-index [page-id :objects] start-page-index))] - (update file :pages-index #(reduce process-index % (keys %))))) - -(defn update-object-indices - [file page-id] - (update-in file [:pages-index page-id :objects] update-page-index)) - -(defn rotated-frame? - [frame] - (not (mth/almost-zero? (:rotation frame 0)))) diff --git a/common/src/app/common/spec.cljc b/common/src/app/common/spec.cljc index e596179ad1..a1fb2fd722 100644 --- a/common/src/app/common/spec.cljc +++ b/common/src/app/common/spec.cljc @@ -107,6 +107,7 @@ (s/def ::number (s/conformer number-conformer str)) (s/def ::integer (s/conformer integer-conformer str)) (s/def ::not-empty-string (s/and string? #(not (str/empty? %)))) +(s/def ::set-of-string (s/every string? :kind set?)) (s/def ::url string?) (s/def ::fn fn?) (s/def ::id ::uuid) diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc new file mode 100644 index 0000000000..168cfe8ca0 --- /dev/null +++ b/common/src/app/common/types/container.cljc @@ -0,0 +1,79 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.common.types.container + (:require + [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] + [app.common.spec :as us] + [app.common.types.shape-tree :as ctt] + [clojure.spec.alpha :as s])) + +(s/def ::type #{:page :component}) +(s/def ::id uuid?) +(s/def ::name string?) +(s/def ::path (s/nilable string?)) + +(s/def ::container + (s/keys :req-un [::id ::name ::ctt/objects] + :opt-un [::type ::path])) + +(defn get-shape + [container shape-id] + (us/assert ::container container) + (us/assert ::us/uuid shape-id) + (-> container + (get :objects) + (get shape-id))) + +(defn instantiate-component + [container component component-file position] + (let [component-shape (get-shape component (:id component)) + + orig-pos (gpt/point (:x component-shape) (:y component-shape)) + delta (gpt/subtract position orig-pos) + + objects (:objects container) + unames (volatile! (ctt/retrieve-used-names objects)) + + frame-id (ctt/frame-id-by-position objects (gpt/add orig-pos delta)) + + update-new-shape + (fn [new-shape original-shape] + (let [new-name (ctt/generate-unique-name @unames (:name new-shape))] + + (when (nil? (:parent-id original-shape)) + (vswap! unames conj new-name)) + + (cond-> new-shape + true + (as-> $ + (gsh/move $ delta) + (assoc $ :frame-id frame-id) + (assoc $ :parent-id + (or (:parent-id $) (:frame-id $))) + (dissoc $ :touched)) + + (nil? (:shape-ref original-shape)) + (assoc :shape-ref (:id original-shape)) + + (nil? (:parent-id original-shape)) + (assoc :component-id (:id original-shape) + :component-file component-file + :component-root? true + :name new-name) + + (some? (:parent-id original-shape)) + (dissoc :component-root?)))) + + [new-shape new-shapes _] + (ctt/clone-object component-shape + nil + (get component :objects) + update-new-shape)] + + [new-shape new-shapes])) + diff --git a/common/src/app/common/types/page.cljc b/common/src/app/common/types/page.cljc index 0ed513dc0d..b86092f35f 100644 --- a/common/src/app/common/types/page.cljc +++ b/common/src/app/common/types/page.cljc @@ -95,12 +95,6 @@ (s/def ::page (s/keys :req-un [::id ::name ::objects ::options])) -(s/def ::type #{:page :component}) -(s/def ::path (s/nilable string?)) -(s/def ::container - (s/keys :req-un [::id ::name ::objects] - :opt-un [::type ::path])) - ;; --- Helpers for flow (defn rename-flow diff --git a/common/src/app/common/types/pages_list.cljc b/common/src/app/common/types/pages_list.cljc new file mode 100644 index 0000000000..3f037ece25 --- /dev/null +++ b/common/src/app/common/types/pages_list.cljc @@ -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.pages-list + (:require + [app.common.data :as d])) + +(defn get-page + [file-data id] + (get-in file-data [:pages-index id])) + +(defn add-page + [file-data page] + (let [; It's legitimate to add a page that is already there, + ; for example in an idempotent changes operation. + conj-if-not-exists (fn [pages id] + (cond-> pages + (not (d/seek #(= % id) pages)) + (conj id)))] + (-> file-data + (update :pages conj-if-not-exists (:id page)) + (update :pages-index assoc (:id page) page)))) + diff --git a/common/src/app/common/types/shape_tree.cljc b/common/src/app/common/types/shape_tree.cljc new file mode 100644 index 0000000000..c9bcef5079 --- /dev/null +++ b/common/src/app/common/types/shape_tree.cljc @@ -0,0 +1,318 @@ +;; 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.shape-tree + (:require + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] + [app.common.math :as mth] + [app.common.pages.helpers :as cph] + [app.common.spec :as us] + [app.common.types.shape :as cts] + [app.common.uuid :as uuid] + [clojure.spec.alpha :as s])) + +(s/def ::objects (s/map-of uuid? ::cts/shape)) + +(defn add-shape + "Insert a shape in the tree, at the given index below the given parent or frame. + Update the parent as needed." + [id shape container frame-id parent-id index ignore-touched] + (let [update-parent-shapes + (fn [shapes] + ;; Ensure that shapes is always a vector. + (let [shapes (into [] shapes)] + (cond + (some #{id} shapes) + shapes + + (nil? index) + (conj shapes id) + + :else + (cph/insert-at-index shapes index [id])))) + + update-parent + (fn [parent] + (-> parent + (update :shapes update-parent-shapes) + (update :shapes d/vec-without-nils) + (cond-> (and (:shape-ref parent) + (not= (:id parent) frame-id) + (not ignore-touched)) + (-> (update :touched cph/set-touched-group :shapes-group) + (dissoc :remote-synced?))))) + + ;; TODO: this looks wrong, why we allow nil values? + update-objects + (fn [objects parent-id] + (if (and (or (nil? parent-id) (contains? objects parent-id)) + (or (nil? frame-id) (contains? objects frame-id))) + (-> objects + (assoc id (-> shape + (assoc :frame-id frame-id) + (assoc :parent-id parent-id) + (assoc :id id))) + (update parent-id update-parent)) + objects)) + + parent-id (or parent-id frame-id)] + + (update container :objects update-objects parent-id))) + +(defn get-frames + "Retrieves all frame objects as vector" + [objects] + (or (-> objects meta ::index-frames) + (let [lookup (d/getf objects) + xform (comp (remove #(= uuid/zero %)) + (keep lookup) + (filter cph/frame-shape?))] + (->> (keys objects) + (into [] xform))))) + +(defn get-frames-ids + "Retrieves all frame ids as vector" + [objects] + (->> (get-frames objects) + (mapv :id))) + +(defn get-nested-frames + [objects frame-id] + (into #{} + (comp (filter cph/frame-shape?) + (map :id)) + (cph/get-children objects frame-id))) + +(defn get-root-frames-ids + "Retrieves all frame objects as vector. It is not implemented in + function of `get-immediate-children` for performance reasons. This + function is executed in the render hot path." + [objects] + (let [add-frame + (fn [result shape] + (cond-> result + (cph/frame-shape? shape) + (conj (:id shape))))] + (cph/reduce-objects objects (complement cph/frame-shape?) add-frame []))) + +(defn get-root-objects + "Get all the objects under the root object" + [objects] + (let [add-shape + (fn [result shape] + (conj result shape))] + (cph/reduce-objects objects (complement cph/frame-shape?) add-shape []))) + +(defn get-root-shapes + "Get all shapes that are not frames" + [objects] + (let [add-shape + (fn [result shape] + (cond-> result + (not (cph/frame-shape? shape)) + (conj shape)))] + (cph/reduce-objects objects (complement cph/frame-shape?) add-shape []))) + +(defn get-root-shapes-ids + [objects] + (->> (get-root-shapes objects) + (mapv :id))) + +(defn get-base + [objects id-a id-b] + + (let [parents-a (reverse (cph/get-parents-seq objects id-a)) + parents-b (reverse (cph/get-parents-seq objects id-b)) + + [base base-child-a base-child-b] + (loop [parents-a (rest parents-a) + parents-b (rest parents-b) + base uuid/zero] + (cond + (not= (first parents-a) (first parents-b)) + [base (first parents-a) (first parents-b)] + + (or (empty? parents-a) (empty? parents-b)) + [uuid/zero (first parents-a) (first parents-b)] + + :else + (recur (rest parents-a) (rest parents-b) (first parents-a)))) + + index-base-a (when base-child-a (cph/get-position-on-parent objects base-child-a)) + index-base-b (when base-child-b (cph/get-position-on-parent objects base-child-b))] + + [base index-base-a index-base-b])) + + +(defn is-shape-over-shape? + [objects base-shape-id over-shape-id {:keys [top-frames?]}] + + (let [[base index-a index-b] (get-base objects base-shape-id over-shape-id)] + (cond + (= base base-shape-id) + (and (not top-frames?) + (cph/frame-shape? objects base-shape-id) + (cph/root-frame? objects base-shape-id)) + + (= base over-shape-id) + (or top-frames? + (not (cph/frame-shape? objects over-shape-id)) + (not (cph/root-frame? objects over-shape-id))) + + :else + (< index-a index-b)))) + +(defn sort-z-index + ([objects ids] + (sort-z-index objects ids nil)) + + ([objects ids {:keys [bottom-frames?] :as options}] + (letfn [(comp [id-a id-b] + (let [type-a (dm/get-in objects [id-a :type]) + type-b (dm/get-in objects [id-b :type])] + (cond + (and bottom-frames? (= :frame type-a) (not= :frame type-b)) + 1 + + (and bottom-frames? (not= :frame type-a) (= :frame type-b)) + -1 + + (= id-a id-b) + 0 + + (is-shape-over-shape? objects id-a id-b options) + 1 + + :else + -1)))] + (sort comp ids)))) + +(defn frame-id-by-position + [objects position] + (assert (gpt/point? position)) + (let [top-frame + (->> (get-frames-ids objects) + (sort-z-index objects) + (d/seek #(and position (gsh/has-point? (get objects %) position))))] + (or top-frame uuid/zero))) + +(defn frame-by-position + [objects position] + (let [frame-id (frame-id-by-position objects position)] + (get objects frame-id))) + +(defn get-viewer-frames + ([objects] + (get-viewer-frames objects nil)) + + ([objects {:keys [all-frames?]}] + (into [] + (comp (map (d/getf objects)) + (if all-frames? + identity + (remove :hide-in-viewer))) + (sort-z-index objects (get-frames-ids objects) {:top-frames? true})))) + +(defn start-page-index + [objects] + (with-meta objects {::index-frames (get-frames (with-meta objects nil))})) + +(defn update-page-index + [objects] + (with-meta objects {::index-frames (get-frames (with-meta objects nil))})) + +(defn start-object-indices + [file] + (letfn [(process-index [page-index page-id] + (update-in page-index [page-id :objects] start-page-index))] + (update file :pages-index #(reduce process-index % (keys %))))) + +(defn update-object-indices + [file page-id] + (update-in file [:pages-index page-id :objects] update-page-index)) + +(defn rotated-frame? + [frame] + (not (mth/almost-zero? (:rotation frame 0)))) + +(defn retrieve-used-names + [objects] + (into #{} (comp (map :name) (remove nil?)) (vals objects))) + +(defn- extract-numeric-suffix + [basename] + (if-let [[_ p1 p2] (re-find #"(.*)-([0-9]+)$" basename)] + [p1 (+ 1 (d/parse-integer p2))] + [basename 1])) + +(defn generate-unique-name + "A unique name generator" + [used basename] + (s/assert ::us/set-of-string used) + (s/assert ::us/string basename) + (if-not (contains? used basename) + basename + (let [[prefix initial] (extract-numeric-suffix basename)] + (loop [counter initial] + (let [candidate (str prefix "-" counter)] + (if (contains? used candidate) + (recur (inc counter)) + candidate)))))) + +(defn clone-object + "Gets a copy of the object and all its children, with new ids + and with the parent-children links correctly set. Admits functions + to make more transformations to the cloned objects and the + original ones. + + Returns the cloned object, the list of all new objects (including + the cloned one), and possibly a list of original objects modified." + + ([object parent-id objects update-new-object] + (clone-object object parent-id objects update-new-object identity)) + + ([object parent-id objects update-new-object update-original-object] + (let [new-id (uuid/next)] + (loop [child-ids (seq (:shapes object)) + new-direct-children [] + new-children [] + updated-children []] + + (if (empty? child-ids) + (let [new-object (cond-> object + true + (assoc :id new-id + :parent-id parent-id) + + (some? (:shapes object)) + (assoc :shapes (mapv :id new-direct-children))) + + new-object (update-new-object new-object object) + new-objects (into [new-object] new-children) + + updated-object (update-original-object object new-object) + updated-objects (if (identical? object updated-object) + updated-children + (into [updated-object] updated-children))] + + [new-object new-objects updated-objects]) + + (let [child-id (first child-ids) + child (get objects child-id) + _ (us/assert some? child) + + [new-child new-child-objects updated-child-objects] + (clone-object child new-id objects update-new-object update-original-object)] + + (recur + (next child-ids) + (into new-direct-children [new-child]) + (into new-children new-child-objects) + (into updated-children updated-child-objects)))))))) + diff --git a/frontend/src/app/main/data/viewer.cljs b/frontend/src/app/main/data/viewer.cljs index 54c0422221..896c493904 100644 --- a/frontend/src/app/main/data/viewer.cljs +++ b/frontend/src/app/main/data/viewer.cljs @@ -10,6 +10,7 @@ [app.common.geom.point :as gpt] [app.common.pages.helpers :as cph] [app.common.spec :as us] + [app.common.types.shape-tree :as ctt] [app.common.types.shape.interactions :as ctsi] [app.main.data.comments :as dcm] [app.main.data.fonts :as df] @@ -116,8 +117,8 @@ (map (fn [page-id] (let [data (get-in file [:data :pages-index page-id])] [page-id (assoc data - :frames (cph/get-viewer-frames (:objects data)) - :all-frames (cph/get-viewer-frames (:objects data) {:all-frames? true}))]))) + :frames (ctt/get-viewer-frames (:objects data)) + :all-frames (ctt/get-viewer-frames (:objects data) {:all-frames? true}))]))) (into {}))] (ptk/reify ::bundle-fetched diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 62dc383ad6..d778cdb0bc 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -21,6 +21,7 @@ [app.common.text :as txt] [app.common.transit :as t] [app.common.types.shape :as cts] + [app.common.types.shape-tree :as ctst] [app.common.uuid :as uuid] [app.config :as cfg] [app.main.data.events :as ev] @@ -59,7 +60,6 @@ [app.util.globals :as ug] [app.util.http :as http] [app.util.i18n :as i18n] - [app.util.names :as un] [app.util.router :as rt] [app.util.timers :as tm] [app.util.webapi :as wapi] @@ -157,7 +157,7 @@ :workspace-project project :workspace-file (assoc file :initialized true) :workspace-data (-> (:data file) - (cph/start-object-indices) + (ctst/start-object-indices) ;; DEBUG: Uncomment this to try out migrations in local without changing ;; the version number #_(assoc :version 17) @@ -270,8 +270,8 @@ ptk/WatchEvent (watch [it state _] (let [pages (get-in state [:workspace-data :pages-index]) - unames (un/retrieve-used-names pages) - name (un/generate-unique-name unames "Page-1") + unames (ctst/retrieve-used-names pages) + name (ctst/generate-unique-name unames "Page-1") changes (-> (pcb/empty-changes it) (pcb/add-empty-page id name))] @@ -285,9 +285,9 @@ (watch [it state _] (let [id (uuid/next) pages (get-in state [:workspace-data :pages-index]) - unames (un/retrieve-used-names pages) + unames (ctst/retrieve-used-names pages) page (get-in state [:workspace-data :pages-index page-id]) - name (un/generate-unique-name unames (:name page)) + name (ctst/generate-unique-name unames (:name page)) no_thumbnails_objects (->> (:objects page) (d/mapm (fn [_ val] (dissoc val :use-for-thumbnail?)))) @@ -991,7 +991,7 @@ (let [selected (wsh/lookup-selected state) pages (-> state :workspace-data :pages-index vals) get-frames (fn [{:keys [objects id] :as page}] - (->> (cph/get-frames objects) + (->> (ctst/get-frames objects) (sequence (comp (filter :use-for-thumbnail?) (map :id) @@ -1223,7 +1223,7 @@ ;; selected and its parents objects (cph/selected-subtree objects selected) - selected (->> (cph/sort-z-index objects selected) + selected (->> (ctst/sort-z-index objects selected) (into (d/ordered-set)))] (assoc data :selected selected))) @@ -1478,7 +1478,7 @@ [frame-id frame-id delta]) (empty? page-selected) - (let [frame-id (cph/frame-id-by-position page-objects mouse-pos) + (let [frame-id (ctst/frame-id-by-position page-objects mouse-pos) delta (gpt/subtract mouse-pos orig-pos)] [frame-id frame-id delta]) @@ -1590,7 +1590,7 @@ height 16 page-id (:current-page-id state) frame-id (-> (wsh/lookup-page-objects state page-id) - (cph/frame-id-by-position @ms/mouse-position)) + (ctst/frame-id-by-position @ms/mouse-position)) shape (cp/setup-rect-selrect {:id id :type :text diff --git a/frontend/src/app/main/data/workspace/bool.cljs b/frontend/src/app/main/data/workspace/bool.cljs index 771c06bb26..70fe8d2224 100644 --- a/frontend/src/app/main/data/workspace/bool.cljs +++ b/frontend/src/app/main/data/workspace/bool.cljs @@ -11,11 +11,11 @@ [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] [app.common.path.shapes-to-path :as stp] + [app.common.types.shape-tree :as ctt] [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.selection :as dws] [app.main.data.workspace.state-helpers :as wsh] - [app.util.names :as un] [beicon.core :as rx] [cuerdas.core :as str] [potok.core :as ptk])) @@ -90,8 +90,8 @@ (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state) base-name (-> bool-type d/name str/capital (str "-1")) - name (-> (un/retrieve-used-names objects) - (un/generate-unique-name base-name)) + name (-> (ctt/retrieve-used-names objects) + (ctt/generate-unique-name base-name)) shapes (selected-shapes state)] (when-not (empty? shapes) diff --git a/frontend/src/app/main/data/workspace/changes.cljs b/frontend/src/app/main/data/workspace/changes.cljs index 209c8f5492..a1f4da573e 100644 --- a/frontend/src/app/main/data/workspace/changes.cljs +++ b/frontend/src/app/main/data/workspace/changes.cljs @@ -13,6 +13,7 @@ [app.common.pages.changes-spec :as pcs] [app.common.pages.helpers :as cph] [app.common.spec :as us] + [app.common.types.shape-tree :as ctst] [app.common.uuid :as uuid] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.undo :as dwu] @@ -165,7 +166,7 @@ (update-in state path (fn [file] (-> file (cp/process-changes redo-changes false) - (cph/update-object-indices page-id)))) + (ctst/update-object-indices page-id)))) (catch :default err (log/error :js/error err) diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs index 6f9ac6d350..39724ee8b7 100644 --- a/frontend/src/app/main/data/workspace/common.cljs +++ b/frontend/src/app/main/data/workspace/common.cljs @@ -7,6 +7,15 @@ (ns app.main.data.workspace.common (:require [app.common.logging :as log] + [app.common.pages :as cp] + [app.common.pages.changes-builder :as pcb] + [app.common.pages.helpers :as cph] + [app.common.spec :as us] + [app.common.types.page :as ctp] + [app.common.types.shape :as cts] + [app.common.types.shape-tree :as ctt] + [app.common.types.shape.interactions :as ctsi] + [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.undo :as dwu] [app.main.worker :as uw] diff --git a/frontend/src/app/main/data/workspace/drawing/box.cljs b/frontend/src/app/main/data/workspace/drawing/box.cljs index bdffbe1f70..aa550f98c5 100644 --- a/frontend/src/app/main/data/workspace/drawing/box.cljs +++ b/frontend/src/app/main/data/workspace/drawing/box.cljs @@ -11,6 +11,7 @@ [app.common.math :as mth] [app.common.pages :as cp] [app.common.pages.helpers :as cph] + [app.common.types.shape-tree :as ctt] [app.common.uuid :as uuid] [app.main.data.workspace.drawing.common :as common] [app.main.data.workspace.state-helpers :as wsh] @@ -65,7 +66,7 @@ focus (:workspace-focus-selected state) zoom (get-in state [:workspace-local :zoom] 1) - fid (cph/frame-id-by-position objects initial) + fid (ctt/frame-id-by-position objects initial) shape (get-in state [:workspace-drawing :object]) shape (-> shape diff --git a/frontend/src/app/main/data/workspace/drawing/curve.cljs b/frontend/src/app/main/data/workspace/drawing/curve.cljs index d60cc040c9..66062fd166 100644 --- a/frontend/src/app/main/data/workspace/drawing/curve.cljs +++ b/frontend/src/app/main/data/workspace/drawing/curve.cljs @@ -8,7 +8,7 @@ (:require [app.common.geom.shapes :as gsh] [app.common.geom.shapes.path :as gsp] - [app.common.pages.helpers :as cph] + [app.common.types.shape-tree :as ctt] [app.main.data.workspace.drawing.common :as common] [app.main.data.workspace.state-helpers :as wsh] [app.main.streams :as ms] @@ -47,7 +47,7 @@ (let [objects (wsh/lookup-page-objects state) content (get-in state [:workspace-drawing :object :content] []) position (get-in content [0 :params] nil) - frame-id (cph/frame-id-by-position objects position)] + frame-id (ctt/frame-id-by-position objects position)] (-> state (assoc-in [:workspace-drawing :object :frame-id] frame-id)))))) diff --git a/frontend/src/app/main/data/workspace/groups.cljs b/frontend/src/app/main/data/workspace/groups.cljs index 2b315fc5e1..d5533bc989 100644 --- a/frontend/src/app/main/data/workspace/groups.cljs +++ b/frontend/src/app/main/data/workspace/groups.cljs @@ -11,10 +11,10 @@ [app.common.pages :as cp] [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] + [app.common.types.shape-tree :as ctt] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.selection :as dws] [app.main.data.workspace.state-helpers :as wsh] - [app.util.names :as un] [beicon.core :as rx] [potok.core :as ptk])) @@ -71,8 +71,8 @@ (= (count shapes) 1) (= (:type (first shapes)) :group)) (:name (first shapes)) - (-> (un/retrieve-used-names objects) - (un/generate-unique-name base-name))) + (-> (ctt/retrieve-used-names objects) + (ctt/generate-unique-name base-name))) selrect (gsh/selection-rect shapes) group (-> (cp/make-minimal-group frame-id selrect gname) diff --git a/frontend/src/app/main/data/workspace/interactions.cljs b/frontend/src/app/main/data/workspace/interactions.cljs index 40c6a80f9e..e1a653be75 100644 --- a/frontend/src/app/main/data/workspace/interactions.cljs +++ b/frontend/src/app/main/data/workspace/interactions.cljs @@ -12,12 +12,12 @@ [app.common.pages.helpers :as cph] [app.common.spec :as us] [app.common.types.page :as ctp] + [app.common.types.shape-tree :as ctst] [app.common.types.shape.interactions :as ctsi] [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.state-helpers :as wsh] [app.main.streams :as ms] - [app.util.names :as un] [beicon.core :as rx] [potok.core :as ptk])) @@ -32,7 +32,7 @@ flows (get-in page [:options :flows] []) unames (into #{} (map :name flows)) - name (un/generate-unique-name unames "Flow-1") + name (ctst/generate-unique-name unames "Flow-1") new-flow {:id (uuid/next) :name name @@ -182,7 +182,7 @@ from-frame-id (if (cph/frame-shape? from-shape) from-id (:frame-id from-shape)) - target-frame (cph/frame-by-position objects position)] + target-frame (ctst/frame-by-position objects position)] (when (and (not= (:id target-frame) uuid/zero) (not= (:id target-frame) from-frame-id) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 6e4fa70ca2..9a8acafb08 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -15,7 +15,9 @@ [app.common.pages.helpers :as cph] [app.common.spec :as us] [app.common.types.color :as ctc] + [app.common.types.container :as ctn] [app.common.types.file :as ctf] + [app.common.types.shape-tree :as ctst] [app.common.types.typography :as ctt] [app.common.uuid :as uuid] [app.main.data.dashboard :as dd] @@ -30,7 +32,6 @@ [app.main.repo :as rp] [app.main.store :as st] [app.util.i18n :refer [tr]] - [app.util.names :as un] [app.util.router :as rt] [app.util.time :as dt] [beicon.core :as rx] @@ -352,7 +353,7 @@ component (cph/get-component libraries id) all-components (-> state :workspace-data :components vals) unames (into #{} (map :name) all-components) - new-name (un/generate-unique-name unames (:name component)) + new-name (ctst/generate-unique-name unames (:name component)) [new-shape new-shapes _updated-shapes main-instance main-instance-page] (dwlh/duplicate-component component) @@ -525,7 +526,7 @@ libraries (wsh/get-libraries state) container (cph/get-container local-file :page page-id) - shape (cph/get-shape container id) + shape (ctn/get-shape container id) changes (-> (pcb/empty-changes it) diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index 1c74d9d1ee..2914f4095a 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -16,9 +16,10 @@ [app.common.spec :as us] [app.common.text :as txt] [app.common.types.color :as ctc] + [app.common.types.container :as ctn] + [app.common.types.shape-tree :as ctst] [app.main.data.workspace.groups :as dwg] [app.main.data.workspace.state-helpers :as wsh] - [app.util.names :as un] [cljs.spec.alpha :as s] [clojure.set :as set])) @@ -93,7 +94,7 @@ (some? (:parent-id new-shape)) (dissoc :component-root?)))] - (cph/clone-object shape nil objects update-new-shape update-original-shape))) + (ctst/clone-object shape nil objects update-new-shape update-original-shape))) (defn generate-add-component "If there is exactly one id, and it's a group, use it as root. Otherwise, @@ -131,7 +132,7 @@ ids from all of them." [component] (let [component-root (cph/get-component-root component)] - (cph/clone-object component-root + (ctst/clone-object component-root nil (get component :objects) identity))) @@ -140,49 +141,9 @@ "Generate changes to create a new instance from a component." [it file-id component-id position page libraries] (let [component (cph/get-component libraries file-id component-id) - component-shape (cph/get-shape component component-id) - orig-pos (gpt/point (:x component-shape) (:y component-shape)) - delta (gpt/subtract position orig-pos) - - objects (:objects page) - unames (volatile! (un/retrieve-used-names objects)) - - frame-id (cph/frame-id-by-position objects (gpt/add orig-pos delta)) - - update-new-shape - (fn [new-shape original-shape] - (let [new-name (un/generate-unique-name @unames (:name new-shape))] - - (when (nil? (:parent-id original-shape)) - (vswap! unames conj new-name)) - - (cond-> new-shape - true - (as-> $ - (gsh/move $ delta) - (assoc $ :frame-id frame-id) - (assoc $ :parent-id - (or (:parent-id $) (:frame-id $))) - (dissoc $ :touched)) - - (nil? (:shape-ref original-shape)) - (assoc :shape-ref (:id original-shape)) - - (nil? (:parent-id original-shape)) - (assoc :component-id (:id original-shape) - :component-file file-id - :component-root? true - :name new-name) - - (some? (:parent-id original-shape)) - (dissoc :component-root?)))) - - [new-shape new-shapes _] - (cph/clone-object component-shape - nil - (get component :objects) - update-new-shape) + [new-shape new-shapes] + (ctn/instantiate-component page component file-id position) changes (reduce #(pcb/add-object %1 %2 {:ignore-touched true}) (pcb/empty-changes it (:id page)) @@ -484,12 +445,12 @@ instance, and all its children, from the given component." [changes libraries container shape-id reset?] (log/debug :msg "Sync shape direct" :shape (str shape-id) :reset? reset?) - (let [shape-inst (cph/get-shape container shape-id) + (let [shape-inst (ctn/get-shape container shape-id) component (cph/get-component libraries (:component-file shape-inst) (:component-id shape-inst)) shape-main (when component - (cph/get-shape component (:shape-ref shape-inst))) + (ctn/get-shape component (:shape-ref shape-inst))) initial-root? (:component-root? shape-inst) @@ -545,9 +506,9 @@ set-remote-synced? (change-remote-synced shape-inst container true)) - children-inst (mapv #(cph/get-shape container %) + children-inst (mapv #(ctn/get-shape container %) (:shapes shape-inst)) - children-main (mapv #(cph/get-shape component %) + children-main (mapv #(ctn/get-shape component %) (:shapes shape-main)) only-inst (fn [changes child-inst] @@ -610,11 +571,11 @@ the values in the shape and all its children." [changes libraries container shape-id] (log/debug :msg "Sync shape inverse" :shape (str shape-id)) - (let [shape-inst (cph/get-shape container shape-id) + (let [shape-inst (ctn/get-shape container shape-id) component (cph/get-component libraries (:component-file shape-inst) (:component-id shape-inst)) - shape-main (cph/get-shape component (:shape-ref shape-inst)) + shape-main (ctn/get-shape component (:shape-ref shape-inst)) initial-root? (:component-root? shape-inst) @@ -670,9 +631,9 @@ set-remote-synced? (change-remote-synced shape-inst container true)) - children-inst (mapv #(cph/get-shape container %) + children-inst (mapv #(ctn/get-shape container %) (:shapes shape-inst)) - children-main (mapv #(cph/get-shape component %) + children-main (mapv #(ctn/get-shape component %) (:shapes shape-main)) only-inst (fn [changes child-inst] @@ -787,7 +748,7 @@ (defn- add-shape-to-instance [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 (cph/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 %) (cph/get-children-with-self (:objects container) (:id root-instance))) @@ -813,7 +774,7 @@ original-shape) [_ new-shapes _] - (cph/clone-object component-shape + (ctst/clone-object component-shape (:id parent-shape) (get component :objects) update-new-shape @@ -855,7 +816,7 @@ (defn- add-shape-to-main [changes shape index component page root-instance root-main] (log/info :msg (str "ADD [C] " (:name shape))) - (let [parent-shape (cph/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) (cph/get-children-with-self (:objects component) (:id root-main))) @@ -875,7 +836,7 @@ original-shape)) [_new-shape new-shapes updated-shapes] - (cph/clone-object shape + (ctst/clone-object shape (:id component-parent-shape) (get page :objects) update-new-shape @@ -982,7 +943,7 @@ index-before " -> " index-after)) - (let [parent (cph/get-shape container (:parent-id shape)) + (let [parent (ctn/get-shape container (:parent-id shape)) changes' (-> changes (update :redo-changes conj (make-change diff --git a/frontend/src/app/main/data/workspace/path/drawing.cljs b/frontend/src/app/main/data/workspace/path/drawing.cljs index ff61c094a1..fe2f4f55e5 100644 --- a/frontend/src/app/main/data/workspace/path/drawing.cljs +++ b/frontend/src/app/main/data/workspace/path/drawing.cljs @@ -8,10 +8,10 @@ (:require [app.common.geom.point :as gpt] [app.common.geom.shapes.path :as upg] - [app.common.pages.helpers :as cph] [app.common.path.commands :as upc] [app.common.path.shapes-to-path :as upsp] [app.common.spec :as us] + [app.common.types.shape-tree :as ctt] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.drawing.common :as dwdc] [app.main.data.workspace.edition :as dwe] @@ -258,7 +258,7 @@ (let [objects (wsh/lookup-page-objects state) content (get-in state [:workspace-drawing :object :content] []) position (get-in content [0 :params] nil) - frame-id (cph/frame-id-by-position objects position)] + frame-id (ctt/frame-id-by-position objects position)] (-> state (assoc-in [:workspace-drawing :object :frame-id] frame-id)))))) diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index dbc2a68ba8..f26c3400ed 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -15,6 +15,7 @@ [app.common.pages.helpers :as cph] [app.common.spec :as us] [app.common.types.page :as ctp] + [app.common.types.shape-tree :as ctt] [app.common.types.shape.interactions :as ctsi] [app.common.uuid :as uuid] [app.main.data.modal :as md] @@ -26,7 +27,6 @@ [app.main.refs :as refs] [app.main.streams :as ms] [app.main.worker :as uw] - [app.util.names :as un] [beicon.core :as rx] [cljs.spec.alpha :as s] [clojure.set :as set] @@ -284,7 +284,7 @@ move to the desired position, and recalculate parents and frames as needed." [all-objects page ids delta it] (let [shapes (map (d/getf all-objects) ids) - unames (volatile! (un/retrieve-used-names (:objects page))) + unames (volatile! (ctt/retrieve-used-names (:objects page))) update-unames! (fn [new-name] (vswap! unames conj new-name)) all-ids (reduce #(into %1 (cons %2 (cph/get-children-ids all-objects %2))) (d/ordered-set) ids) ids-map (into {} (map #(vector % (uuid/next))) all-ids) @@ -319,7 +319,7 @@ (defn- prepare-duplicate-frame-change [changes objects page unames update-unames! ids-map obj delta] (let [new-id (ids-map (:id obj)) - frame-name (un/generate-unique-name @unames (:name obj)) + frame-name (ctt/generate-unique-name @unames (:name obj)) _ (update-unames! frame-name) new-frame (-> obj @@ -354,7 +354,7 @@ (if (some? obj) (let [new-id (ids-map (:id obj)) parent-id (or parent-id frame-id) - name (un/generate-unique-name @unames (:name obj)) + name (ctt/generate-unique-name @unames (:name obj)) _ (update-unames! name) new-obj (-> obj @@ -395,7 +395,7 @@ (let [update-flows (fn [flows] (reduce (fn [flows frame] - (let [name (un/generate-unique-name @unames "Flow-1") + (let [name (ctt/generate-unique-name @unames "Flow-1") _ (vswap! unames conj name) new-flow {:id (uuid/next) :name name diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index 77d526f4b7..237ce60294 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -16,6 +16,7 @@ [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.uuid :as uuid] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.edition :as dwe] @@ -23,7 +24,6 @@ [app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.state-helpers :as wsh] [app.main.streams :as ms] - [app.util.names :as un] [beicon.core :as rx] [cljs.spec.alpha :as s] [potok.core :as ptk])) @@ -84,8 +84,8 @@ id (or (:id attrs) (uuid/next)) name (-> objects - (un/retrieve-used-names) - (un/generate-unique-name (:name attrs))) + (ctst/retrieve-used-names) + (ctst/generate-unique-name (:name attrs))) shape (make-new-shape (assoc attrs :id id :name name) diff --git a/frontend/src/app/main/data/workspace/svg_upload.cljs b/frontend/src/app/main/data/workspace/svg_upload.cljs index 3c830bd59b..683bc946fd 100644 --- a/frontend/src/app/main/data/workspace/svg_upload.cljs +++ b/frontend/src/app/main/data/workspace/svg_upload.cljs @@ -13,15 +13,14 @@ [app.common.geom.shapes :as gsh] [app.common.pages :as cp] [app.common.pages.changes-builder :as pcb] - [app.common.pages.helpers :as cph] [app.common.spec :refer [max-safe-int min-safe-int]] + [app.common.types.shape-tree :as ctt] [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.selection :as dws] [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.state-helpers :as wsh] [app.util.color :as uc] - [app.util.names :as un] [app.util.path.parser :as upp] [app.util.svg :as usvg] [beicon.core :as rx] @@ -360,7 +359,7 @@ (let [{:keys [tag attrs hidden]} element-data attrs (usvg/format-styles attrs) element-data (cond-> element-data (map? element-data) (assoc :attrs attrs)) - name (un/generate-unique-name unames (or (:id attrs) (tag->name tag))) + name (ctt/generate-unique-name unames (or (:id attrs) (tag->name tag))) att-refs (usvg/find-attr-references attrs) references (usvg/find-def-references (:defs svg-data) att-refs) @@ -437,17 +436,17 @@ (try (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) - frame-id (cph/frame-id-by-position objects position) + frame-id (ctt/frame-id-by-position objects position) selected (wsh/lookup-selected state) [vb-x vb-y vb-width vb-height] (svg-dimensions svg-data) x (- x vb-x (/ vb-width 2)) y (- y vb-y (/ vb-height 2)) - unames (un/retrieve-used-names objects) + unames (ctt/retrieve-used-names objects) svg-name (->> (str/replace (:name svg-data) ".svg" "") - (un/generate-unique-name unames)) + (ctt/generate-unique-name unames)) svg-data (-> svg-data (assoc :x x diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index e8e9268fa5..34b2ef9200 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -16,6 +16,7 @@ [app.common.pages.common :as cpc] [app.common.pages.helpers :as cph] [app.common.spec :as us] + [app.common.types.shape-tree :as ctt] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.collapse :as dwc] [app.main.data.workspace.guides :as dwg] @@ -752,7 +753,7 @@ (let [position @ms/mouse-position page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) - frame-id (cph/frame-id-by-position objects position) + frame-id (ctt/frame-id-by-position objects position) moving-shapes (->> ids diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 46302a4a13..35edbc26c7 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -10,6 +10,7 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.pages.helpers :as cph] + [app.common.types.shape-tree :as ctt] [app.main.data.workspace.state-helpers :as wsh] [app.main.store :as st] [okulary.core :as l])) @@ -284,7 +285,7 @@ (l/derived :options workspace-page)) (def workspace-frames - (l/derived cph/get-frames workspace-page-objects =)) + (l/derived ctt/get-frames workspace-page-objects =)) (def workspace-editor (l/derived :workspace-editor st/state)) diff --git a/frontend/src/app/main/render.cljs b/frontend/src/app/main/render.cljs index 240a442018..66b8ee2c46 100644 --- a/frontend/src/app/main/render.cljs +++ b/frontend/src/app/main/render.cljs @@ -21,6 +21,7 @@ [app.common.geom.shapes.bounds :as gsb] [app.common.math :as mth] [app.common.pages.helpers :as cph] + [app.common.types.shape-tree :as ctst] [app.config :as cfg] [app.main.fonts :as fonts] [app.main.ui.context :as muc] @@ -61,7 +62,7 @@ (defn- calculate-dimensions [objects] (let [bounds - (->> (cph/get-root-objects objects) + (->> (ctst/get-root-objects objects) (map (partial gsb/get-object-bounds objects)) (gsh/join-rects))] (-> bounds diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs index 18035e283e..5efd823323 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs @@ -10,6 +10,7 @@ [app.common.data.macros :as dm] [app.common.pages.helpers :as cph] [app.common.types.page :as ctp] + [app.common.types.shape-tree :as ctt] [app.common.types.shape.interactions :as ctsi] [app.common.uuid :as uuid] [app.main.data.workspace :as dw] @@ -182,7 +183,7 @@ (let [objects (deref refs/workspace-page-objects) destination (get objects (:destination interaction)) - frames (mf/with-memo [objects] (cph/get-viewer-frames objects {:all-frames? (not= :navigate (:action-type interaction))})) + frames (mf/with-memo [objects] (ctt/get-viewer-frames objects {:all-frames? (not= :navigate (:action-type interaction))})) overlay-pos-type (:overlay-pos-type interaction) close-click-outside? (:close-click-outside interaction false) diff --git a/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs b/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs index ece6b20b22..6f16912839 100644 --- a/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs @@ -9,7 +9,7 @@ [app.common.data :as d] [app.common.geom.shapes :as gsh] [app.common.math :as mth] - [app.common.pages.helpers :as cph] + [app.common.types.shape-tree :as ctst] [app.common.uuid :as uuid] [app.main.refs :as refs] [app.util.geom.grid :as gg] @@ -134,7 +134,7 @@ [:g.grid-display {:style {:pointer-events "none"}} (for [frame frames] (when (and (not (is-transform? frame)) - (not (cph/rotated-frame? frame)) + (not (ctst/rotated-frame? frame)) (or (empty? focus) (contains? focus (:id frame)))) [:& grid-display-frame {:key (str "grid-" (:id frame)) :zoom zoom diff --git a/frontend/src/app/main/ui/workspace/viewport/guides.cljs b/frontend/src/app/main/ui/workspace/viewport/guides.cljs index ed338e282e..067db23031 100644 --- a/frontend/src/app/main/ui/workspace/viewport/guides.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/guides.cljs @@ -11,6 +11,7 @@ [app.common.geom.shapes :as gsh] [app.common.math :as mth] [app.common.pages.helpers :as cph] + [app.common.types.shape-tree :as ctst] [app.common.uuid :as uuid] [app.main.data.workspace :as dw] [app.main.refs :as refs] @@ -292,7 +293,7 @@ (when (or (nil? frame) (and (cph/root-frame? frame) - (not (cph/rotated-frame? frame)))) + (not (ctst/rotated-frame? frame)))) [:g.guide-area {:opacity (when frame-guide-outside? 0)} (when-not disabled-guides? (let [{:keys [x y width height]} (guide-area-axis pos vbox zoom frame axis)] diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index a6f4881490..2909ab0b4d 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -10,6 +10,7 @@ [app.common.geom.shapes :as gsh] [app.common.pages :as cp] [app.common.pages.helpers :as cph] + [app.common.types.shape-tree :as ctt] [app.main.data.shortcuts :as dsc] [app.main.data.workspace :as dw] [app.main.data.workspace.path.shortcuts :as psc] @@ -183,7 +184,7 @@ ids (into (d/ordered-set) - (cph/sort-z-index objects ids {:bottom-frames? mod?})) + (ctt/sort-z-index objects ids {:bottom-frames? mod?})) grouped? (fn [id] (contains? #{:group :bool} (get-in objects [id :type]))) @@ -218,7 +219,7 @@ (let [root-frame-ids (mf/use-memo (mf/deps objects) - #(cph/get-root-shapes-ids objects)) + #(ctt/get-root-shapes-ids objects)) modifiers (select-keys modifiers root-frame-ids)] (sfd/use-dynamic-modifiers objects globals/document modifiers))) @@ -229,7 +230,7 @@ (defn setup-active-frames [objects hover-ids selected active-frames zoom transform vbox] - (let [all-frames (mf/use-memo (mf/deps objects) #(cph/get-root-frames-ids objects)) + (let [all-frames (mf/use-memo (mf/deps objects) #(ctt/get-root-frames-ids objects)) selected-frames (mf/use-memo (mf/deps selected) #(->> all-frames (filter selected))) xf-selected-frame (comp (remove cph/root-frame?) diff --git a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs index 56725968f8..1ae92aa12a 100644 --- a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs @@ -10,7 +10,7 @@ [app.common.data.macros :as dm] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] - [app.common.pages.helpers :as cph] + [app.common.types.shape-tree :as ctt] [app.common.uuid :as uuid] [app.main.data.workspace :as dw] [app.main.data.workspace.interactions :as dwi] @@ -178,7 +178,7 @@ on-frame-enter (unchecked-get props "on-frame-enter") on-frame-leave (unchecked-get props "on-frame-leave") on-frame-select (unchecked-get props "on-frame-select") - frames (cph/get-frames objects)] + frames (ctt/get-frames objects)] [:g.frame-titles (for [frame frames] diff --git a/frontend/src/app/util/geom/snap_points.cljs b/frontend/src/app/util/geom/snap_points.cljs index 1940deaf79..8a7a1cbe66 100644 --- a/frontend/src/app/util/geom/snap_points.cljs +++ b/frontend/src/app/util/geom/snap_points.cljs @@ -8,7 +8,8 @@ (:require [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.shape-tree :as ctst])) (defn selrect-snap-points [{:keys [x y width height] :as selrect}] #{(gpt/point x y) @@ -38,7 +39,7 @@ (cond (and (some? frame) - (not (cph/rotated-frame? frame)) + (not (ctst/rotated-frame? frame)) (not (cph/root-frame? frame))) #{} diff --git a/frontend/src/app/util/names.cljs b/frontend/src/app/util/names.cljs deleted file mode 100644 index 6a2288fcdb..0000000000 --- a/frontend/src/app/util/names.cljs +++ /dev/null @@ -1,38 +0,0 @@ -;; 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.util.names - (:require - [app.common.data :as d] - [app.common.spec :as us] - [cljs.spec.alpha :as s])) - -(s/def ::set-of-string (s/every string? :kind set?)) - -(defn- extract-numeric-suffix - [basename] - (if-let [[_ p1 p2] (re-find #"(.*)-([0-9]+)$" basename)] - [p1 (+ 1 (d/parse-integer p2))] - [basename 1])) - -(defn retrieve-used-names - [objects] - (into #{} (comp (map :name) (remove nil?)) (vals objects))) - -(defn generate-unique-name - "A unique name generator" - [used basename] - (s/assert ::set-of-string used) - (s/assert ::us/string basename) - (if-not (contains? used basename) - basename - (let [[prefix initial] (extract-numeric-suffix basename)] - (loop [counter initial] - (let [candidate (str prefix "-" counter)] - (if (contains? used candidate) - (recur (inc counter)) - candidate)))))) - diff --git a/frontend/src/app/util/snap_data.cljs b/frontend/src/app/util/snap_data.cljs index 81f60b9520..916f1b9925 100644 --- a/frontend/src/app/util/snap_data.cljs +++ b/frontend/src/app/util/snap_data.cljs @@ -12,6 +12,7 @@ [app.common.data :as d] [app.common.pages.diff :as diff] [app.common.pages.helpers :as cph] + [app.common.types.shape-tree :as ctst] [app.common.uuid :as uuid] [app.util.geom.grid :as gg] [app.util.geom.snap-points :as snap] @@ -55,7 +56,7 @@ (defn get-grids-snap-points [frame coord] - (if (not (cph/rotated-frame? frame)) + (if (not (ctst/rotated-frame? frame)) [] (let [grid->snap (fn [[grid-type position]] {:type :layout @@ -196,7 +197,7 @@ (defn add-page "Adds page information" [snap-data {:keys [objects options] :as page}] - (let [frames (cph/get-frames objects) + (let [frames (ctst/get-frames objects) shapes (->> (vals (:objects page)) (remove cph/frame-shape?)) guides (vals (:guides options)) diff --git a/frontend/test/app/components_basic_test.cljs b/frontend/test/app/components_basic_test.cljs index b392e7100d..bc0d2b7447 100644 --- a/frontend/test/app/components_basic_test.cljs +++ b/frontend/test/app/components_basic_test.cljs @@ -3,6 +3,7 @@ [app.common.data :as d] [app.common.geom.point :as gpt] [app.common.pages.helpers :as cph] + [app.common.types.container :as ctc] [app.main.data.workspace :as dw] [app.main.data.workspace.groups :as dwg] [app.main.data.workspace.libraries :as dwl] @@ -520,7 +521,7 @@ ; (let [page (thp/current-page new-state) shape1 (thp/get-shape new-state :shape1) - parent1 (cph/get-shape page (:parent-id shape1)) + parent1 (ctc/get-shape page (:parent-id shape1)) [[group shape1 shape2] [c-group c-shape1 c-shape2] diff --git a/frontend/test/app/components_sync_test.cljs b/frontend/test/app/components_sync_test.cljs index b3d754a37a..442b8c60f0 100644 --- a/frontend/test/app/components_sync_test.cljs +++ b/frontend/test/app/components_sync_test.cljs @@ -4,6 +4,7 @@ [app.common.data :as d] [app.common.geom.point :as gpt] [app.common.pages.helpers :as cph] + [app.common.types.container :as ctc] [app.main.data.workspace :as dw] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.shapes :as dwsh] @@ -1352,7 +1353,7 @@ instance1 (thp/get-shape state :instance1) instance2 (thp/get-shape state :instance2) - shape2 (cph/get-shape (wsh/lookup-page state) + shape2 (ctc/get-shape (wsh/lookup-page state) (first (:shapes instance2))) update-fn1 (fn [shape] diff --git a/frontend/test/app/test_helpers/libraries.cljs b/frontend/test/app/test_helpers/libraries.cljs index f3644d8e30..428653234c 100644 --- a/frontend/test/app/test_helpers/libraries.cljs +++ b/frontend/test/app/test_helpers/libraries.cljs @@ -8,6 +8,7 @@ [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.pages.helpers :as cph] + [app.common.types.container :as ctc] [app.main.data.workspace :as dw] [app.main.data.workspace.libraries-helpers :as dwlh] [app.main.data.workspace.state-helpers :as wsh] @@ -59,7 +60,7 @@ verify that they are a well constructed instance tree." [state root-inst-id] (let [page (thp/current-page state) - root-inst (cph/get-shape page root-inst-id) + root-inst (ctc/get-shape page root-inst-id) shapes-inst (cph/get-children-with-self (:objects page) root-inst-id)] (is-instance-root (first shapes-inst)) @@ -72,7 +73,7 @@ verify that they are not a component instance." [state root-inst-id] (let [page (thp/current-page state) - root-inst (cph/get-shape page root-inst-id) + root-inst (ctc/get-shape page root-inst-id) shapes-inst (cph/get-children-with-self (:objects page) root-inst-id)] (run! is-noninstance shapes-inst) @@ -84,7 +85,7 @@ the main component and all its shapes." [state root-inst-id] (let [page (thp/current-page state) - root-inst (cph/get-shape page root-inst-id) + root-inst (ctc/get-shape page root-inst-id) libs (wsh/get-libraries state) component (cph/get-component libs (:component-id root-inst)) @@ -102,7 +103,7 @@ (cph/get-component libs (:component-id component-shape)) main-shape - (cph/get-shape component (:shape-ref shape))] + (ctc/get-shape component (:shape-ref shape))] (t/is (some? main-shape))))] @@ -122,7 +123,7 @@ corresponding component shape missing." [state root-inst-id] (let [page (thp/current-page state) - root-inst (cph/get-shape page root-inst-id) + root-inst (ctc/get-shape page root-inst-id) libs (wsh/get-libraries state) component (cph/get-component libs (:component-id root-inst)) @@ -140,7 +141,7 @@ (cph/get-component libs (:component-id component-shape)) main-shape - (cph/get-shape component (:shape-ref shape))] + (ctc/get-shape component (:shape-ref shape))] (t/is (some? main-shape))))] From ce09ea6eb503c672bc24533b47a46e82a4f40320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Mon, 13 Jun 2022 16:30:29 +0200 Subject: [PATCH 03/14] :tada: Add library page for components on migration --- common/src/app/common/data.cljc | 4 + common/src/app/common/file_builder.cljc | 4 +- common/src/app/common/geom/shapes.cljc | 10 + common/src/app/common/pages/changes.cljc | 75 ++----- common/src/app/common/pages/common.cljc | 2 +- common/src/app/common/pages/init.cljc | 186 ------------------ common/src/app/common/pages/migrations.cljc | 72 +++++++ common/src/app/common/types/page.cljc | 18 ++ common/src/app/common/types/shape_tree.cljc | 31 ++- .../shape}/spec_interactions_test.cljc | 0 10 files changed, 150 insertions(+), 252 deletions(-) delete mode 100644 common/src/app/common/pages/init.cljc rename common/test/app/common/{ => types/shape}/spec_interactions_test.cljc (100%) diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index baa0f2fe33..d03a935a25 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -173,6 +173,10 @@ [data] (into {} (remove (comp nil? second)) data)) +(defn vec-without-nils + [coll] + (into [] (remove nil?) coll)) + (defn without-qualified [data] (into {} (remove (comp qualified-keyword? first)) data)) diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index 5fcc64c0fd..d744e41a94 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -13,6 +13,7 @@ [app.common.pages.changes :as ch] [app.common.pages.changes-spec :as pcs] [app.common.pages.init :as init] + [app.common.types.page :as ctp] [app.common.spec :as us] [app.common.types.page :as ctp] [app.common.uuid :as uuid] @@ -179,8 +180,7 @@ (assert (nil? (:current-component-id file))) (let [page-id (or (:id data) (uuid/next)) - page (-> init/empty-page-data - (assoc :id page-id) + page (-> (ctp/make-empty-page page-id "Page-1") (d/deep-merge data))] (-> file (commit-change diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index 31c1037795..c75f47e52d 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -41,11 +41,21 @@ ;; --- Helpers +(defn bounding-box + "Returns a rect that wraps the shape after all transformations applied." + [shape] + ; TODO: perhaps we need to store this calculation in a shape attribute + (gpr/points->rect (:points shape))) + (defn left-bound + "Returns the lowest x coord of the shape BEFORE applying transformations." + ; TODO: perhaps some day we want after transformations, but for the + ; moment it's enough as is now. [shape] (get shape :x (:x (:selrect shape)))) ; Paths don't have :x attribute (defn top-bound + "Returns the lowest y coord of the shape BEFORE applying transformations." [shape] (get shape :y (:y (:selrect shape)))) ; Paths don't have :y attribute diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc index 6e0c741151..2d912f7b0f 100644 --- a/common/src/app/common/pages/changes.cljc +++ b/common/src/app/common/pages/changes.cljc @@ -17,7 +17,10 @@ [app.common.pages.init :as init] [app.common.spec :as us] [app.common.pages.changes-spec :as pcs] - [app.common.types.shape :as cts])) + [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])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Specific helpers @@ -28,10 +31,6 @@ [coll o] (into [] (filter #(not= % o)) coll)) -(defn vec-without-nils - [coll] - (into [] (remove nil?) coll)) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Page Transformation Changes ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -74,44 +73,9 @@ (defmethod process-change :add-obj [data {:keys [id obj page-id component-id frame-id parent-id index ignore-touched]}] - (letfn [(update-parent-shapes [shapes] - ;; Ensure that shapes is always a vector. - (let [shapes (into [] shapes)] - (cond - (some #{id} shapes) - shapes - - (nil? index) - (conj shapes id) - - :else - (cph/insert-at-index shapes index [id])))) - - (update-parent [parent] - (-> parent - (update :shapes update-parent-shapes) - (update :shapes vec-without-nils) - (cond-> (and (:shape-ref parent) - (not= (:id parent) frame-id) - (not ignore-touched)) - (-> (update :touched cph/set-touched-group :shapes-group) - (dissoc :remote-synced?))))) - - ;; TODO: this looks wrong, why we allow nil values? - (update-objects [objects parent-id] - (if (and (or (nil? parent-id) (contains? objects parent-id)) - (or (nil? frame-id) (contains? objects frame-id))) - (-> objects - (assoc id (-> obj - (assoc :frame-id frame-id) - (assoc :parent-id parent-id) - (assoc :id id))) - (update parent-id update-parent)) - objects)) - - (update-container [data] - (let [parent-id (or parent-id frame-id)] - (update data :objects update-objects parent-id)))] + (let [update-container + (fn [container] + (ctst/add-shape id obj container frame-id parent-id index ignore-touched))] (if page-id (d/update-in-when data [:pages-index page-id] update-container) @@ -237,7 +201,7 @@ ;; We need to ensure that no `nil` in the ;; shapes list after adding all the ;; incoming shapes to the parent. - (update :shapes vec-without-nils))] + (update :shapes d/vec-without-nils))] (cond-> parent (and (:shape-ref parent) (= (:type parent) :group) (not ignore-touched)) (-> (update :touched cph/set-touched-group :shapes-group) @@ -258,7 +222,7 @@ (-> objects (d/update-in-when [pid :shapes] without-obj sid) - (d/update-in-when [pid :shapes] vec-without-nils) + (d/update-in-when [pid :shapes] d/vec-without-nils) (cond-> component? (d/update-when pid #(-> % (update :touched cph/set-touched-group :shapes-group) (dissoc :remote-synced?))))))))) @@ -323,22 +287,11 @@ [data {:keys [id name page]}] (when (and id name page) (ex/raise :type :conflict - :hint "name or page should be provided, never both")) - (letfn [(conj-if-not-exists [pages id] - (cond-> pages - (not (d/seek #(= % id) pages)) - (conj id)))] - (if (and (string? name) (uuid? id)) - (let [page (assoc init/empty-page-data - :id id - :name name)] - (-> data - (update :pages conj-if-not-exists id) - (update :pages-index assoc id page))) - - (-> data - (update :pages conj-if-not-exists (:id page)) - (update :pages-index assoc (:id page) page))))) + :hint "id+name or page should be provided, never both")) + (let [page (if (and (string? name) (uuid? id)) + (ctp/make-empty-page id name) + page)] + (ctpl/add-page data page))) (defmethod process-change :mod-page [data {:keys [id name]}] diff --git a/common/src/app/common/pages/common.cljc b/common/src/app/common/pages/common.cljc index 1b73deac24..cd9e94ee70 100644 --- a/common/src/app/common/pages/common.cljc +++ b/common/src/app/common/pages/common.cljc @@ -9,7 +9,7 @@ [app.common.colors :as clr] [app.common.uuid :as uuid])) -(def file-version 19) +(def file-version 20) (def default-color clr/gray-20) (def root uuid/zero) diff --git a/common/src/app/common/pages/init.cljc b/common/src/app/common/pages/init.cljc deleted file mode 100644 index 3b1e85b04f..0000000000 --- a/common/src/app/common/pages/init.cljc +++ /dev/null @@ -1,186 +0,0 @@ -;; 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.pages.init - (:require - [app.common.colors :as clr] - [app.common.data :as d] - [app.common.exceptions :as ex] - [app.common.geom.shapes :as gsh] - [app.common.pages.common :refer [file-version default-color]] - [app.common.uuid :as uuid])) - -(def root uuid/zero) - -(def empty-page-data - {:options {} - :name "Page-1" - :objects - {root - {:id root - :type :frame - :name "Root Frame"}}}) - -(def empty-file-data - {:version file-version - :pages [] - :pages-index {}}) - -(def default-shape-attrs - {}) - -(def default-frame-attrs - {:frame-id uuid/zero - :fills [{:fill-color clr/white - :fill-opacity 1}] - :strokes [] - :shapes [] - :hide-fill-on-export false}) - -(def ^:private minimal-shapes - [{:type :rect - :name "Rect-1" - :fills [{:fill-color default-color - :fill-opacity 1}] - :strokes [] - :rx 0 - :ry 0} - - {:type :image - :rx 0 - :ry 0 - :fills [] - :strokes []} - - {:type :circle - :name "Circle-1" - :fills [{:fill-color default-color - :fill-opacity 1}] - :strokes []} - - {:type :path - :name "Path-1" - :fills [] - :strokes [{:stroke-style :solid - :stroke-alignment :center - :stroke-width 2 - :stroke-color clr/black - :stroke-opacity 1}]} - - {:type :frame - :name "Board-1" - :fills [{:fill-color clr/white - :fill-opacity 1}] - :strokes [] - :stroke-style :none - :stroke-alignment :center - :stroke-width 0 - :stroke-color clr/black - :stroke-opacity 0 - :rx 0 - :ry 0} - - {:type :text - :name "Text-1" - :content nil} - - {:type :svg-raw}]) - -(def empty-selrect - {:x 0 :y 0 - :x1 0 :y1 0 - :x2 0.01 :y2 0.01 - :width 0.01 :height 0.01}) - -(defn make-minimal-shape - [type] - (let [type (cond (= type :curve) :path - :else type) - shape (d/seek #(= type (:type %)) minimal-shapes)] - (when-not shape - (ex/raise :type :assertion - :code :shape-type-not-implemented - :context {:type type})) - - (cond-> shape - :always - (assoc :id (uuid/next)) - - (not= :path (:type shape)) - (assoc :x 0 - :y 0 - :width 0.01 - :height 0.01 - :selrect {:x 0 - :y 0 - :x1 0 - :y1 0 - :x2 0.01 - :y2 0.01 - :width 0.01 - :height 0.01})))) - -(defn make-minimal-group - [frame-id selection-rect group-name] - {:id (uuid/next) - :type :group - :name group-name - :shapes [] - :frame-id frame-id - :x (:x selection-rect) - :y (:y selection-rect) - :width (:width selection-rect) - :height (:height selection-rect)}) - -(defn make-file-data - ([file-id] - (make-file-data file-id (uuid/next))) - - ([file-id page-id] - (let [pd (assoc empty-page-data - :id page-id - :name "Page-1")] - (-> empty-file-data - (assoc :id file-id) - (update :pages conj page-id) - (update :pages-index assoc page-id pd))))) - -(defn setup-rect-selrect - "Initializes the selrect and points for a shape" - [shape] - (let [selrect (gsh/rect->selrect shape) - points (gsh/rect->points shape)] - (-> shape - (assoc :selrect selrect - :points points)))) - -(defn- setup-rect - "A specialized function for setup rect-like shapes." - [shape {:keys [x y width height]}] - (-> shape - (assoc :x x :y y :width width :height height) - (setup-rect-selrect))) - -(defn- setup-image - [{:keys [metadata] :as shape} props] - (-> (setup-rect shape props) - (assoc - :proportion (/ (:width metadata) - (:height metadata)) - :proportion-lock true))) - -(defn setup-shape - "A function that initializes the first coordinates for - the shape. Used mainly for draw operations." - ([props] - (setup-shape {:type :rect} props)) - - ([shape props] - (case (:type shape) - :image (setup-image shape props) - (setup-rect shape props)))) - - diff --git a/common/src/app/common/pages/migrations.cljc b/common/src/app/common/pages/migrations.cljc index d2beb3ce04..512df70925 100644 --- a/common/src/app/common/pages/migrations.cljc +++ b/common/src/app/common/pages/migrations.cljc @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.geom.matrix :as gmt] + [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.geom.shapes.path :as gsp] [app.common.geom.shapes.text :as gsht] @@ -15,6 +16,10 @@ [app.common.math :as mth] [app.common.pages :as cp] [app.common.pages.helpers :as cph] + [app.common.types.container :as ctc] + [app.common.types.page :as ctp] + [app.common.types.pages-list :as ctpl] + [app.common.types.shape-tree :as ctst] [app.common.uuid :as uuid] [cuerdas.core :as str])) @@ -432,5 +437,72 @@ (update :pages-index d/update-vals update-container) (update :components d/update-vals update-container)))) +(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] + (ctc/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)))))))] + + (if (empty? components) + data + (-> data + (add-library-page) + (add-instance-grid 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/page.cljc b/common/src/app/common/types/page.cljc index b86092f35f..2aee3761a6 100644 --- a/common/src/app/common/types/page.cljc +++ b/common/src/app/common/types/page.cljc @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.spec :as us] [app.common.types.shape :as cts] + [app.common.uuid :as uuid] [clojure.spec.alpha :as s])) ;; --- Grid options @@ -95,6 +96,23 @@ (s/def ::page (s/keys :req-un [::id ::name ::objects ::options])) +;; --- Initialization + +(def root uuid/zero) + +(def empty-page-data + {:options {} + :objects {root + {:id root + :type :frame + :name "Root Frame"}}}) + +(defn make-empty-page + [id name] + (assoc empty-page-data + :id id + :name name)) + ;; --- Helpers for flow (defn rename-flow diff --git a/common/src/app/common/types/shape_tree.cljc b/common/src/app/common/types/shape_tree.cljc index c9bcef5079..4fd9cb0a35 100644 --- a/common/src/app/common/types/shape_tree.cljc +++ b/common/src/app/common/types/shape_tree.cljc @@ -272,10 +272,13 @@ original ones. Returns the cloned object, the list of all new objects (including - the cloned one), and possibly a list of original objects modified." + the cloned one), and possibly a list of original objects modified. + + The list of objects are returned in tree traversal order, respecting + the order of the children of each parent." ([object parent-id objects update-new-object] - (clone-object object parent-id objects update-new-object identity)) + (clone-object object parent-id objects update-new-object (fn [object _] object))) ([object parent-id objects update-new-object update-original-object] (let [new-id (uuid/next)] @@ -316,3 +319,27 @@ (into new-children new-child-objects) (into updated-children updated-child-objects)))))))) +(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] + (let [shapes-bounds (map gsh/bounding-box shapes) + + grid-size (mth/ceil (mth/sqrt (count shapes))) + row-size (+ (apply max (map :height shapes-bounds)) + gap) + column-size (+ (apply max (map :width shapes-bounds)) + gap) + + next-pos (fn [position] + (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))] + (with-meta new-pos + {:counter counter})))] + (iterate next-pos + (with-meta (gpt/point 0 0) + {:counter 0})))) + diff --git a/common/test/app/common/spec_interactions_test.cljc b/common/test/app/common/types/shape/spec_interactions_test.cljc similarity index 100% rename from common/test/app/common/spec_interactions_test.cljc rename to common/test/app/common/types/shape/spec_interactions_test.cljc From 165cdd871f346fc823ec3e8e367cd81e36709dc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Fri, 17 Jun 2022 16:44:32 +0200 Subject: [PATCH 04/14] :tada: Allow to duplicate components with main instance --- .../src/app/common/pages/changes_builder.cljc | 9 ++++++ .../app/main/data/workspace/libraries.cljs | 20 ++++++++----- .../data/workspace/libraries_helpers.cljs | 29 +++++++++++++++---- frontend/src/debug.cljs | 6 +++- 4 files changed, 49 insertions(+), 15 deletions(-) diff --git a/common/src/app/common/pages/changes_builder.cljc b/common/src/app/common/pages/changes_builder.cljc index 13e9d710f2..bdf4a1a969 100644 --- a/common/src/app/common/pages/changes_builder.cljc +++ b/common/src/app/common/pages/changes_builder.cljc @@ -223,6 +223,15 @@ (update :undo-changes d/preconj del-change) (apply-changes-local))))) +(defn add-objects + ([changes objects] + (add-objects changes objects nil)) + + ([changes objects params] + (reduce #(add-object %1 %2 params) + changes + objects))) + (defn change-parent ([changes parent-id shapes] (change-parent changes parent-id shapes nil)) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 9a8acafb08..2217af1dde 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -355,19 +355,23 @@ unames (into #{} (map :name) all-components) new-name (ctst/generate-unique-name unames (:name component)) - [new-shape new-shapes _updated-shapes main-instance main-instance-page] - (dwlh/duplicate-component component) + main-instance-page (wsh/lookup-page state (:main-instance-page component)) + main-instance-shape (ctn/get-shape main-instance-page (:main-instance-id component)) - _ (prn "OJOOOOOOOOOOOOOOO falta calcular main-instance") + [new-component-shape new-component-shapes + new-main-instance-shape new-main-instance-shapes] + (dwlh/duplicate-component component main-instance-page main-instance-shape) - changes (-> (pcb/empty-changes it nil) ;; no objects are changed - (pcb/with-objects nil) ;; in the current page - (pcb/add-component (:id new-shape) + changes (-> (pcb/empty-changes it nil) + (pcb/with-page main-instance-page) + (pcb/with-objects (:objects main-instance-page)) + (pcb/add-objects new-main-instance-shapes {:ignore-touched true}) + (pcb/add-component (:id new-component-shape) (:path component) new-name - new-shapes + new-component-shapes [] - (:id main-instance) + (:id new-main-instance-shape) (:id main-instance-page)))] (rx/of (dch/commit-changes changes)))))) diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index 2914f4095a..39885b8477 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -130,12 +130,29 @@ (defn duplicate-component "Clone the root shape of the component and all children. Generate new ids from all of them." - [component] - (let [component-root (cph/get-component-root component)] - (ctst/clone-object component-root - nil - (get component :objects) - identity))) + [component main-instance-page 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)) + + component-root (cph/get-component-root component) + + [new-component-shape new-component-shapes _] + (ctst/clone-object component-root + nil + (get component :objects) + identity) + + + [new-instance-shape new-instance-shapes] + (ctn/instantiate-component main-instance-page + {:id (:id new-component-shape) + :name (:name new-component-shape) + :objects (d/index-by :id new-component-shapes)} + (:component-file main-instance-shape) + position)] + + [new-component-shape new-component-shapes + new-instance-shape new-instance-shapes])) (defn generate-instantiate-component "Generate changes to create a new instance from a component." diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs index bef2d105d9..09d651d14e 100644 --- a/frontend/src/debug.cljs +++ b/frontend/src/debug.cljs @@ -276,7 +276,11 @@ (dorun (for [component (vals components)] (do (println) - (println (str/format "[%s]" (:name component))) + (println (str/format "[%s]" (:name component)) + (when show-ids + (str/format " (main: %s/%s)" + (:main-instance-page component) + (:main-instance-id component)))) (show-shape (:id component) 0 (:objects component))))))))) (defn ^:export dump-tree From 54e0071c9c9517468cb819da7d9d76be69e1ffb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 23 Jun 2022 17:43:43 +0200 Subject: [PATCH 05/14] :tada: Scaffolding to write unit tests of common types --- backend/src/app/rpc/mutations/files.clj | 3 +- backend/src/app/rpc/queries/files.clj | 2 +- backend/src/app/tasks/file_gc.clj | 1 - common/src/app/common/file_builder.cljc | 18 +- common/src/app/common/pages.cljc | 12 +- common/src/app/common/pages/changes.cljc | 16 +- .../src/app/common/pages/changes_builder.cljc | 3 +- common/src/app/common/pages/migrations.cljc | 8 +- .../src/app/common/types/components_list.cljc | 28 +++ common/src/app/common/types/container.cljc | 89 ++++++++-- common/src/app/common/types/file.cljc | 167 +++++++++++++++++- common/src/app/common/types/pages_list.cljc | 8 + common/src/app/common/types/shape.cljc | 158 +++++++++++++++++ common/src/app/common/types/shape_tree.cljc | 6 +- common/test/app/common/geom_shapes_test.cljc | 4 +- common/test/app/common/pages_test.cljc | 15 +- .../test/app/common/test_helpers/files.cljc | 110 ++++++++++++ common/test/app/common/types/file_test.cljc | 79 +++++++++ .../types/shape/spec_interactions_test.cljc | 26 +-- frontend/src/app/main/data/workspace.cljs | 7 +- .../src/app/main/data/workspace/common.cljs | 9 - .../src/app/main/data/workspace/drawing.cljs | 4 +- .../app/main/data/workspace/drawing/box.cljs | 4 +- .../main/data/workspace/drawing/common.cljs | 5 +- .../src/app/main/data/workspace/groups.cljs | 6 +- .../data/workspace/libraries_helpers.cljs | 42 +---- .../app/main/data/workspace/svg_upload.cljs | 8 +- .../main/ui/workspace/viewport/selection.cljs | 6 +- frontend/src/app/worker/import.cljs | 4 +- frontend/src/debug.cljs | 76 +------- frontend/test/app/components_basic_test.cljs | 4 +- frontend/test/app/components_sync_test.cljs | 4 +- frontend/test/app/test_helpers/libraries.cljs | 14 +- frontend/test/app/test_helpers/pages.cljs | 5 +- frontend/test/app/util/snap_data_test.cljs | 1 - 35 files changed, 724 insertions(+), 228 deletions(-) create mode 100644 common/src/app/common/types/components_list.cljc create mode 100644 common/test/app/common/test_helpers/files.cljc create mode 100644 common/test/app/common/types/file_test.cljc diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index dba09a40a2..b3ee8cb3b8 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -11,6 +11,7 @@ [app.common.pages :as cp] [app.common.pages.migrations :as pmg] [app.common.spec :as us] + [app.common.types.file :as ctf] [app.common.uuid :as uuid] [app.config :as cf] [app.db :as db] @@ -69,7 +70,7 @@ :or {is-shared false revn 0} :as params}] (let [id (or id (:id data) (uuid/next)) - data (or data (cp/make-file-data id)) + data (or data (ctf/make-file-data id)) file (db/insert! conn :file (d/without-nils {:id id diff --git a/backend/src/app/rpc/queries/files.clj b/backend/src/app/rpc/queries/files.clj index 7adc19fbdb..0fd9431e02 100644 --- a/backend/src/app/rpc/queries/files.clj +++ b/backend/src/app/rpc/queries/files.clj @@ -12,8 +12,8 @@ [app.common.geom.shapes :as gsh] [app.common.pages.helpers :as cph] [app.common.pages.migrations :as pmg] - [app.common.types.shape-tree :as ctt] [app.common.spec :as us] + [app.common.types.shape-tree :as ctt] [app.db :as db] [app.db.sql :as sql] [app.rpc.helpers :as rpch] diff --git a/backend/src/app/tasks/file_gc.clj b/backend/src/app/tasks/file_gc.clj index 1f9d5042e7..ec8b0a2e80 100644 --- a/backend/src/app/tasks/file_gc.clj +++ b/backend/src/app/tasks/file_gc.clj @@ -12,7 +12,6 @@ (:require [app.common.data :as d] [app.common.logging :as l] - [app.common.pages.helpers :as cph] [app.common.pages.migrations :as pmg] [app.common.types.shape-tree :as ctt] [app.db :as db] diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index d744e41a94..a924186b35 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -12,10 +12,10 @@ [app.common.geom.shapes :as gsh] [app.common.pages.changes :as ch] [app.common.pages.changes-spec :as pcs] - [app.common.pages.init :as init] - [app.common.types.page :as ctp] [app.common.spec :as us] + [app.common.types.file :as ctf] [app.common.types.page :as ctp] + [app.common.types.shape :as cts] [app.common.uuid :as uuid] [cuerdas.core :as str])) @@ -169,7 +169,7 @@ ([id name] {:id id :name name - :data (-> init/empty-file-data + :data (-> ctf/empty-file-data (assoc :id id)) ;; We keep the changes so we can send them to the backend @@ -209,7 +209,7 @@ (defn add-artboard [file data] (assert (nil? (:current-component-id file))) - (let [obj (-> (init/make-minimal-shape :frame) + (let [obj (-> (cts/make-minimal-shape :frame) (merge data) (check-name file :frame) (setup-selrect) @@ -233,9 +233,9 @@ (defn add-group [file data] (let [frame-id (:current-frame-id file) - selrect init/empty-selrect + selrect cts/empty-selrect name (:name data) - obj (-> (init/make-minimal-group frame-id selrect name) + obj (-> (cts/make-minimal-group frame-id selrect name) (merge data) (check-name file :group) (d/without-nils))] @@ -347,7 +347,7 @@ (update :parent-stack pop)))) (defn create-shape [file type data] - (let [obj (-> (init/make-minimal-shape type) + (let [obj (-> (cts/make-minimal-shape type) (merge data) (check-name file :type) (setup-selrect) @@ -515,10 +515,10 @@ (defn start-component [file data] - (let [selrect init/empty-selrect + (let [selrect cts/empty-selrect name (:name data) path (:path data) - obj (-> (init/make-minimal-group nil selrect name) + obj (-> (cts/make-minimal-group nil selrect name) (merge data) (check-name file :group) (d/without-nils))] diff --git a/common/src/app/common/pages.cljc b/common/src/app/common/pages.cljc index e500d372d6..b4c43458cb 100644 --- a/common/src/app/common/pages.cljc +++ b/common/src/app/common/pages.cljc @@ -12,7 +12,7 @@ [app.common.pages.common :as common] [app.common.pages.focus :as focus] [app.common.pages.indices :as indices] - [app.common.pages.init :as init])) + [app.common.types.file :as ctf])) ;; Common (dm/export common/root) @@ -36,11 +36,5 @@ (dm/export changes/process-changes) ;; Initialization -(dm/export init/default-frame-attrs) -(dm/export init/default-shape-attrs) -(dm/export init/make-file-data) -(dm/export init/make-minimal-shape) -(dm/export init/make-minimal-group) -(dm/export init/empty-file-data) -(dm/export init/setup-shape) -(dm/export init/setup-rect-selrect) +(dm/export ctf/make-file-data) +(dm/export ctf/empty-file-data) diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc index 2d912f7b0f..139d37e652 100644 --- a/common/src/app/common/pages/changes.cljc +++ b/common/src/app/common/pages/changes.cljc @@ -14,9 +14,9 @@ [app.common.math :as mth] [app.common.pages.common :refer [component-sync-attrs]] [app.common.pages.helpers :as cph] - [app.common.pages.init :as init] [app.common.spec :as us] [app.common.pages.changes-spec :as pcs] + [app.common.types.components-list :as ctkl] [app.common.types.page :as ctp] [app.common.types.pages-list :as ctpl] [app.common.types.shape :as cts] @@ -346,13 +346,13 @@ (defmethod process-change :add-component [data {:keys [id name path main-instance-id main-instance-page shapes]}] - (assoc-in data [:components id] - {:id id - :name name - :path path - :main-instance-id main-instance-id - :main-instance-page main-instance-page - :objects (d/index-by :id shapes)})) + (ctkl/add-component data + id + name + path + main-instance-id + main-instance-page + shapes)) (defmethod process-change :mod-component [data {:keys [id name path objects]}] diff --git a/common/src/app/common/pages/changes_builder.cljc b/common/src/app/common/pages/changes_builder.cljc index bdf4a1a969..50891acc15 100644 --- a/common/src/app/common/pages/changes_builder.cljc +++ b/common/src/app/common/pages/changes_builder.cljc @@ -16,6 +16,7 @@ [app.common.math :as mth] [app.common.pages :as cp] [app.common.pages.helpers :as cph] + [app.common.types.file :as ctf] [app.common.uuid :as uuid])) ;; Auxiliary functions to help create a set of changes (undo + redo) @@ -49,7 +50,7 @@ (defn with-objects [changes objects] - (let [file-data (-> (cp/make-file-data (uuid/next) uuid/zero) + (let [file-data (-> (ctf/make-file-data (uuid/next) uuid/zero) (assoc-in [:pages-index uuid/zero :objects] objects))] (vary-meta changes assoc ::file-data file-data ::applied-changes-count 0))) diff --git a/common/src/app/common/pages/migrations.cljc b/common/src/app/common/pages/migrations.cljc index 512df70925..601671bfda 100644 --- a/common/src/app/common/pages/migrations.cljc +++ b/common/src/app/common/pages/migrations.cljc @@ -8,7 +8,6 @@ (:require [app.common.data :as d] [app.common.geom.matrix :as gmt] - [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.geom.shapes.path :as gsp] [app.common.geom.shapes.text :as gsht] @@ -16,9 +15,10 @@ [app.common.math :as mth] [app.common.pages :as cp] [app.common.pages.helpers :as cph] - [app.common.types.container :as ctc] + [app.common.types.container :as ctn] [app.common.types.page :as ctp] [app.common.types.pages-list :as ctpl] + [app.common.types.shape :as cts] [app.common.types.shape-tree :as ctst] [app.common.uuid :as uuid] [cuerdas.core :as str])) @@ -89,7 +89,7 @@ (fix-empty-points [shape] (let [shape (cond-> shape - (empty? (:selrect shape)) (cp/setup-rect-selrect))] + (empty? (:selrect shape)) (cts/setup-rect-selrect))] (cond-> shape (empty? (:points shape)) (assoc :points (gsh/rect->points (:selrect shape)))))) @@ -456,7 +456,7 @@ (let [page (ctpl/get-page data page-id) [new-shape new-shapes] - (ctc/instantiate-component page + (ctn/instantiate-component page component (:id data) position) diff --git a/common/src/app/common/types/components_list.cljc b/common/src/app/common/types/components_list.cljc new file mode 100644 index 0000000000..6f5643e673 --- /dev/null +++ b/common/src/app/common/types/components_list.cljc @@ -0,0 +1,28 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.common.types.components-list + (:require + [app.common.data :as d])) + +(defn components-seq + [file-data] + (vals (:components file-data))) + +(defn add-component + [file-data id name path main-instance-id main-instance-page shapes] + (assoc-in file-data [:components id] + {:id id + :name name + :path path + :main-instance-id main-instance-id + :main-instance-page main-instance-page + :objects (d/index-by :id shapes)})) + +(defn get-component + [file-data component-id] + (get-in file-data [:components component-id])) + diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index 168cfe8ca0..7f7e8d9855 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -9,7 +9,7 @@ [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.spec :as us] - [app.common.types.shape-tree :as ctt] + [app.common.types.shape-tree :as ctst] [clojure.spec.alpha :as s])) (s/def ::type #{:page :component}) @@ -18,9 +18,32 @@ (s/def ::path (s/nilable string?)) (s/def ::container - (s/keys :req-un [::id ::name ::ctt/objects] + (s/keys :req-un [::id ::name ::ctst/objects] :opt-un [::type ::path])) +(defn make-container + [page-or-component type] + (assoc page-or-component :type type)) + +(defn page? + [container] + (= (:type container) :page)) + +(defn component? + [container] + (= (:type container) :component)) + +(defn get-container + [file type id] + (us/assert map? file) + (us/assert ::type type) + (us/assert uuid? id) + + (-> (if (= type :page) + (get-in file [:pages-index id]) + (get-in file [:components id])) + (assoc :type type))) + (defn get-shape [container shape-id] (us/assert ::container container) @@ -29,21 +52,65 @@ (get :objects) (get shape-id))) +(defn shapes-seq + [container] + (vals (:objects container))) + +(defn make-component-shape + "Clone the shape and all children. Generate new ids and detach + from parent and frame. Update the original shapes to have links + to the new ones." + [shape objects file-id] + (assert (nil? (:component-id shape))) + (assert (nil? (:component-file shape))) + (assert (nil? (:shape-ref shape))) + (let [;; Ensure that the component root is not an instance and + ;; it's no longer tied to a frame. + update-new-shape (fn [new-shape _original-shape] + (cond-> new-shape + true + (-> (assoc :frame-id nil) + (dissoc :component-root?)) + + (nil? (:parent-id new-shape)) + (dissoc :component-id + :component-file + :shape-ref))) + + ;; Make the original shape an instance of the new component. + ;; If one of the original shape children already was a component + ;; instance, maintain this instanceness untouched. + update-original-shape (fn [original-shape new-shape] + (cond-> original-shape + (nil? (:shape-ref original-shape)) + (-> (assoc :shape-ref (:id new-shape)) + (dissoc :touched)) + + (nil? (:parent-id new-shape)) + (assoc :component-id (:id new-shape) + :component-file file-id + :component-root? true) + + (some? (:parent-id new-shape)) + (dissoc :component-root?)))] + + (ctst/clone-object shape nil objects update-new-shape update-original-shape))) + (defn instantiate-component - [container component component-file position] + [container component component-file-id position] (let [component-shape (get-shape component (:id component)) orig-pos (gpt/point (:x component-shape) (:y component-shape)) delta (gpt/subtract position orig-pos) objects (:objects container) - unames (volatile! (ctt/retrieve-used-names objects)) + unames (volatile! (ctst/retrieve-used-names objects)) - frame-id (ctt/frame-id-by-position objects (gpt/add orig-pos delta)) + frame-id (ctst/frame-id-by-position objects (gpt/add orig-pos delta)) update-new-shape (fn [new-shape original-shape] - (let [new-name (ctt/generate-unique-name @unames (:name new-shape))] + (let [new-name (ctst/generate-unique-name @unames (:name new-shape))] (when (nil? (:parent-id original-shape)) (vswap! unames conj new-name)) @@ -62,7 +129,7 @@ (nil? (:parent-id original-shape)) (assoc :component-id (:id original-shape) - :component-file component-file + :component-file component-file-id :component-root? true :name new-name) @@ -70,10 +137,10 @@ (dissoc :component-root?)))) [new-shape new-shapes _] - (ctt/clone-object component-shape - nil - (get component :objects) - update-new-shape)] + (ctst/clone-object component-shape + nil + (get component :objects) + update-new-shape)] [new-shape new-shapes])) diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index 5c7475d609..b999f3ccda 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -6,10 +6,20 @@ (ns app.common.types.file (:require + [app.common.data :as d] + [app.common.pages.common :refer [file-version]] + [app.common.pages.helpers :as cph] [app.common.spec :as us] [app.common.types.color :as ctc] + [app.common.types.components-list :as ctkl] + [app.common.types.container :as ctn] [app.common.types.page :as ctp] - [clojure.spec.alpha :as s])) + [app.common.types.pages-list :as ctpl] + [app.common.uuid :as uuid] + [clojure.spec.alpha :as s] + [cuerdas.core :as str])) + +;; Specs (s/def :internal.media-object/name string?) (s/def :internal.media-object/width ::us/safe-integer) @@ -57,3 +67,158 @@ ::recent-colors ::typographies ::media])) + +;; Initialization + +(def empty-file-data + {:version file-version + :pages [] + :pages-index {}}) + +(defn make-file-data + ([file-id] + (make-file-data file-id (uuid/next))) + + ([file-id page-id] + (let [page (ctp/make-empty-page page-id "Page-1")] + (-> empty-file-data + (assoc :id file-id) + (ctpl/add-page page))))) + +;; Helpers + +(defn update-file-data + [file f] + (update file :data f)) + +(defn containers-seq + "Generate a sequence of all pages and all components, wrapped as containers" + [file-data] + (concat (map #(ctn/make-container % :page) (ctpl/pages-seq file-data)) + (map #(ctn/make-container % :component) (ctkl/components-seq file-data)))) + +(defn absorb-assets + "Find all assets of a library that are used in the file, and + move them to the file local library." + [file-data library-data] + (let [library-page-id (uuid/next) + + add-library-page + (fn [file-data] + (let [page (ctp/make-empty-page library-page-id "Library page")] + (-> file-data + (ctpl/add-page page)))) + + find-instances-in-container + (fn [container component] + (let [instances (filter #(= (:component-id %) (:id component)) + (ctn/shapes-seq container))] + (when (d/not-empty? instances) + [[container instances]]))) + + find-instances + (fn [file-data component] + (mapcat #(find-instances-in-container % component) (containers-seq file-data))) + + absorb-component + (fn [file-data _component] + ;; TODO: complete this + file-data) + + used-components + (mapcat (fn [component] + (let [instances (find-instances file-data component)] + (when instances + [[component instances]]))) + (ctkl/components-seq library-data))] + + (if (empty? used-components) + file-data + (as-> file-data $ + (add-library-page $) + (reduce absorb-component + $ + used-components))))) + +;; Debug helpers + +(defn dump-tree + ([file-data page-id libraries] + (dump-tree file-data page-id libraries false false)) + + ([file-data page-id libraries show-ids] + (dump-tree file-data page-id libraries show-ids false)) + + ([file-data page-id libraries show-ids show-touched] + (let [page (ctpl/get-page file-data page-id) + objects (:objects page) + components (:components file-data) + root (d/seek #(nil? (:parent-id %)) (vals objects))] + + (letfn [(show-shape [shape-id level objects] + (let [shape (get objects shape-id)] + (println (str/pad (str (str/repeat " " level) + (:name shape) + (when (seq (:touched shape)) "*") + (when show-ids (str/format " <%s>" (:id shape)))) + {:length 20 + :type :right}) + (show-component shape objects)) + (when show-touched + (when (seq (:touched shape)) + (println (str (str/repeat " " level) + " " + (str (:touched shape))))) + (when (:remote-synced? shape) + (println (str (str/repeat " " level) + " (remote-synced)")))) + (when (:shapes shape) + (dorun (for [shape-id (:shapes shape)] + (show-shape shape-id (inc level) objects)))))) + + (show-component [shape objects] + (if (nil? (:shape-ref shape)) + "" + (let [root-shape (cph/get-component-shape objects shape) + component-id (when root-shape (:component-id root-shape)) + component-file-id (when root-shape (:component-file root-shape)) + component-file (when component-file-id (get libraries component-file-id nil)) + component (when component-id + (if component-file + (get-in component-file [:data :components component-id]) + (get components component-id))) + component-shape (when (and component (:shape-ref shape)) + (get-in component [:objects (:shape-ref shape)]))] + (str/format " %s--> %s%s%s" + (cond (:component-root? shape) "#" + (:component-id shape) "@" + :else "-") + (when component-file (str/format "<%s> " (:name component-file))) + (or (:name component-shape) "?") + (if (or (:component-root? shape) + (nil? (:component-id shape)) + true) + "" + (let [component-id (:component-id shape) + component-file-id (:component-file shape) + component-file (when component-file-id (get libraries component-file-id nil)) + component (if component-file + (get-in component-file [:data :components component-id]) + (get components component-id))] + (str/format " (%s%s)" + (when component-file (str/format "<%s> " (:name component-file))) + (:name component))))))))] + + (println "[Page]") + (show-shape (:id root) 0 objects) + + (dorun (for [component (vals components)] + (do + (println) + (println (str/format "[%s]" (:name component)) + (when show-ids + (str/format " (main: %s/%s)" + (:main-instance-page component) + (:main-instance-id component)))) + (show-shape (:id component) 0 (:objects component))))))))) + diff --git a/common/src/app/common/types/pages_list.cljc b/common/src/app/common/types/pages_list.cljc index 3f037ece25..5275e480ea 100644 --- a/common/src/app/common/types/pages_list.cljc +++ b/common/src/app/common/types/pages_list.cljc @@ -24,3 +24,11 @@ (update :pages conj-if-not-exists (:id page)) (update :pages-index assoc (:id page) page)))) +(defn pages-seq + [file-data] + (vals (:pages-index file-data))) + +(defn update-page + [file-data page-id f] + (update-in file-data [:pages-index page-id] f)) + diff --git a/common/src/app/common/types/shape.cljc b/common/src/app/common/types/shape.cljc index a5b182f64e..249c589ced 100644 --- a/common/src/app/common/types/shape.cljc +++ b/common/src/app/common/types/shape.cljc @@ -6,8 +6,13 @@ (ns app.common.types.shape (:require + [app.common.colors :as clr] + [app.common.data :as d] + [app.common.exceptions :as ex] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] + [app.common.pages.common :refer [default-color]] [app.common.spec :as us] [app.common.types.color :as ctc] [app.common.types.shape.blur :as ctsb] @@ -16,6 +21,7 @@ [app.common.types.shape.layout :as ctsl] [app.common.types.shape.radius :as ctsr] [app.common.types.shape.shadow :as ctss] + [app.common.uuid :as uuid] [clojure.set :as set] [clojure.spec.alpha :as s])) @@ -316,3 +322,155 @@ (s/and (s/multi-spec shape-spec :type) #(contains? % :type) #(contains? % :name))) + + +;; --- Initialization + +(def default-shape-attrs + {}) + +(def default-frame-attrs + {:frame-id uuid/zero + :fills [{:fill-color clr/white + :fill-opacity 1}] + :strokes [] + :shapes [] + :hide-fill-on-export false}) + +(def ^:private minimal-shapes + [{:type :rect + :name "Rect-1" + :fills [{:fill-color default-color + :fill-opacity 1}] + :strokes [] + :rx 0 + :ry 0} + + {:type :image + :rx 0 + :ry 0 + :fills [] + :strokes []} + + {:type :circle + :name "Circle-1" + :fills [{:fill-color default-color + :fill-opacity 1}] + :strokes []} + + {:type :path + :name "Path-1" + :fills [] + :strokes [{:stroke-style :solid + :stroke-alignment :center + :stroke-width 2 + :stroke-color clr/black + :stroke-opacity 1}]} + + {:type :frame + :name "Board-1" + :fills [{:fill-color clr/white + :fill-opacity 1}] + :strokes [] + :stroke-style :none + :stroke-alignment :center + :stroke-width 0 + :stroke-color clr/black + :stroke-opacity 0 + :rx 0 + :ry 0} + + {:type :text + :name "Text-1" + :content nil} + + {:type :svg-raw}]) + +(def empty-selrect + {:x 0 :y 0 + :x1 0 :y1 0 + :x2 0.01 :y2 0.01 + :width 0.01 :height 0.01}) + +(defn make-minimal-shape + [type] + (let [type (cond (= type :curve) :path + :else type) + shape (d/seek #(= type (:type %)) minimal-shapes)] + (when-not shape + (ex/raise :type :assertion + :code :shape-type-not-implemented + :context {:type type})) + + (cond-> shape + :always + (assoc :id (uuid/next)) + + (not= :path (:type shape)) + (assoc :x 0 + :y 0 + :width 0.01 + :height 0.01 + :selrect {:x 0 + :y 0 + :x1 0 + :y1 0 + :x2 0.01 + :y2 0.01 + :width 0.01 + :height 0.01})))) + +(defn make-minimal-group + [frame-id rect group-name] + {:id (uuid/next) + :type :group + :name group-name + :shapes [] + :frame-id frame-id + :x (:x rect) + :y (:y rect) + :width (:width rect) + :height (:height rect)}) + +(defn setup-rect-selrect + "Initializes the selrect and points for a shape." + [shape] + (let [selrect (gsh/rect->selrect shape) + points (gsh/rect->points shape)] + (-> shape + (assoc :selrect selrect + :points points)))) + +(defn- setup-rect + "A specialized function for setup rect-like shapes." + [shape {:keys [x y width height]}] + (-> shape + (assoc :x x :y y :width width :height height) + (setup-rect-selrect))) + +(defn- setup-image + [{:keys [metadata] :as shape} props] + (-> (setup-rect shape props) + (assoc + :proportion (/ (:width metadata) + (:height metadata)) + :proportion-lock true))) + +(defn setup-shape + "A function that initializes the geometric data of + the shape. The props must have :x :y :width :height." + ([props] + (setup-shape {:type :rect} props)) + + ([shape props] + (case (:type shape) + :image (setup-image shape props) + (setup-rect shape props)))) + +(defn make-shape + "Make a non group shape, ready to use." + [type geom-props attrs] + (-> (make-minimal-shape type) + (setup-shape geom-props) + (merge attrs))) + diff --git a/common/src/app/common/types/shape_tree.cljc b/common/src/app/common/types/shape_tree.cljc index 4fd9cb0a35..c7294699a4 100644 --- a/common/src/app/common/types/shape_tree.cljc +++ b/common/src/app/common/types/shape_tree.cljc @@ -65,6 +65,11 @@ (update container :objects update-objects parent-id))) +(defn set-shape + "Replace a shape in the tree with a new one" + [container shape] + (assoc-in container [:objects (:id shape)] shape)) + (defn get-frames "Retrieves all frame objects as vector" [objects] @@ -149,7 +154,6 @@ [base index-base-a index-base-b])) - (defn is-shape-over-shape? [objects base-shape-id over-shape-id {:keys [top-frames?]}] diff --git a/common/test/app/common/geom_shapes_test.cljc b/common/test/app/common/geom_shapes_test.cljc index 079ecafd62..b7307b3c1e 100644 --- a/common/test/app/common/geom_shapes_test.cljc +++ b/common/test/app/common/geom_shapes_test.cljc @@ -10,7 +10,7 @@ [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.math :as mth :refer [close?]] - [app.common.pages :refer [make-minimal-shape]] + [app.common.types.shape :as cts] [clojure.test :as t])) (def default-path @@ -41,7 +41,7 @@ (defn create-test-shape ([type] (create-test-shape type {})) ([type params] - (-> (make-minimal-shape type) + (-> (cts/make-minimal-shape type) (merge params) (cond-> (= type :path) (add-path-data) diff --git a/common/test/app/common/pages_test.cljc b/common/test/app/common/pages_test.cljc index abfa5f8f31..7de27ffae3 100644 --- a/common/test/app/common/pages_test.cljc +++ b/common/test/app/common/pages_test.cljc @@ -9,12 +9,13 @@ [clojure.test :as t] [clojure.pprint :refer [pprint]] [app.common.pages :as cp] + [app.common.types.file :as ctf] [app.common.uuid :as uuid])) (t/deftest process-change-set-option (let [file-id (uuid/custom 2 2) page-id (uuid/custom 1 1) - data (cp/make-file-data file-id page-id)] + data (ctf/make-file-data file-id page-id)] (t/testing "Sets option single" (let [chg {:type :set-option :page-id page-id @@ -80,7 +81,7 @@ (t/deftest process-change-add-obj (let [file-id (uuid/custom 2 2) page-id (uuid/custom 1 1) - data (cp/make-file-data file-id page-id) + data (ctf/make-file-data file-id page-id) id-a (uuid/custom 2 1) id-b (uuid/custom 2 2) id-c (uuid/custom 2 3)] @@ -134,7 +135,7 @@ (t/deftest process-change-mod-obj (let [file-id (uuid/custom 2 2) page-id (uuid/custom 1 1) - data (cp/make-file-data file-id page-id)] + data (ctf/make-file-data file-id page-id)] (t/testing "simple mod-obj" (let [chg {:type :mod-obj :page-id page-id @@ -161,7 +162,7 @@ (let [file-id (uuid/custom 2 2) page-id (uuid/custom 1 1) id (uuid/custom 2 1) - data (cp/make-file-data file-id page-id) + data (ctf/make-file-data file-id page-id) data (-> data (assoc-in [:pages-index page-id :objects uuid/zero :shapes] [id]) (assoc-in [:pages-index page-id :objects id] @@ -205,7 +206,7 @@ file-id (uuid/custom 2 2) page-id (uuid/custom 1 1) - data (cp/make-file-data file-id page-id) + data (ctf/make-file-data file-id page-id) data (update-in data [:pages-index page-id :objects] #(-> % @@ -449,7 +450,7 @@ :obj {:type :rect :name "Shape 3"}} ] - data (cp/make-file-data file-id page-id) + data (ctf/make-file-data file-id page-id) data (cp/process-changes data changes)] (t/testing "preserve order on multiple shape mov 1" @@ -556,7 +557,7 @@ :parent-id group-1-id :shapes [shape-1-id shape-2-id]}] - data (cp/make-file-data file-id page-id) + data (ctf/make-file-data file-id page-id) data (cp/process-changes data changes)] (t/testing "case 1" diff --git a/common/test/app/common/test_helpers/files.cljc b/common/test/app/common/test_helpers/files.cljc new file mode 100644 index 0000000000..bbb9c58dd2 --- /dev/null +++ b/common/test/app/common/test_helpers/files.cljc @@ -0,0 +1,110 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.common.test-helpers.files + (:require + [app.common.geom.point :as gpt] + [app.common.types.components-list :as ctkl] + [app.common.types.container :as ctn] + [app.common.types.file :as ctf] + [app.common.types.pages-list :as ctpl] + [app.common.types.shape :as cts] + [app.common.types.shape-tree :as ctst] + [app.common.uuid :as uuid])) + +(def ^:private idmap (atom {})) + +(defn reset-idmap! [] + (reset! idmap {})) + +(defn id + [label] + (get @idmap label)) + +(defn sample-file + ([file-id page-id] (sample-file file-id page-id nil)) + ([file-id page-id props] + (merge {:id file-id + :name (get props :name "File1") + :data (ctf/make-file-data file-id page-id)} + props))) + +(defn sample-shape + [file label type page-id props] + (ctf/update-file-data + file + (fn [file-data] + (let [frame-id (get props :frame-id uuid/zero) + parent-id (get props :parent-id uuid/zero) + shape (if (= type :group) + (cts/make-minimal-group frame-id + {:x 0 :y 0 :width 1 :height 1} + (get props :name "Group1")) + (cts/make-shape type + {:x 0 :y 0 :width 1 :height 1} + props))] + + (swap! idmap assoc label (:id shape)) + (ctpl/update-page file-data + page-id + #(ctst/add-shape (:id shape) + shape + % + frame-id + parent-id + 0 + true)))))) + +(defn sample-component + [file label page-id shape-id] + (ctf/update-file-data + file + (fn [file-data] + (let [page (ctpl/get-page file-data page-id) + + [component-shape component-shapes updated-shapes] + (ctn/make-component-shape (ctn/get-shape page shape-id) + (:objects page) + (:id file))] + + (swap! idmap assoc label (:id component-shape)) + (-> file-data + (ctpl/update-page page-id + #(reduce (fn [page shape] (ctst/set-shape page shape)) + % + updated-shapes)) + (ctkl/add-component (:id component-shape) + (:name component-shape) + "" + shape-id + page-id + component-shapes)))))) + +(defn sample-instance + [file label page-id library component-id] + (ctf/update-file-data + file + (fn [file-data] + (let [[instance-shape instance-shapes] + (ctn/instantiate-component (ctpl/get-page file-data page-id) + (ctkl/get-component (:data library) component-id) + (:id library) + (gpt/point 0 0))] + + (swap! idmap assoc label (:id instance-shape)) + (-> file-data + (ctpl/update-page page-id + #(reduce (fn [page shape] + (ctst/add-shape (:id shape) + shape + page + uuid/zero + (:parent-id shape) + 0 + true)) + % + instance-shapes))))))) + diff --git a/common/test/app/common/types/file_test.cljc b/common/test/app/common/types/file_test.cljc new file mode 100644 index 0000000000..0049732abf --- /dev/null +++ b/common/test/app/common/types/file_test.cljc @@ -0,0 +1,79 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.common.types.file-test + (:require + [clojure.test :as t] + [app.common.geom.point :as gpt] + [app.common.types.components-list :as ctkl] + [app.common.types.file :as ctf] + [app.common.types.pages-list :as ctpl] + [app.common.types.shape :as cts] + [app.common.types.shape-tree :as ctst] + [app.common.uuid :as uuid] + [app.common.test-helpers.files :as thf] + + [app.common.data :as d] + [app.common.pages.helpers :as cph] + [cuerdas.core :as str] + )) + +(t/use-fixtures :each + {:before thf/reset-idmap!}) + +(t/deftest test-absorb-assets + (let [library-id (uuid/custom 1 1) + library-page-id (uuid/custom 2 2) + file-id (uuid/custom 3 3) + file-page-id (uuid/custom 4 4) + + library (-> (thf/sample-file library-id library-page-id {:is-shared true}) + (thf/sample-shape :group1 + :group + library-page-id + {:name "Group1"}) + (thf/sample-shape :shape1 + :rect + library-page-id + {:name "Rect1" + :parent-id (thf/id :group1)}) + (thf/sample-component :component1 + library-page-id + (thf/id :group1))) + + file (-> (thf/sample-file file-id file-page-id) + (thf/sample-instance :instance1 + file-page-id + library + (thf/id :component1))) + + absorbed-file (ctf/update-file-data + file + #(ctf/absorb-assets % (:data library)))] + + (println "\n===== library") + (ctf/dump-tree (:data library) + library-page-id + {} + true) + + (println "\n===== file") + (ctf/dump-tree (:data file) + file-page-id + {library-id {:id library-id + :name "Library 1" + :data library}} + true) + + (println "\n===== absorbed file") + (ctf/dump-tree (:data absorbed-file) + file-page-id + {} + true) + + (t/is (= library-id (:id library))) + (t/is (= file-id (:id absorbed-file))))) + diff --git a/common/test/app/common/types/shape/spec_interactions_test.cljc b/common/test/app/common/types/shape/spec_interactions_test.cljc index 7c90b625c8..a840194963 100644 --- a/common/test/app/common/types/shape/spec_interactions_test.cljc +++ b/common/test/app/common/types/shape/spec_interactions_test.cljc @@ -4,20 +4,20 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.common.spec-interactions-test +(ns app.common.types.shape.spec-interactions-test (:require [clojure.test :as t] [clojure.pprint :refer [pprint]] [app.common.exceptions :as ex] - [app.common.pages.init :as cpi] + [app.common.types.shape :as cts] [app.common.types.shape.interactions :as ctsi] [app.common.uuid :as uuid] [app.common.geom.point :as gpt])) (t/deftest set-event-type (let [interaction ctsi/default-interaction - shape (cpi/make-minimal-shape :rect) - frame (cpi/make-minimal-shape :frame)] + shape (cts/make-minimal-shape :rect) + frame (cts/make-minimal-shape :frame)] (t/testing "Set event type unchanged" (let [new-interaction @@ -148,7 +148,7 @@ (t/deftest option-delay - (let [frame (cpi/make-minimal-shape :frame) + (let [frame (cts/make-minimal-shape :frame) i1 ctsi/default-interaction i2 (ctsi/set-event-type i1 :after-delay frame)] @@ -211,10 +211,10 @@ (t/deftest option-overlay-opts - (let [base-frame (-> (cpi/make-minimal-shape :frame) + (let [base-frame (-> (cts/make-minimal-shape :frame) (assoc-in [:selrect :width] 100) (assoc-in [:selrect :height] 100)) - overlay-frame (-> (cpi/make-minimal-shape :frame) + overlay-frame (-> (cts/make-minimal-shape :frame) (assoc-in [:selrect :width] 30) (assoc-in [:selrect :height] 20)) objects {(:id base-frame) base-frame @@ -542,12 +542,12 @@ (t/deftest remap-interactions - (let [frame1 (cpi/make-minimal-shape :frame) - frame2 (cpi/make-minimal-shape :frame) - frame3 (cpi/make-minimal-shape :frame) - frame4 (cpi/make-minimal-shape :frame) - frame5 (cpi/make-minimal-shape :frame) - frame6 (cpi/make-minimal-shape :frame) + (let [frame1 (cts/make-minimal-shape :frame) + frame2 (cts/make-minimal-shape :frame) + frame3 (cts/make-minimal-shape :frame) + frame4 (cts/make-minimal-shape :frame) + frame5 (cts/make-minimal-shape :frame) + frame6 (cts/make-minimal-shape :frame) objects {(:id frame3) frame3 (:id frame4) frame4 diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index d778cdb0bc..8407df47f8 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -14,7 +14,6 @@ [app.common.geom.proportions :as gpr] [app.common.geom.shapes :as gsh] [app.common.math :as mth] - [app.common.pages :as cp] [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] [app.common.spec :as us] @@ -1591,7 +1590,7 @@ page-id (:current-page-id state) frame-id (-> (wsh/lookup-page-objects state page-id) (ctst/frame-id-by-position @ms/mouse-position)) - shape (cp/setup-rect-selrect + shape (cts/setup-rect-selrect {:id id :type :text :name "Text" @@ -1681,12 +1680,12 @@ (let [srect (gsh/selection-rect selected-objs) frame-id (get-in objects [(first selected) :frame-id]) parent-id (get-in objects [(first selected) :parent-id]) - shape (-> (cp/make-minimal-shape :frame) + shape (-> (cts/make-minimal-shape :frame) (merge {:x (:x srect) :y (:y srect) :width (:width srect) :height (:height srect)}) (assoc :frame-id frame-id :parent-id parent-id) (cond-> (not= frame-id uuid/zero) (assoc :fills [] :hide-in-viewer true)) - (cp/setup-rect-selrect))] + (cts/setup-rect-selrect))] (rx/of (dwu/start-undo-transaction) (dwsh/add-shape shape) diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs index 39724ee8b7..6f9ac6d350 100644 --- a/frontend/src/app/main/data/workspace/common.cljs +++ b/frontend/src/app/main/data/workspace/common.cljs @@ -7,15 +7,6 @@ (ns app.main.data.workspace.common (:require [app.common.logging :as log] - [app.common.pages :as cp] - [app.common.pages.changes-builder :as pcb] - [app.common.pages.helpers :as cph] - [app.common.spec :as us] - [app.common.types.page :as ctp] - [app.common.types.shape :as cts] - [app.common.types.shape-tree :as ctt] - [app.common.types.shape.interactions :as ctsi] - [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.undo :as dwu] [app.main.worker :as uw] diff --git a/frontend/src/app/main/data/workspace/drawing.cljs b/frontend/src/app/main/data/workspace/drawing.cljs index cb75f95cf3..06384d41c0 100644 --- a/frontend/src/app/main/data/workspace/drawing.cljs +++ b/frontend/src/app/main/data/workspace/drawing.cljs @@ -7,7 +7,7 @@ (ns app.main.data.workspace.drawing "Drawing interactions." (:require - [app.common.pages :as cp] + [app.common.types.shape :as cts] [app.common.uuid :as uuid] [app.main.data.workspace.common :as dwc] [app.main.data.workspace.drawing.box :as box] @@ -91,7 +91,7 @@ (ptk/reify ::handle-drawing ptk/UpdateEvent (update [_ state] - (let [data (cp/make-minimal-shape type)] + (let [data (cts/make-minimal-shape type)] (update-in state [:workspace-drawing :object] merge data))) ptk/WatchEvent diff --git a/frontend/src/app/main/data/workspace/drawing/box.cljs b/frontend/src/app/main/data/workspace/drawing/box.cljs index aa550f98c5..1868eb61be 100644 --- a/frontend/src/app/main/data/workspace/drawing/box.cljs +++ b/frontend/src/app/main/data/workspace/drawing/box.cljs @@ -9,8 +9,8 @@ [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.math :as mth] - [app.common.pages :as cp] [app.common.pages.helpers :as cph] + [app.common.types.shape :as cts] [app.common.types.shape-tree :as ctt] [app.common.uuid :as uuid] [app.main.data.workspace.drawing.common :as common] @@ -70,7 +70,7 @@ shape (get-in state [:workspace-drawing :object]) shape (-> shape - (cp/setup-shape {:x (:x initial) + (cts/setup-shape {:x (:x initial) :y (:y initial) :width 0.01 :height 0.01}) diff --git a/frontend/src/app/main/data/workspace/drawing/common.cljs b/frontend/src/app/main/data/workspace/drawing/common.cljs index 03f956aa59..95429a6e66 100644 --- a/frontend/src/app/main/data/workspace/drawing/common.cljs +++ b/frontend/src/app/main/data/workspace/drawing/common.cljs @@ -9,8 +9,9 @@ [app.common.geom.matrix :as gmt] [app.common.geom.shapes :as gsh] [app.common.math :as mth] - [app.common.pages :as cp] [app.common.pages.helpers :as cph] + [app.common.types.shape :as cts] + [app.main.data.workspace.common :as dwc] [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.undo :as dwu] @@ -55,7 +56,7 @@ (assoc :height 17 :width 4 :grow-type :auto-width) click-draw? - (cp/setup-rect-selrect) + (cts/setup-rect-selrect) :always (-> (gsh/transform-shape) diff --git a/frontend/src/app/main/data/workspace/groups.cljs b/frontend/src/app/main/data/workspace/groups.cljs index d5533bc989..5906db729f 100644 --- a/frontend/src/app/main/data/workspace/groups.cljs +++ b/frontend/src/app/main/data/workspace/groups.cljs @@ -8,9 +8,9 @@ (:require [app.common.data :as d] [app.common.geom.shapes :as gsh] - [app.common.pages :as cp] [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] + [app.common.types.shape :as cts] [app.common.types.shape-tree :as ctt] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.selection :as dws] @@ -75,8 +75,8 @@ (ctt/generate-unique-name base-name))) selrect (gsh/selection-rect shapes) - group (-> (cp/make-minimal-group frame-id selrect gname) - (cp/setup-shape selrect) + group (-> (cts/make-minimal-group frame-id selrect gname) + (cts/setup-shape selrect) (assoc :shapes (mapv :id shapes) :parent-id parent-id :frame-id frame-id diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index 39885b8477..70000e1e04 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -56,46 +56,6 @@ ;; ---- Components and instances creation ---- -(defn make-component-shape - "Clone the shape and all children. Generate new ids and detach - from parent and frame. Update the original shapes to have links - to the new ones." - [shape objects file-id] - (assert (nil? (:component-id shape))) - (assert (nil? (:component-file shape))) - (assert (nil? (:shape-ref shape))) - (let [;; Ensure that the component root is not an instance and - ;; it's no longer tied to a frame. - update-new-shape (fn [new-shape _original-shape] - (cond-> new-shape - true - (-> (assoc :frame-id nil) - (dissoc :component-root?)) - - (nil? (:parent-id new-shape)) - (dissoc :component-id - :component-file - :shape-ref))) - - ;; Make the original shape an instance of the new component. - ;; If one of the original shape children already was a component - ;; instance, maintain this instanceness untouched. - update-original-shape (fn [original-shape new-shape] - (cond-> original-shape - (nil? (:shape-ref original-shape)) - (-> (assoc :shape-ref (:id new-shape)) - (dissoc :touched)) - - (nil? (:parent-id new-shape)) - (assoc :component-id (:id new-shape) - :component-file file-id - :component-root? true) - - (some? (:parent-id new-shape)) - (dissoc :component-root?)))] - - (ctst/clone-object shape nil objects update-new-shape update-original-shape))) - (defn generate-add-component "If there is exactly one id, and it's a group, use it as root. Otherwise, create a group that contains all ids. Then, make a component with it, @@ -115,7 +75,7 @@ (dwg/prepare-create-group it objects page-id shapes name true)) [new-shape new-shapes updated-shapes] - (make-component-shape group objects file-id) + (ctn/make-component-shape group objects file-id) changes (-> changes (pcb/add-component (:id new-shape) diff --git a/frontend/src/app/main/data/workspace/svg_upload.cljs b/frontend/src/app/main/data/workspace/svg_upload.cljs index 683bc946fd..2f3e5040eb 100644 --- a/frontend/src/app/main/data/workspace/svg_upload.cljs +++ b/frontend/src/app/main/data/workspace/svg_upload.cljs @@ -11,9 +11,9 @@ [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] - [app.common.pages :as cp] [app.common.pages.changes-builder :as pcb] [app.common.spec :refer [max-safe-int min-safe-int]] + [app.common.types.shape :as cts] [app.common.types.shape-tree :as ctt] [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] @@ -182,7 +182,7 @@ (assoc :svg-attrs attrs) (assoc :svg-viewbox (-> (select-keys svg-data [:width :height]) (assoc :x offset-x :y offset-y))) - (cp/setup-rect-selrect)))) + (cts/setup-rect-selrect)))) (defn create-svg-root [frame-id svg-data] (let [{:keys [name x y width height offset-x offset-y]} svg-data] @@ -194,7 +194,7 @@ :height height :x (+ x offset-x) :y (+ y offset-y)} - (cp/setup-rect-selrect) + (cts/setup-rect-selrect) (assoc :svg-attrs (-> (:attrs svg-data) (dissoc :viewBox :xmlns) (d/without-keys usvg/inheritable-props)))))) @@ -214,7 +214,7 @@ (assoc :svg-attrs (d/without-keys attrs usvg/inheritable-props)) (assoc :svg-viewbox (-> (select-keys svg-data [:width :height]) (assoc :x offset-x :y offset-y))) - (cp/setup-rect-selrect)))) + (cts/setup-rect-selrect)))) (defn create-path-shape [name frame-id svg-data {:keys [attrs] :as data}] (when (and (contains? attrs :d) (seq (:d attrs))) diff --git a/frontend/src/app/main/ui/workspace/viewport/selection.cljs b/frontend/src/app/main/ui/workspace/viewport/selection.cljs index 4a1e4f7de6..2f774d0d24 100644 --- a/frontend/src/app/main/ui/workspace/viewport/selection.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/selection.cljs @@ -11,7 +11,7 @@ [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] - [app.common.pages :as cp] + [app.common.types.shape :as cts] [app.main.data.workspace :as dw] [app.main.refs :as refs] [app.main.store :as st] @@ -343,7 +343,7 @@ #(->> shapes (map gsh/transform-shape) (gsh/selection-rect) - (cp/setup-shape))) + (cts/setup-shape))) on-resize (fn [current-position _initial-position event] (when (dom/left-mouse? event) @@ -371,7 +371,7 @@ #(->> shapes (map gsh/transform-shape) (gsh/selection-rect) - (cp/setup-shape)))] + (cts/setup-shape)))] [:& controls-selection {:shape shape diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index aff53ab5b0..b606ca3dba 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -14,8 +14,8 @@ [app.common.geom.shapes.path :as gpa] [app.common.logging :as log] [app.common.media :as cm] - [app.common.pages :as cp] [app.common.text :as ct] + [app.common.types.file :as ctf] [app.common.uuid :as uuid] [app.main.repo :as rp] [app.util.http :as http] @@ -133,7 +133,7 @@ :name (:name context) :is-shared (:shared context) :project-id (:project-id context) - :data (-> cp/empty-file-data (assoc :id file-id))}))) + :data (-> ctf/empty-file-data (assoc :id file-id))}))) (defn link-file-libraries "Create a new file on the back-end" diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs index 09d651d14e..077993bc3d 100644 --- a/frontend/src/debug.cljs +++ b/frontend/src/debug.cljs @@ -8,8 +8,8 @@ (:require [app.common.data :as d] [app.common.logging :as l] - [app.common.pages.helpers :as cph] [app.common.transit :as t] + [app.common.types.file :as ctf] [app.common.uuid :as uuid] [app.main.data.dashboard.shortcuts] [app.main.data.viewer.shortcuts] @@ -211,77 +211,9 @@ ([state show-ids] (dump-tree' state show-ids false)) ([state show-ids show-touched] (let [page-id (get state :current-page-id) - objects (get-in state [:workspace-data :pages-index page-id :objects]) - components (get-in state [:workspace-data :components]) - libraries (get state :workspace-libraries) - root (d/seek #(nil? (:parent-id %)) (vals objects))] - - (letfn [(show-shape [shape-id level objects] - (let [shape (get objects shape-id)] - (println (str/pad (str (str/repeat " " level) - (:name shape) - (when (seq (:touched shape)) "*") - (when show-ids (str/format " <%s>" (:id shape)))) - {:length 20 - :type :right}) - (show-component shape objects)) - (when show-touched - (when (seq (:touched shape)) - (println (str (str/repeat " " level) - " " - (str (:touched shape))))) - (when (:remote-synced? shape) - (println (str (str/repeat " " level) - " (remote-synced)")))) - (when (:shapes shape) - (dorun (for [shape-id (:shapes shape)] - (show-shape shape-id (inc level) objects)))))) - - (show-component [shape objects] - (if (nil? (:shape-ref shape)) - "" - (let [root-shape (cph/get-component-shape objects shape) - component-id (when root-shape (:component-id root-shape)) - component-file-id (when root-shape (:component-file root-shape)) - component-file (when component-file-id (get libraries component-file-id nil)) - component (when component-id - (if component-file - (get-in component-file [:data :components component-id]) - (get components component-id))) - component-shape (when (and component (:shape-ref shape)) - (get-in component [:objects (:shape-ref shape)]))] - (str/format " %s--> %s%s%s" - (cond (:component-root? shape) "#" - (:component-id shape) "@" - :else "-") - (when component-file (str/format "<%s> " (:name component-file))) - (or (:name component-shape) "?") - (if (or (:component-root? shape) - (nil? (:component-id shape)) - true) - "" - (let [component-id (:component-id shape) - component-file-id (:component-file shape) - component-file (when component-file-id (get libraries component-file-id nil)) - component (if component-file - (get-in component-file [:data :components component-id]) - (get components component-id))] - (str/format " (%s%s)" - (when component-file (str/format "<%s> " (:name component-file))) - (:name component))))))))] - - (println "[Page]") - (show-shape (:id root) 0 objects) - - (dorun (for [component (vals components)] - (do - (println) - (println (str/format "[%s]" (:name component)) - (when show-ids - (str/format " (main: %s/%s)" - (:main-instance-page component) - (:main-instance-id component)))) - (show-shape (:id component) 0 (:objects component))))))))) + file-data (get state :workspace-data) + libraries (get state :workspace-libraries)] + (ctf/dump-tree file-data page-id libraries show-ids show-touched)))) (defn ^:export dump-tree ([] (dump-tree' @st/state)) diff --git a/frontend/test/app/components_basic_test.cljs b/frontend/test/app/components_basic_test.cljs index bc0d2b7447..05474e551f 100644 --- a/frontend/test/app/components_basic_test.cljs +++ b/frontend/test/app/components_basic_test.cljs @@ -3,7 +3,7 @@ [app.common.data :as d] [app.common.geom.point :as gpt] [app.common.pages.helpers :as cph] - [app.common.types.container :as ctc] + [app.common.types.container :as ctn] [app.main.data.workspace :as dw] [app.main.data.workspace.groups :as dwg] [app.main.data.workspace.libraries :as dwl] @@ -521,7 +521,7 @@ ; (let [page (thp/current-page new-state) shape1 (thp/get-shape new-state :shape1) - parent1 (ctc/get-shape page (:parent-id shape1)) + parent1 (ctn/get-shape page (:parent-id shape1)) [[group shape1 shape2] [c-group c-shape1 c-shape2] diff --git a/frontend/test/app/components_sync_test.cljs b/frontend/test/app/components_sync_test.cljs index 442b8c60f0..05bf78ea2c 100644 --- a/frontend/test/app/components_sync_test.cljs +++ b/frontend/test/app/components_sync_test.cljs @@ -4,7 +4,7 @@ [app.common.data :as d] [app.common.geom.point :as gpt] [app.common.pages.helpers :as cph] - [app.common.types.container :as ctc] + [app.common.types.container :as ctn] [app.main.data.workspace :as dw] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.shapes :as dwsh] @@ -1353,7 +1353,7 @@ instance1 (thp/get-shape state :instance1) instance2 (thp/get-shape state :instance2) - shape2 (ctc/get-shape (wsh/lookup-page state) + shape2 (ctn/get-shape (wsh/lookup-page state) (first (:shapes instance2))) update-fn1 (fn [shape] diff --git a/frontend/test/app/test_helpers/libraries.cljs b/frontend/test/app/test_helpers/libraries.cljs index 428653234c..6e0142b3e2 100644 --- a/frontend/test/app/test_helpers/libraries.cljs +++ b/frontend/test/app/test_helpers/libraries.cljs @@ -8,7 +8,7 @@ [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.pages.helpers :as cph] - [app.common.types.container :as ctc] + [app.common.types.container :as ctn] [app.main.data.workspace :as dw] [app.main.data.workspace.libraries-helpers :as dwlh] [app.main.data.workspace.state-helpers :as wsh] @@ -60,7 +60,7 @@ verify that they are a well constructed instance tree." [state root-inst-id] (let [page (thp/current-page state) - root-inst (ctc/get-shape page root-inst-id) + root-inst (ctn/get-shape page root-inst-id) shapes-inst (cph/get-children-with-self (:objects page) root-inst-id)] (is-instance-root (first shapes-inst)) @@ -73,7 +73,7 @@ verify that they are not a component instance." [state root-inst-id] (let [page (thp/current-page state) - root-inst (ctc/get-shape page root-inst-id) + root-inst (ctn/get-shape page root-inst-id) shapes-inst (cph/get-children-with-self (:objects page) root-inst-id)] (run! is-noninstance shapes-inst) @@ -85,7 +85,7 @@ the main component and all its shapes." [state root-inst-id] (let [page (thp/current-page state) - root-inst (ctc/get-shape page root-inst-id) + root-inst (ctn/get-shape page root-inst-id) libs (wsh/get-libraries state) component (cph/get-component libs (:component-id root-inst)) @@ -103,7 +103,7 @@ (cph/get-component libs (:component-id component-shape)) main-shape - (ctc/get-shape component (:shape-ref shape))] + (ctn/get-shape component (:shape-ref shape))] (t/is (some? main-shape))))] @@ -123,7 +123,7 @@ corresponding component shape missing." [state root-inst-id] (let [page (thp/current-page state) - root-inst (ctc/get-shape page root-inst-id) + root-inst (ctn/get-shape page root-inst-id) libs (wsh/get-libraries state) component (cph/get-component libs (:component-id root-inst)) @@ -141,7 +141,7 @@ (cph/get-component libs (:component-id component-shape)) main-shape - (ctc/get-shape component (:shape-ref shape))] + (ctn/get-shape component (:shape-ref shape))] (t/is (some? main-shape))))] diff --git a/frontend/test/app/test_helpers/pages.cljs b/frontend/test/app/test_helpers/pages.cljs index 6156a02cea..a061ecc43e 100644 --- a/frontend/test/app/test_helpers/pages.cljs +++ b/frontend/test/app/test_helpers/pages.cljs @@ -9,6 +9,7 @@ [app.common.geom.shapes :as gsh] [app.common.pages :as cp] [app.common.pages.helpers :as cph] + [app.common.types.shape :as cts] [app.main.data.workspace :as dw] [app.main.data.workspace.groups :as dwg] [app.main.data.workspace.layout :as layout] @@ -69,9 +70,7 @@ ([state label type props] (let [page (current-page state) frame (cph/get-frame (:objects page)) - shape (-> (cp/make-minimal-shape type) - (cp/setup-shape {:x 0 :y 0 :width 1 :height 1}) - (merge props))] + shape (cts/make-shape type {:x 0 :y 0 :width 1 :height 1} props)] (swap! idmap assoc label (:id shape)) (update state :workspace-data cp/process-changes diff --git a/frontend/test/app/util/snap_data_test.cljs b/frontend/test/app/util/snap_data_test.cljs index 6982d4f6f5..9f18e5a1c1 100644 --- a/frontend/test/app/util/snap_data_test.cljs +++ b/frontend/test/app/util/snap_data_test.cljs @@ -9,7 +9,6 @@ [app.common.uuid :as uuid] [cljs.test :as t :include-macros true] [cljs.pprint :refer [pprint]] - [app.common.pages.init :as init] [app.common.file-builder :as fb] [app.util.snap-data :as sd])) From 7da159d52aedf33e394f96338e702ae7925775b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Wed, 29 Jun 2022 15:23:29 +0200 Subject: [PATCH 06/14] :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 b3ee8cb3b8..ccdacf86ad 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 601671bfda..cb186933f2 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 0000000000..5dbe238656 --- /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 6f5643e673..6fb2b08728 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 7f7e8d9855..63a5985045 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 b999f3ccda..e9543453d1 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 c7294699a4..b03054e245 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 0049732abf..76764c69da 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 237ce60294..880db210c5 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 3e19ec5f91..0e6debc1a1 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]) From 43e0b5cfa56ff618cabfea0fe56777ad9fdc4a61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Fri, 1 Jul 2022 16:51:49 +0200 Subject: [PATCH 07/14] :tada: Absorb colors and typographies --- common/src/app/common/pages/changes.cljc | 8 +- common/src/app/common/pages/helpers.cljc | 15 -- common/src/app/common/pages/migrations.cljc | 3 +- common/src/app/common/types/color.cljc | 149 ++++++++++----- common/src/app/common/types/colors_list.cljc | 26 +++ common/src/app/common/types/component.cljc | 21 ++- common/src/app/common/types/container.cljc | 3 +- common/src/app/common/types/file.cljc | 158 ++++++---------- .../app/common/types/typographies_list.cljc | 26 +++ common/src/app/common/types/typography.cljc | 36 +++- .../app/common/test_helpers/components.cljc | 150 +++++++++++++++ .../test/app/common/test_helpers/files.cljc | 53 +++++- common/test/app/common/types/file_test.cljc | 173 +++++++++++++++--- .../data/workspace/libraries_helpers.cljs | 26 ++- .../src/app/main/data/workspace/shapes.cljs | 3 +- .../app/main/ui/workspace/sidebar/layers.cljs | 3 +- .../sidebar/options/menus/component.cljs | 3 +- frontend/test/app/test_helpers/libraries.cljs | 11 +- 18 files changed, 644 insertions(+), 223 deletions(-) create mode 100644 common/src/app/common/types/colors_list.cljc create mode 100644 common/src/app/common/types/typographies_list.cljc create mode 100644 common/test/app/common/test_helpers/components.cljc diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc index 139d37e652..2004050617 100644 --- a/common/src/app/common/pages/changes.cljc +++ b/common/src/app/common/pages/changes.cljc @@ -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]}] diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index 5984cd46ae..23424981c3 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -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] diff --git a/common/src/app/common/pages/migrations.cljc b/common/src/app/common/pages/migrations.cljc index cb186933f2..c9f2881686 100644 --- a/common/src/app/common/pages/migrations.cljc +++ b/common/src/app/common/pages/migrations.cljc @@ -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 diff --git a/common/src/app/common/types/color.cljc b/common/src/app/common/types/color.cljc index 674de1cc2c..ea9ad35d63 100644 --- a/common/src/app/common/types/color.cljc +++ b/common/src/app/common/types/color.cljc @@ -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 $)))) diff --git a/common/src/app/common/types/colors_list.cljc b/common/src/app/common/types/colors_list.cljc new file mode 100644 index 0000000000..d9378b1b38 --- /dev/null +++ b/common/src/app/common/types/colors_list.cljc @@ -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)) + diff --git a/common/src/app/common/types/component.cljc b/common/src/app/common/types/component.cljc index 5dbe238656..85a15fed3e 100644 --- a/common/src/app/common/types/component.cljc +++ b/common/src/app/common/types/component.cljc @@ -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)])) diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index 63a5985045..266aac703d 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -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 diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index e9543453d1..43c4a58cb4 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -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 diff --git a/common/src/app/common/types/typographies_list.cljc b/common/src/app/common/types/typographies_list.cljc new file mode 100644 index 0000000000..ae3e794527 --- /dev/null +++ b/common/src/app/common/types/typographies_list.cljc @@ -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)) + diff --git a/common/src/app/common/types/typography.cljc b/common/src/app/common/types/typography.cljc index ff63bf14be..036b8fe341 100644 --- a/common/src/app/common/types/typography.cljc +++ b/common/src/app/common/types/typography.cljc @@ -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))))) diff --git a/common/test/app/common/test_helpers/components.cljc b/common/test/app/common/test_helpers/components.cljc new file mode 100644 index 0000000000..5cadf9a7d5 --- /dev/null +++ b/common/test/app/common/test_helpers/components.cljc @@ -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])) + diff --git a/common/test/app/common/test_helpers/files.cljc b/common/test/app/common/test_helpers/files.cljc index bbb9c58dd2..d6728d3685 100644 --- a/common/test/app/common/test_helpers/files.cljc +++ b/common/test/app/common/test_helpers/files.cljc @@ -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))))) + diff --git a/common/test/app/common/types/file_test.cljc b/common/test/app/common/types/file_test.cljc index 76764c69da..bb297f8bff 100644 --- a/common/test/app/common/types/file_test.cljc +++ b/common/test/app/common/types/file_test.cljc @@ -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)))) diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index 70000e1e04..1bb134525f 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -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)] diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index 880db210c5..e33522b54c 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -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)) diff --git a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs index 27a9fe4685..0cdd5193ce 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs @@ -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 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 0e6debc1a1..4f4ed16074 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 @@ -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 diff --git a/frontend/test/app/test_helpers/libraries.cljs b/frontend/test/app/test_helpers/libraries.cljs index 6e0142b3e2..26cc294a63 100644 --- a/frontend/test/app/test_helpers/libraries.cljs +++ b/frontend/test/app/test_helpers/libraries.cljs @@ -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 From dcf18b3aeeae55e932cce09456ca85317109401e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 7 Jul 2022 16:07:34 +0200 Subject: [PATCH 08/14] :wrench: Refactor sync-file for performance --- .../src/app/common/pages/changes_builder.cljc | 4 +- common/src/app/common/types/color.cljc | 4 +- common/src/app/common/types/component.cljc | 10 +- common/src/app/common/types/file.cljc | 6 +- common/src/app/common/types/typography.cljc | 4 +- common/test/app/common/types/file_test.cljc | 2 +- .../app/main/data/workspace/libraries.cljs | 136 ++++++++++-------- .../data/workspace/libraries_helpers.cljs | 50 +++++-- .../app/main/ui/workspace/sidebar/assets.cljs | 6 +- frontend/test/app/components_basic_test.cljs | 2 +- frontend/test/app/test_helpers/libraries.cljs | 1 + 11 files changed, 136 insertions(+), 89 deletions(-) diff --git a/common/src/app/common/pages/changes_builder.cljc b/common/src/app/common/pages/changes_builder.cljc index 50891acc15..03b8e1ca44 100644 --- a/common/src/app/common/pages/changes_builder.cljc +++ b/common/src/app/common/pages/changes_builder.cljc @@ -112,7 +112,9 @@ redo-changes (:redo-changes changes) new-changes (if (< index (count redo-changes)) (->> (subvec (:redo-changes changes) index) - (map #(assoc % :page-id uuid/zero))) + (map #(-> % + (assoc :page-id uuid/zero) + (dissoc :component-id)))) []) new-file-data (cp/process-changes file-data new-changes)] (vary-meta changes assoc ::file-data new-file-data diff --git a/common/src/app/common/types/color.cljc b/common/src/app/common/types/color.cljc index ea9ad35d63..5f462796c3 100644 --- a/common/src/app/common/types/color.cljc +++ b/common/src/app/common/types/color.cljc @@ -238,9 +238,9 @@ (defn uses-library-color? "Check if the shape uses the given library color." - [shape library-id color] + [shape library-id color-id] (let [all-colors (get-all-colors shape)] - (some #(and (= (:ref-id %) (:id color)) + (some #(and (= (:ref-id %) color-id) (= (:ref-file %) library-id)) all-colors))) diff --git a/common/src/app/common/types/component.cljc b/common/src/app/common/types/component.cljc index 85a15fed3e..279b8e247e 100644 --- a/common/src/app/common/types/component.cljc +++ b/common/src/app/common/types/component.cljc @@ -7,10 +7,10 @@ (ns app.common.types.component) (defn instance-of? - [shape file-id component] + [shape file-id component-id] (and (some? (:component-id shape)) (some? (:component-file shape)) - (= (:component-id shape) (:id component)) + (= (:component-id shape) component-id) (= (:component-file shape) file-id))) (defn is-main-of? @@ -28,3 +28,9 @@ [component] (get-in component [:objects (:id component)])) +(defn uses-library-components? + "Check if the shape uses any component in the given library." + [shape library-id] + (and (some? (:component-id shape)) + (= (:component-file shape) library-id))) + diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index 43c4a58cb4..c6015374a3 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -123,15 +123,15 @@ (defmethod uses-asset? :component [_ shape library-id component] - (ctk/instance-of? shape library-id component)) + (ctk/instance-of? shape library-id (:id component))) (defmethod uses-asset? :color [_ shape library-id color] - (ctc/uses-library-color? shape library-id color)) + (ctc/uses-library-color? shape library-id (:id color))) (defmethod uses-asset? :typography [_ shape library-id typography] - (cty/uses-library-typography? shape library-id typography)) + (cty/uses-library-typography? shape library-id (:id typography))) (defn find-asset-type-usages "Find all usages of an asset in a file (may be in pages or in the components diff --git a/common/src/app/common/types/typography.cljc b/common/src/app/common/types/typography.cljc index 036b8fe341..0c1a15ef7d 100644 --- a/common/src/app/common/types/typography.cljc +++ b/common/src/app/common/types/typography.cljc @@ -49,13 +49,13 @@ (defn uses-library-typography? "Check if the shape uses the given library typography." - [shape library-id typography] + [shape library-id typography-id] (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)) + #(and (= (:typography-ref-id %) typography-id) (= (:typography-ref-file %) library-id)))))) (defn remap-typographies diff --git a/common/test/app/common/types/file_test.cljc b/common/test/app/common/types/file_test.cljc index bb297f8bff..5c5f8527ff 100644 --- a/common/test/app/common/types/file_test.cljc +++ b/common/test/app/common/types/file_test.cljc @@ -109,7 +109,7 @@ (t/is (= (count components) 1)) (t/is (= (:name p-group) "Group1")) - (t/is (ctk/instance-of? p-group file-id component1)) + (t/is (ctk/instance-of? p-group file-id (: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)) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 2217af1dde..6be2910206 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -138,7 +138,7 @@ (pcb/update-color color))] (rx/of (dwu/start-undo-transaction) (dch/commit-changes changes) - (sync-file (:current-file-id state) file-id) + (sync-file (:current-file-id state) file-id :colors (:id color)) (dwu/commit-undo-transaction)))) (defn update-color @@ -241,7 +241,7 @@ (pcb/update-typography typography))] (rx/of (dwu/start-undo-transaction) (dch/commit-changes changes) - (sync-file (:current-file-id state) file-id) + (sync-file (:current-file-id state) file-id :typographies (:id typography)) (dwu/commit-undo-transaction)))) (defn update-typography @@ -577,13 +577,15 @@ (ptk/reify ::update-component-sync ptk/WatchEvent (watch [_ state _] - (let [current-file-id (:current-file-id state)] + (let [current-file-id (:current-file-id state) + page (wsh/lookup-page state) + shape (ctn/get-shape page shape-id)] (rx/of (dwu/start-undo-transaction) (update-component shape-id) - (sync-file current-file-id file-id) + (sync-file current-file-id file-id :components (:component-id shape)) (when (not= current-file-id file-id) - (sync-file file-id file-id)) + (sync-file file-id file-id :components (:component-id shape))) (dwu/commit-undo-transaction)))))) (defn update-component-in-bulk @@ -603,62 +605,77 @@ shapes in all pages in the file that use some color, typography or component of the library, and copy the new values to the shapes. Do it also for shapes inside components of the local file library." - [file-id library-id] - (us/assert ::us/uuid file-id) - (us/assert ::us/uuid library-id) - (ptk/reify ::sync-file - ptk/UpdateEvent - (update [_ state] - (if (not= library-id (:current-file-id state)) - (d/assoc-in-when state [:workspace-libraries library-id :synced-at] (dt/now)) - state)) + ([file-id library-id] + (sync-file file-id library-id nil nil)) + ([file-id library-id asset-type asset-id] + (us/assert ::us/uuid file-id) + (us/assert ::us/uuid library-id) + (us/assert (s/nilable #{:colors :components :typographies}) asset-type) + (us/assert (s/nilable ::us/uuid) asset-id) + (ptk/reify ::sync-file + ptk/UpdateEvent + (update [_ state] + (if (not= library-id (:current-file-id state)) + (d/assoc-in-when state [:workspace-libraries library-id :synced-at] (dt/now)) + state)) - ptk/WatchEvent - (watch [it state _] - (when (and (some? file-id) (some? library-id)) ; Prevent race conditions while navigating out of the file - (log/info :msg "SYNC-FILE" - :file (dwlh/pretty-file file-id state) - :library (dwlh/pretty-file library-id state)) - (let [file (wsh/get-file state file-id) + ptk/WatchEvent + (watch [it state _] + (when (and (some? file-id) (some? library-id)) ; Prevent race conditions while navigating out of the file + (log/info :msg "SYNC-FILE" + :file (dwlh/pretty-file file-id state) + :library (dwlh/pretty-file library-id state)) + (let [file (wsh/get-file state file-id) - library-changes (reduce - pcb/concat-changes - (pcb/empty-changes it) - [(dwlh/generate-sync-library it file-id :components library-id state) - (dwlh/generate-sync-library it file-id :colors library-id state) - (dwlh/generate-sync-library it file-id :typographies library-id state)]) - file-changes (reduce - pcb/concat-changes - (pcb/empty-changes it) - [(dwlh/generate-sync-file it file-id :components library-id state) - (dwlh/generate-sync-file it file-id :colors library-id state) - (dwlh/generate-sync-file it file-id :typographies library-id state)]) + sync-components? (or (nil? asset-type) (= asset-type :components)) + sync-colors? (or (nil? asset-type) (= asset-type :colors)) + sync-typographies? (or (nil? asset-type) (= asset-type :typographies)) - changes (pcb/concat-changes library-changes file-changes)] + library-changes (reduce + pcb/concat-changes + (pcb/empty-changes it) + [(when sync-components? + (dwlh/generate-sync-library it file-id :components asset-id library-id state)) + (when sync-colors? + (dwlh/generate-sync-library it file-id :colors asset-id library-id state)) + (when sync-typographies? + (dwlh/generate-sync-library it file-id :typographies asset-id library-id state))]) + file-changes (reduce + pcb/concat-changes + (pcb/empty-changes it) + [(when sync-components? + (dwlh/generate-sync-file it file-id :components asset-id library-id state)) + (when sync-colors? + (dwlh/generate-sync-file it file-id :colors asset-id library-id state)) + (when sync-typographies? + (dwlh/generate-sync-file it file-id :typographies asset-id library-id state))]) - (log/debug :msg "SYNC-FILE finished" :js/rchanges (log-changes - (:redo-changes changes) - file)) - (rx/concat - (rx/of (dm/hide-tag :sync-dialog)) - (when (seq (:redo-changes changes)) - (rx/of (dch/commit-changes (assoc changes ;; TODO a ver qué pasa con esto - :file-id file-id)))) - (when (not= file-id library-id) - ;; When we have just updated the library file, give some time for the - ;; update to finish, before marking this file as synced. - ;; TODO: look for a more precise way of syncing this. - ;; Maybe by using the stream (second argument passed to watch) - ;; to wait for the corresponding changes-committed and then proceed - ;; with the :update-sync mutation. - (rx/concat (rx/timer 3000) - (rp/mutation :update-sync - {:file-id file-id - :library-id library-id}))) - (when (seq (:redo-changes library-changes)) - (rx/of (sync-file-2nd-stage file-id library-id))))))))) + changes (pcb/concat-changes library-changes file-changes)] -(defn sync-file-2nd-stage + (log/debug :msg "SYNC-FILE finished" :js/rchanges (log-changes + (:redo-changes changes) + file)) + (rx/concat + (rx/of (dm/hide-tag :sync-dialog)) + (when (seq (:redo-changes changes)) + (rx/of (dch/commit-changes (assoc changes ;; TODO a ver qué pasa con esto + :file-id file-id)))) + (when (not= file-id library-id) + ;; When we have just updated the library file, give some time for the + ;; update to finish, before marking this file as synced. + ;; TODO: look for a more precise way of syncing this. + ;; Maybe by using the stream (second argument passed to watch) + ;; to wait for the corresponding changes-committed and then proceed + ;; with the :update-sync mutation. + (rx/concat (rx/timer 3000) + (rp/mutation :update-sync + {:file-id file-id + :library-id library-id}))) + (when (and (seq (:redo-changes library-changes)) + sync-components?) + (rx/of (sync-file-2nd-stage file-id library-id)))))))))) + +(defn- sync-file-2nd-stage "If some components have been modified, we need to launch another synchronization to update the instances of the changed components." ;; TODO: this does not work if there are multiple nested components. Only the @@ -667,9 +684,10 @@ ;; recursively. But for this not to cause an infinite loop, we need to ;; implement updated-at at component level, to detect what components have ;; not changed, and then not to apply sync and terminate the loop. - [file-id library-id] + [file-id library-id asset-id] (us/assert ::us/uuid file-id) (us/assert ::us/uuid library-id) + (us/assert (s/nilable ::us/uuid) asset-id) (ptk/reify ::sync-file-2nd-stage ptk/WatchEvent (watch [it state _] @@ -680,8 +698,8 @@ changes (reduce pcb/concat-changes (pcb/empty-changes it) - [(dwlh/generate-sync-file it file-id :components library-id state) - (dwlh/generate-sync-library it file-id :components library-id state)])] + [(dwlh/generate-sync-file it file-id :components asset-id library-id state) + (dwlh/generate-sync-library it file-id :components asset-id library-id state)])] (when (seq (:redo-changes changes)) (log/debug :msg "SYNC-FILE (2nd stage) finished" :js/rchanges (log-changes (:redo-changes changes) diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index 1bb134525f..4103e81378 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -154,14 +154,19 @@ (defn generate-sync-file "Generate changes to synchronize all shapes in all pages of the given file, - that use assets of the given type in the given library." - [it file-id asset-type library-id state] + that use assets of the given type in the given library. + + If an asset id is given, only shapes linked to this particular asset will + be syncrhonized." + [it file-id asset-type asset-id library-id state] (s/assert #{:colors :components :typographies} asset-type) + (s/assert (s/nilable ::us/uuid) asset-id) (s/assert ::us/uuid file-id) (s/assert ::us/uuid library-id) (log/info :msg "Sync file with library" :asset-type asset-type + :asset-id asset-id :file (pretty-file file-id state) :library (pretty-file library-id state)) @@ -174,6 +179,7 @@ changes (generate-sync-container it asset-type + asset-id library-id state (cph/make-container page :page)))) @@ -182,11 +188,19 @@ (defn generate-sync-library "Generate changes to synchronize all shapes in all components of the local library of the given file, that use assets of the given type in - the given library." - [it file-id asset-type library-id state] + the given library. + + If an asset id is given, only shapes linked to this particular asset will + be syncrhonized." + [it file-id asset-type asset-id library-id state] + (s/assert #{:colors :components :typographies} asset-type) + (s/assert (s/nilable ::us/uuid) asset-id) + (s/assert ::us/uuid file-id) + (s/assert ::us/uuid library-id) (log/info :msg "Sync local components with library" :asset-type asset-type + :asset-id asset-id :file (pretty-file file-id state) :library (pretty-file library-id state)) @@ -199,6 +213,7 @@ changes (generate-sync-container it asset-type + asset-id library-id state (cph/make-container local-component :component)))) @@ -207,14 +222,14 @@ (defn- generate-sync-container "Generate changes to synchronize all shapes in a particular container (a page or a component) that use assets of the given type in the given library." - [it asset-type library-id state container] + [it asset-type asset-id library-id state container] (if (cph/page? container) (log/debug :msg "Sync page in local file" :page-id (:id container)) (log/debug :msg "Sync component in local library" :component-id (:id container))) - (let [linked-shapes (->> (vals (:objects container)) - (filter #(uses-assets? asset-type % library-id (cph/page? container))))] + (let [linked-shapes (->> (vals (:objects container)) + (filter #(uses-assets? asset-type asset-id % library-id (cph/page? container))))] (loop [shapes (seq linked-shapes) changes (-> (pcb/empty-changes it) (pcb/with-container container) @@ -231,21 +246,26 @@ (defmulti uses-assets? "Checks if a shape uses some asset of the given type in the given library." - (fn [asset-type _ _ _] asset-type)) + (fn [asset-type _ _ _ _] asset-type)) (defmethod uses-assets? :components - [_ shape library-id page?] - (and (some? (:component-id shape)) - (= (:component-file shape) library-id) + [_ component-id shape library-id page?] + (and (if (nil? component-id) + (ctk/uses-library-components? shape library-id) + (ctk/instance-of? shape library-id component-id)) (or (:component-root? shape) (not page?)))) ; avoid nested components inside pages (defmethod uses-assets? :colors - [_ shape library-id _] - (ctc/uses-library-colors? shape library-id)) + [_ color-id shape library-id _] + (if (nil? color-id) + (ctc/uses-library-colors? shape library-id) + (ctc/uses-library-color? shape library-id color-id))) (defmethod uses-assets? :typographies - [_ shape library-id _] - (cty/uses-library-typographies? shape library-id)) + [_ typography-id shape library-id _] + (if (nil? typography-id) + (cty/uses-library-typographies? shape library-id) + (cty/uses-library-typography? shape library-id typography-id))) (defmulti generate-sync-shape "Generate changes to synchronize one shape from all assets of the given type diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index 8e408c0609..fc84f89509 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -568,7 +568,7 @@ (on-assets-delete) (st/emit! (dwu/start-undo-transaction) (dwl/delete-component {:id (:component-id @state)}) - (dwl/sync-file file-id file-id) + (dwl/sync-file file-id file-id :components (:component-id @state)) (dwu/commit-undo-transaction))))) on-rename @@ -1120,7 +1120,7 @@ (on-assets-delete) (st/emit! (dwu/start-undo-transaction) (dwl/delete-color color) - (dwl/sync-file file-id file-id) + (dwl/sync-file file-id file-id :color (:id color)) (dwu/commit-undo-transaction))))) rename-color-clicked @@ -1762,7 +1762,7 @@ (on-assets-delete) (st/emit! (dwu/start-undo-transaction) (dwl/delete-typography (:id @state)) - (dwl/sync-file file-id file-id) + (dwl/sync-file file-id file-id :typographies (:id @state)) (dwu/commit-undo-transaction))))) editing-id (or (:rename-typography local-data) diff --git a/frontend/test/app/components_basic_test.cljs b/frontend/test/app/components_basic_test.cljs index 05474e551f..52958aee5f 100644 --- a/frontend/test/app/components_basic_test.cljs +++ b/frontend/test/app/components_basic_test.cljs @@ -341,7 +341,7 @@ (ptk/emit! store (dwl/delete-component {:id component-id}) - (dwl/sync-file (:id file) (:id file)) + (dwl/sync-file (:id file) (:id file) :components component-id) :the/end)))) (t/deftest test-instantiate-component diff --git a/frontend/test/app/test_helpers/libraries.cljs b/frontend/test/app/test_helpers/libraries.cljs index 26cc294a63..b2754977b4 100644 --- a/frontend/test/app/test_helpers/libraries.cljs +++ b/frontend/test/app/test_helpers/libraries.cljs @@ -5,6 +5,7 @@ [app.common.pages.helpers :as cph] [app.common.types.component :as ctk] [app.common.types.container :as ctn] + [app.main.data.workspace.state-helpers :as wsh] [app.test-helpers.pages :as thp])) ;; ---- Helpers to manage libraries and synchronization From eebd596fca3fe448ca810784b276606d880d4d5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Fri, 8 Jul 2022 12:09:28 +0200 Subject: [PATCH 09/14] :recycle: Use main-instance? attribute --- common/src/app/common/pages/changes_builder.cljc | 3 +++ common/src/app/common/pages/migrations.cljc | 9 +++++---- common/src/app/common/types/container.cljc | 10 +++++++--- common/src/app/common/types/file.cljc | 9 +++++---- common/test/app/common/test_helpers/files.cljc | 9 +++++---- .../main/data/workspace/libraries_helpers.cljs | 15 ++++++++------- 6 files changed, 33 insertions(+), 22 deletions(-) diff --git a/common/src/app/common/pages/changes_builder.cljc b/common/src/app/common/pages/changes_builder.cljc index 03b8e1ca44..3652bed1c4 100644 --- a/common/src/app/common/pages/changes_builder.cljc +++ b/common/src/app/common/pages/changes_builder.cljc @@ -564,6 +564,9 @@ {:type :set :attr :component-root? :val (:component-root? shape)} + {:type :set + :attr :main-instance? + :val (:main-instance? shape)} {:type :set :attr :shape-ref :val (:shape-ref shape)} diff --git a/common/src/app/common/pages/migrations.cljc b/common/src/app/common/pages/migrations.cljc index c9f2881686..d7cb9a8630 100644 --- a/common/src/app/common/pages/migrations.cljc +++ b/common/src/app/common/pages/migrations.cljc @@ -455,10 +455,11 @@ (let [page (ctpl/get-page data page-id) [new-shape new-shapes] - (ctn/instantiate-component page - component - (:id data) - position) + (ctn/make-component-instance page + component + (:id data) + position + true) add-shapes (fn [page] diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index 266aac703d..a0f63f0f1e 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -94,15 +94,16 @@ (nil? (:parent-id new-shape)) (assoc :component-id (:id new-shape) :component-file file-id - :component-root? true) + :component-root? true + :main-instance? true) (some? (:parent-id new-shape)) (dissoc :component-root?)))] (ctst/clone-object shape nil objects update-new-shape update-original-shape))) -(defn instantiate-component - [container component component-file-id position] +(defn make-component-instance + [container component component-file-id position main-instance?] (let [component-shape (get-shape component (:id component)) orig-pos (gpt/point (:x component-shape) (:y component-shape)) @@ -138,6 +139,9 @@ :component-root? true :name new-name) + (and (nil? (:parent-id original-shape)) main-instance?) + (assoc :main-instance? true) + (some? (:parent-id original-shape)) (dissoc :component-root?)))) diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index c6015374a3..fb51d9fb71 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -194,10 +194,11 @@ ; Make a new main instance for the component [main-instance-shape main-instance-shapes] - (ctn/instantiate-component page - component - (:id file-data) - position) + (ctn/make-component-instance page + component + (:id file-data) + position + true) ; Add all shapes of the main instance to the library page add-main-instance-shapes diff --git a/common/test/app/common/test_helpers/files.cljc b/common/test/app/common/test_helpers/files.cljc index d6728d3685..d6b684d25d 100644 --- a/common/test/app/common/test_helpers/files.cljc +++ b/common/test/app/common/test_helpers/files.cljc @@ -91,10 +91,11 @@ file (fn [file-data] (let [[instance-shape instance-shapes] - (ctn/instantiate-component (ctpl/get-page file-data page-id) - (ctkl/get-component (:data library) component-id) - (:id library) - (gpt/point 0 0))] + (ctn/make-component-instance (ctpl/get-page file-data page-id) + (ctkl/get-component (:data library) component-id) + (:id library) + (gpt/point 0 0) + false)] (swap! idmap assoc label (:id instance-shape)) (-> file-data diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index 4103e81378..41a29f64e7 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -106,12 +106,13 @@ [new-instance-shape new-instance-shapes] - (ctn/instantiate-component main-instance-page - {:id (:id new-component-shape) - :name (:name new-component-shape) - :objects (d/index-by :id new-component-shapes)} - (:component-file main-instance-shape) - position)] + (ctn/make-component-instance main-instance-page + {:id (:id new-component-shape) + :name (:name new-component-shape) + :objects (d/index-by :id new-component-shapes)} + (:component-file main-instance-shape) + position + false)] [new-component-shape new-component-shapes new-instance-shape new-instance-shapes])) @@ -122,7 +123,7 @@ (let [component (cph/get-component libraries file-id component-id) [new-shape new-shapes] - (ctn/instantiate-component page component file-id position) + (ctn/make-component-instance page component file-id position false) changes (reduce #(pcb/add-object %1 %2 {:ignore-touched true}) (pcb/empty-changes it (:id page)) From 1ef37281e6d5a1b36252a370652e92d6af23e3e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Fri, 8 Jul 2022 16:43:37 +0200 Subject: [PATCH 10/14] :tada: Auto sync when changing main instance --- common/src/app/common/pages/changes.cljc | 33 +++++++++++++- frontend/src/app/main/data/workspace.cljs | 3 +- .../app/main/data/workspace/libraries.cljs | 45 ++++++++++++++++++- .../data/workspace/libraries_helpers.cljs | 1 + .../app/main/ui/workspace/sidebar/assets.cljs | 1 - 5 files changed, 78 insertions(+), 5 deletions(-) diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc index 2004050617..f47ed59a27 100644 --- a/common/src/app/common/pages/changes.cljc +++ b/common/src/app/common/pages/changes.cljc @@ -17,6 +17,7 @@ [app.common.spec :as us] [app.common.pages.changes-spec :as pcs] [app.common.types.components-list :as ctkl] + [app.common.types.container :as ctn] [app.common.types.colors-list :as ctcl] [app.common.types.page :as ctp] [app.common.types.pages-list :as ctpl] @@ -37,7 +38,7 @@ ;; Page Transformation Changes ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; --- Changes Processing Impl +;; === Changes Processing Impl (defmulti process-change (fn [_ change] (:type change))) (defmulti process-operation (fn [_ op] (:type op))) @@ -387,7 +388,7 @@ [data {:keys [id]}] (update data :typographies dissoc id)) -;; -- Operations +;; === Operations (defmethod process-operation :set [shape op] @@ -451,3 +452,31 @@ (ex/raise :type :not-implemented :code :operation-not-implemented :context {:type (:type op)})) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Component changes detection +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Analyze one change and checks if if modifies the main instance of +;; any component, so that it needs to be synced immediately to the +;; main component. Return the ids of the components that need sync. + +(defmulti components-changed (fn [_ change] (:type change))) + +(defmethod components-changed :mod-obj + [file-data {:keys [id page-id component-id operations]}] + (when page-id + (let [page (ctpl/get-page file-data page-id) + shape-and-parents (map #(ctn/get-shape page %) + (into [id] (cph/get-parent-ids (:objects page) id))) + any-set? (some #(= (:type %) :set) operations)] + (when any-set? + (into #{} (->> shape-and-parents + (filter #(:main-instance? %)) + (map :id))))))) + +(defmethod components-changed :default + [_ _] + nil) + diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 8407df47f8..0b6db38e10 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -214,7 +214,8 @@ (watch [_ state _] (if (contains? (get-in state [:workspace-data :pages-index]) page-id) (rx/of (dwp/preload-data-uris) - (dwth/watch-state-changes)) + (dwth/watch-state-changes) + (dwl/watch-component-changes)) (let [default-page-id (get-in state [:workspace-data :pages 0])] (rx/of (go-to-page default-page-id))))) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 6be2910206..8694a905d8 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -10,6 +10,7 @@ [app.common.geom.point :as gpt] [app.common.logging :as log] [app.common.pages :as cp] + [app.common.pages.changes :as ch] [app.common.pages.changes-builder :as pcb] [app.common.pages.changes-spec :as pcs] [app.common.pages.helpers :as cph] @@ -29,6 +30,7 @@ [app.main.data.workspace.selection :as dws] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.undo :as dwu] + [app.main.refs :as refs] [app.main.repo :as rp] [app.main.store :as st] [app.util.i18n :refer [tr]] @@ -615,7 +617,8 @@ (ptk/reify ::sync-file ptk/UpdateEvent (update [_ state] - (if (not= library-id (:current-file-id state)) + (if (and (not= library-id (:current-file-id state)) + (nil? asset-id)) (d/assoc-in-when state [:workspace-libraries library-id :synced-at] (dt/now)) state)) @@ -743,6 +746,46 @@ :callback do-dismiss}] :sync-dialog)))))) +(defn watch-component-changes + "Watch the state for changes that affect to any main instance. If a change is detected will throw + an update-component-sync, so changes are immediately propagated to the component and copies." + [] + (ptk/reify ::watch-component-changes + ptk/WatchEvent + (watch [_ _ stream] + (let [stopper + (->> stream + (rx/filter #(or (= :app.main.data.workspace/finalize-page (ptk/type %)) + (= ::watch-component-changes (ptk/type %))))) + + workspace-data-str + (->> (rx/concat + (rx/of nil) + (rx/from-atom refs/workspace-data {:emit-current-value? true}))) + + change-str + (->> stream + (rx/filter #(or (dch/commit-changes? %) + (= (ptk/type %) :app.main.data.workspace.notifications/handle-file-change))) + (rx/observe-on :async)) + + check-changes + (fn [[event data]] + (let [changes (-> event deref :changes) + components-changed (reduce #(into %1 (ch/components-changed data %2)) + #{} + changes)] + (js/console.log "components-changed" (clj->js components-changed)) + (when (d/not-empty? components-changed) + (apply st/emit! + (map #(update-component-sync % (:id data)) + components-changed)))))] + + (->> change-str + (rx/with-latest-from workspace-data-str) + (rx/map check-changes) + (rx/take-until stopper)))))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Backend interactions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index 41a29f64e7..14a57309fa 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -254,6 +254,7 @@ (and (if (nil? component-id) (ctk/uses-library-components? shape library-id) (ctk/instance-of? shape library-id component-id)) + (not (:main-instance? shape)) ; not need to sync the main instance (avoid infinite loop) (or (:component-root? shape) (not page?)))) ; avoid nested components inside pages (defmethod uses-assets? :colors diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index fc84f89509..e80065095f 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -188,7 +188,6 @@ (add-group % group-name))))) (st/emit! (dwu/commit-undo-transaction))) - (defn- on-drop-asset [event asset dragging? selected-assets selected-assets-full selected-assets-paths rename] (let [create-typed-assets-group (partial create-assets-group rename)] From a5bf1c03e71961c52c11a906b5debc74a97f45fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 12 Jul 2022 13:52:48 +0200 Subject: [PATCH 11/14] :tada: Make components-v2 an optional feature --- backend/src/app/rpc/mutations/files.clj | 31 ++++--- backend/src/app/rpc/queries/files.clj | 46 +++++----- backend/src/app/rpc/queries/viewer.clj | 10 +-- backend/test/app/services_files_test.clj | 33 ++++--- backend/test/app/services_viewer_test.clj | 12 ++- backend/test/app/test_helpers.clj | 4 +- common/src/app/common/pages/changes.cljc | 13 ++- .../src/app/common/pages/changes_builder.cljc | 2 +- common/src/app/common/pages/common.cljc | 2 +- common/src/app/common/pages/migrations.cljc | 63 -------------- .../src/app/common/types/components_list.cljc | 20 +++-- common/src/app/common/types/container.cljc | 11 ++- common/src/app/common/types/file.cljc | 85 +++++++++++++++++-- common/test/app/common/pages_test.cljc | 14 +-- .../app/common/test_helpers/components.cljc | 3 +- .../test/app/common/test_helpers/files.cljc | 7 +- common/test/app/common/types/file_test.cljc | 1 + .../resources/styles/main/partials/modal.scss | 3 +- frontend/src/app/main.cljs | 1 + frontend/src/app/main/data/dashboard.cljs | 6 +- frontend/src/app/main/data/viewer.cljs | 13 ++- .../src/app/main/data/workspace/changes.cljs | 1 + .../app/main/data/workspace/libraries.cljs | 44 ++++++---- .../data/workspace/libraries_helpers.cljs | 20 ++--- .../app/main/data/workspace/persistence.cljs | 43 +++++++--- .../src/app/main/data/workspace/shapes.cljs | 31 +++---- frontend/src/app/main/{ui => }/features.cljs | 13 ++- frontend/src/app/main/ui/alert.cljs | 78 +++++++++++++++++ frontend/src/app/main/ui/context.cljs | 1 + frontend/src/app/main/ui/dashboard/grid.cljs | 9 +- frontend/src/app/main/ui/workspace.cljs | 20 ++--- .../app/main/ui/workspace/sidebar/layers.cljs | 29 +++---- .../sidebar/options/menus/component.cljs | 14 ++- .../sidebar/options/shapes/frame.cljs | 2 +- frontend/src/app/render.cljs | 63 +++++++------- frontend/src/app/worker/thumbnails.cljs | 11 +-- frontend/src/features.cljs | 5 +- frontend/test/app/test_helpers/pages.cljs | 3 +- frontend/translations/en.po | 12 +++ frontend/translations/es.po | 12 +++ 40 files changed, 495 insertions(+), 296 deletions(-) rename frontend/src/app/main/{ui => }/features.cljs (85%) create mode 100644 frontend/src/app/main/ui/alert.cljs diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index ccdacf86ad..e4768e78e7 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -46,7 +46,7 @@ (s/def ::is-shared ::us/boolean) (s/def ::create-file (s/keys :req-un [::profile-id ::name ::project-id] - :opt-un [::id ::is-shared])) + :opt-un [::id ::is-shared ::components-v2])) (sv/defmethod ::create-file [{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}] @@ -66,11 +66,12 @@ (defn create-file [conn {:keys [id name project-id is-shared data revn - modified-at deleted-at ignore-sync-until] + modified-at deleted-at ignore-sync-until + components-v2] :or {is-shared false revn 0} :as params}] (let [id (or id (:id data) (uuid/next)) - data (or data (ctf/make-file-data id)) + data (or data (ctf/make-file-data id components-v2)) file (db/insert! conn :file (d/without-nils {:id id @@ -317,10 +318,11 @@ (s/def ::session-id ::us/uuid) (s/def ::revn ::us/integer) +(s/def ::components-v2 ::us/boolean) (s/def ::update-file (s/and (s/keys :req-un [::id ::session-id ::profile-id ::revn] - :opt-un [::changes ::changes-with-metadata]) + :opt-un [::changes ::changes-with-metadata ::components-v2]) (fn [o] (or (contains? o :changes) (contains? o :changes-with-metadata))))) @@ -357,7 +359,8 @@ (simpl/del-object backend file)))) (defn- update-file - [{:keys [conn metrics] :as cfg} {:keys [file changes changes-with-metadata session-id profile-id] :as params}] + [{:keys [conn metrics] :as cfg} + {:keys [file changes changes-with-metadata session-id profile-id components-v2] :as params}] (when (> (:revn params) (:revn file)) @@ -382,12 +385,18 @@ (update :data (fn [data] ;; Trace the length of bytes of processed data (mtx/run! metrics {:id :update-file-bytes-processed :inc (alength data)}) - (-> data - (blob/decode) - (assoc :id (:id file)) - (pmg/migrate-data) - (cp/process-changes changes) - (blob/encode)))))] + (cond-> data + :always + (-> (blob/decode) + (assoc :id (:id file)) + (pmg/migrate-data)) + + components-v2 + (ctf/migrate-to-components-v2) + + :always + (-> (cp/process-changes changes) + (blob/encode))))))] ;; Insert change to the xlog (db/insert! conn :file-change {:id (uuid/next) diff --git a/backend/src/app/rpc/queries/files.clj b/backend/src/app/rpc/queries/files.clj index 0fd9431e02..95ae1c0dc4 100644 --- a/backend/src/app/rpc/queries/files.clj +++ b/backend/src/app/rpc/queries/files.clj @@ -13,6 +13,7 @@ [app.common.pages.helpers :as cph] [app.common.pages.migrations :as pmg] [app.common.spec :as us] + [app.common.types.file :as ctf] [app.common.types.shape-tree :as ctt] [app.db :as db] [app.db.sql :as sql] @@ -27,7 +28,6 @@ [cuerdas.core :as str])) (declare decode-row) -(declare decode-row-xf) ;; --- Helpers & Specs @@ -39,6 +39,7 @@ (s/def ::profile-id ::us/uuid) (s/def ::team-id ::us/uuid) (s/def ::search-term ::us/string) +(s/def ::components-v2 ::us/boolean) ;; --- Query: File Permissions @@ -123,8 +124,7 @@ (defn check-comment-permissions! [conn profile-id file-id share-id] (let [can-read (has-read-permissions? conn profile-id file-id) - can-comment (has-comment-permissions? conn profile-id file-id share-id) - ] + can-comment (has-comment-permissions? conn profile-id file-id share-id)] (when-not (or can-read can-comment) (ex/raise :type :not-found :code :object-not-found @@ -227,20 +227,29 @@ (d/index-by :object-id :data)))))) (defn retrieve-file - [{:keys [pool] :as cfg} id] - (->> (db/get-by-id pool :file id) - (decode-row) - (pmg/migrate-file))) + [{:keys [pool] :as cfg} id components-v2] + (let [file (->> (db/get-by-id pool :file id) + (decode-row) + (pmg/migrate-file))] + + (if components-v2 + (update file :data ctf/migrate-to-components-v2) + (if (get-in file [:data :options :components-v2]) + (ex/raise :type :restriction + :code :feature-disabled + :hint "tried to open a components-v2 file with feature disabled") + file)))) (s/def ::file - (s/keys :req-un [::profile-id ::id])) + (s/keys :req-un [::profile-id ::id] + :opt-un [::components-v2])) (sv/defmethod ::file "Retrieve a file by its ID. Only authenticated users." - [{:keys [pool] :as cfg} {:keys [profile-id id] :as params}] + [{:keys [pool] :as cfg} {:keys [profile-id id components-v2] :as params}] (let [perms (get-permissions pool profile-id id)] (check-read-permissions! perms) - (let [file (retrieve-file cfg id) + (let [file (retrieve-file cfg id components-v2) thumbs (retrieve-object-thumbnails cfg id)] (-> file (assoc :thumbnails thumbs) @@ -269,7 +278,7 @@ (s/def ::page (s/and (s/keys :req-un [::profile-id ::file-id] - :opt-un [::page-id ::object-id]) + :opt-un [::page-id ::object-id ::components-v2]) (fn [obj] (if (contains? obj :object-id) (contains? obj :page-id) @@ -285,9 +294,9 @@ mandatory. Mainly used for rendering purposes." - [{:keys [pool] :as cfg} {:keys [profile-id file-id page-id object-id] :as props}] + [{:keys [pool] :as cfg} {:keys [profile-id file-id page-id object-id components-v2] :as props}] (check-read-permissions! pool profile-id file-id) - (let [file (retrieve-file cfg file-id) + (let [file (retrieve-file cfg file-id components-v2) page-id (or page-id (-> file :data :pages first)) page (get-in file [:data :pages-index page-id])] @@ -374,14 +383,15 @@ (update :objects assoc-thumbnails page-id thumbs))))) (s/def ::file-data-for-thumbnail - (s/keys :req-un [::profile-id ::file-id])) + (s/keys :req-un [::profile-id ::file-id] + :opt-in [::components-v2])) (sv/defmethod ::file-data-for-thumbnail "Retrieves the data for generate the thumbnail of the file. Used mainly for render thumbnails on dashboard." - [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as props}] + [{:keys [pool] :as cfg} {:keys [profile-id file-id components-v2] :as props}] (check-read-permissions! pool profile-id file-id) - (let [file (retrieve-file cfg file-id)] + (let [file (retrieve-file cfg file-id components-v2)] {:file-id file-id :revn (:revn file) :page (get-file-thumbnail-data cfg file)})) @@ -523,7 +533,3 @@ (cond-> row changes (assoc :changes (blob/decode changes)) data (assoc :data (blob/decode data))))) - -(def decode-row-xf - (comp (map decode-row) - (map pmg/migrate-file))) diff --git a/backend/src/app/rpc/queries/viewer.clj b/backend/src/app/rpc/queries/viewer.clj index 681b8ef47e..03d9c93423 100644 --- a/backend/src/app/rpc/queries/viewer.clj +++ b/backend/src/app/rpc/queries/viewer.clj @@ -23,8 +23,8 @@ (db/get-by-id pool :project id {:columns [:id :name :team-id]})) (defn- retrieve-bundle - [{:keys [pool] :as cfg} file-id profile-id] - (p/let [file (files/retrieve-file cfg file-id) + [{:keys [pool] :as cfg} file-id profile-id components-v2] + (p/let [file (files/retrieve-file cfg file-id components-v2) project (retrieve-project pool (:project-id file)) libs (files/retrieve-file-libraries cfg false file-id) users (comments/retrieve-file-comments-users pool file-id profile-id) @@ -47,14 +47,14 @@ (s/def ::share-id ::us/uuid) (s/def ::view-only-bundle - (s/keys :req-un [::file-id] :opt-un [::profile-id ::share-id])) + (s/keys :req-un [::file-id] :opt-un [::profile-id ::share-id ::components-v2])) (sv/defmethod ::view-only-bundle {:auth false} - [{:keys [pool] :as cfg} {:keys [profile-id file-id share-id] :as params}] + [{:keys [pool] :as cfg} {:keys [profile-id file-id share-id components-v2] :as params}] (p/let [slink (slnk/retrieve-share-link pool file-id share-id) perms (files/get-permissions pool profile-id file-id share-id) thumbs (files/retrieve-object-thumbnails cfg file-id) - bundle (p/-> (retrieve-bundle cfg file-id profile-id) + bundle (p/-> (retrieve-bundle cfg file-id profile-id components-v2) (assoc :permissions perms) (assoc-in [:file :thumbnails] thumbs))] diff --git a/backend/test/app/services_files_test.clj b/backend/test/app/services_files_test.clj index aa188b2157..c8ab0c25c1 100644 --- a/backend/test/app/services_files_test.clj +++ b/backend/test/app/services_files_test.clj @@ -32,7 +32,8 @@ :project-id proj-id :id file-id :name "foobar" - :is-shared false} + :is-shared false + :components-v2 true} out (th/mutation! data)] ;; (th/print-result! out) @@ -71,7 +72,8 @@ (t/testing "query single file without users" (let [data {::th/type :file :profile-id (:id prof) - :id file-id} + :id file-id + :components-v2 true} out (th/query! data)] ;; (th/print-result! out) @@ -95,7 +97,8 @@ (t/testing "query single file after delete" (let [data {::th/type :file :profile-id (:id prof) - :id file-id} + :id file-id + :components-v2 true} out (th/query! data)] ;; (th/print-result! out) @@ -143,6 +146,7 @@ :session-id (uuid/random) :profile-id profile-id :revn revn + :components-v2 true :changes changes} out (th/mutation! params)] (t/is (nil? (:error out))) @@ -171,6 +175,7 @@ :id shid :parent-id uuid/zero :frame-id uuid/zero + :components-v2 true :obj {:id shid :name "image" :frame-id uuid/zero @@ -246,7 +251,8 @@ :profile-id (:id profile2) :project-id (:default-project-id profile1) :name "foobar" - :is-shared false} + :is-shared false + :components-v2 true} out (th/mutation! data) error (:error out)] @@ -462,6 +468,7 @@ (th/update-file* {:file-id (:id file) :profile-id (:id prof) :revn 0 + :components-v2 true :changes changes}) (t/testing "RPC page query (rendering purposes)" @@ -469,7 +476,8 @@ ;; Query :page RPC method without passing page-id (let [data {::th/type :page :profile-id (:id prof) - :file-id (:id file)} + :file-id (:id file) + :components-v2 true} {:keys [error result] :as out} (th/query! data)] ;; (th/print-result! out) @@ -485,7 +493,8 @@ (let [data {::th/type :page :profile-id (:id prof) :file-id (:id file) - :page-id page-id} + :page-id page-id + :components-v2 true} {:keys [error result] :as out} (th/query! data)] ;; (th/print-result! out) (t/is (map? result)) @@ -501,7 +510,8 @@ :profile-id (:id prof) :file-id (:id file) :page-id page-id - :object-id frame1-id} + :object-id frame1-id + :components-v2 true} {:keys [error result] :as out} (th/query! data)] ;; (th/print-result! out) (t/is (map? result)) @@ -516,7 +526,8 @@ (let [data {::th/type :page :profile-id (:id prof) :file-id (:id file) - :object-id frame1-id} + :object-id frame1-id + :components-v2 true} {:keys [error result] :as out} (th/query! data)] ;; (th/print-result! out) (t/is (= :validation (th/ex-type error))) @@ -537,7 +548,8 @@ ;; Check the result (let [data {::th/type :file-data-for-thumbnail :profile-id (:id prof) - :file-id (:id file)} + :file-id (:id file) + :components-v2 true} {:keys [error result] :as out} (th/query! data)] ;; (th/print-result! out) (t/is (map? result)) @@ -562,7 +574,8 @@ ;; Check the result (let [data {::th/type :file-data-for-thumbnail :profile-id (:id prof) - :file-id (:id file)} + :file-id (:id file) + :components-v2 true} {:keys [error result] :as out} (th/query! data)] ;; (th/print-result! out) (t/is (map? result)) diff --git a/backend/test/app/services_viewer_test.clj b/backend/test/app/services_viewer_test.clj index e8a01c255a..86ad9189f6 100644 --- a/backend/test/app/services_viewer_test.clj +++ b/backend/test/app/services_viewer_test.clj @@ -30,7 +30,8 @@ (let [data {::th/type :view-only-bundle :profile-id (:id prof) :file-id (:id file) - :page-id (get-in file [:data :pages 0])} + :page-id (get-in file [:data :pages 0]) + :components-v2 true} out (th/query! data)] @@ -63,7 +64,8 @@ (let [data {::th/type :view-only-bundle :profile-id (:id prof2) :file-id (:id file) - :page-id (get-in file [:data :pages 0])} + :page-id (get-in file [:data :pages 0]) + :components-v2 true} out (th/query! data)] ;; (th/print-result! out) @@ -78,7 +80,8 @@ :profile-id (:id prof2) :share-id @share-id :file-id (:id file) - :page-id (get-in file [:data :pages 0])} + :page-id (get-in file [:data :pages 0]) + :components-v2 true} out (th/query! data)] ;; (th/print-result! out) @@ -93,7 +96,8 @@ (let [data {::th/type :view-only-bundle :share-id @share-id :file-id (:id file) - :page-id (get-in file [:data :pages 0])} + :page-id (get-in file [:data :pages 0]) + :components-v2 true} out (th/query! data)] ;; (th/print-result! out) diff --git a/backend/test/app/test_helpers.clj b/backend/test/app/test_helpers.clj index 8849ac3ccc..8f436ea02d 100644 --- a/backend/test/app/test_helpers.clj +++ b/backend/test/app/test_helpers.clj @@ -164,7 +164,8 @@ (us/assert uuid? project-id) (#'files/create-file conn (merge {:id (mk-uuid "file" i) - :name (str "file" i)} + :name (str "file" i) + :components-v2 true} params)))) (defn mark-file-deleted* @@ -249,6 +250,7 @@ :metrics metrics} {:file file :revn revn + :components-v2 true :changes changes :session-id session-id :profile-id profile-id})))) diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc index f47ed59a27..6916500a17 100644 --- a/common/src/app/common/pages/changes.cljc +++ b/common/src/app/common/pages/changes.cljc @@ -465,15 +465,20 @@ (defmulti components-changed (fn [_ change] (:type change))) (defmethod components-changed :mod-obj - [file-data {:keys [id page-id component-id operations]}] + [file-data {:keys [id page-id _component-id operations]}] (when page-id (let [page (ctpl/get-page file-data page-id) shape-and-parents (map #(ctn/get-shape page %) (into [id] (cph/get-parent-ids (:objects page) id))) - any-set? (some #(= (:type %) :set) operations)] - (when any-set? + need-sync? (fn [operation] + ; We need to trigger a sync if the shape has changed any + ; attribute that participates in components syncronization. + (and (= (:type operation) :set) + (component-sync-attrs (:attr operation)))) + any-sync? (some need-sync? operations)] + (when any-sync? (into #{} (->> shape-and-parents - (filter #(:main-instance? %)) + (filter #(:main-instance? %)) ; Select shapes that are main component instances (map :id))))))) (defmethod components-changed :default diff --git a/common/src/app/common/pages/changes_builder.cljc b/common/src/app/common/pages/changes_builder.cljc index 3652bed1c4..671e6a636e 100644 --- a/common/src/app/common/pages/changes_builder.cljc +++ b/common/src/app/common/pages/changes_builder.cljc @@ -50,7 +50,7 @@ (defn with-objects [changes objects] - (let [file-data (-> (ctf/make-file-data (uuid/next) uuid/zero) + (let [file-data (-> (ctf/make-file-data (uuid/next) uuid/zero true) (assoc-in [:pages-index uuid/zero :objects] objects))] (vary-meta changes assoc ::file-data file-data ::applied-changes-count 0))) diff --git a/common/src/app/common/pages/common.cljc b/common/src/app/common/pages/common.cljc index cd9e94ee70..1b73deac24 100644 --- a/common/src/app/common/pages/common.cljc +++ b/common/src/app/common/pages/common.cljc @@ -9,7 +9,7 @@ [app.common.colors :as clr] [app.common.uuid :as uuid])) -(def file-version 20) +(def file-version 19) (def default-color clr/gray-20) (def root uuid/zero) diff --git a/common/src/app/common/pages/migrations.cljc b/common/src/app/common/pages/migrations.cljc index d7cb9a8630..c1ee1dbd68 100644 --- a/common/src/app/common/pages/migrations.cljc +++ b/common/src/app/common/pages/migrations.cljc @@ -440,68 +440,5 @@ (update :pages-index d/update-vals update-container) (update :components d/update-vals update-container)))) -(defmethod migrate 20 - [data] - (let [components (ctkl/components-seq data)] - (if (empty? components) - data - (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/make-component-instance page - component - (:id data) - position - true) - - 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 ctk/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/components_list.cljc b/common/src/app/common/types/components_list.cljc index 6fb2b08728..cb4bd70ae2 100644 --- a/common/src/app/common/types/components_list.cljc +++ b/common/src/app/common/types/components_list.cljc @@ -14,13 +14,19 @@ (defn add-component [file-data id name path main-instance-id main-instance-page shapes] - (assoc-in file-data [:components id] - {:id id - :name name - :path path - :main-instance-id main-instance-id - :main-instance-page main-instance-page - :objects (d/index-by :id shapes)})) + (let [components-v2 (get-in file-data [:options :components-v2])] + (cond-> file-data + :always + (assoc-in [:components id] + {:id id + :name name + :path path + :objects (d/index-by :id shapes)}) + + components-v2 + (update-in [:components id] #(assoc % + :main-instance-id main-instance-id + :main-instance-page main-instance-page))))) (defn get-component [file-data component-id] diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index a0f63f0f1e..d58c05e3a4 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -65,7 +65,7 @@ "Clone the shape and all children. Generate new ids and detach from parent and frame. Update the original shapes to have links to the new ones." - [shape objects file-id] + [shape objects file-id components-v2] (assert (nil? (:component-id shape))) (assert (nil? (:component-file shape))) (assert (nil? (:shape-ref shape))) @@ -94,8 +94,10 @@ (nil? (:parent-id new-shape)) (assoc :component-id (:id new-shape) :component-file file-id - :component-root? true - :main-instance? true) + :component-root? true) + + components-v2 + (assoc :main-instance? true) (some? (:parent-id new-shape)) (dissoc :component-root?)))] @@ -103,6 +105,9 @@ (ctst/clone-object shape nil objects update-new-shape update-original-shape))) (defn make-component-instance + "Clone the shapes of the component, generating new names and ids, and linking + each new shape to the corresponding one of the component. Place the new instance + coordinates in the given position." [container component component-file-id position main-instance?] (let [component-shape (get-shape component (:id component)) diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index fb51d9fb71..4a84cf67eb 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -83,14 +83,17 @@ :pages-index {}}) (defn make-file-data - ([file-id] - (make-file-data file-id (uuid/next))) + ([file-id components-v2] + (make-file-data file-id (uuid/next) components-v2)) - ([file-id page-id] + ([file-id page-id components-v2] (let [page (ctp/make-empty-page page-id "Page-1")] - (-> empty-file-data - (assoc :id file-id) - (ctpl/add-page page))))) + (cond-> (-> empty-file-data + (assoc :id file-id) + (ctpl/add-page page)) + + components-v2 + (assoc-in [:options :components-v2] true))))) ;; Helpers @@ -162,9 +165,9 @@ assets-seq))) (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)." + [file-data grid-gap] (let [library-page (d/seek #(= (:name %) "Library page") (ctpl/pages-seq file-data))] (if (some? library-page) (let [compare-pos (fn [pos shape] @@ -180,6 +183,74 @@ (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 migrate-to-components-v2 + "If there is any component in the file library, add a new 'Library Page' and generate + main instances for all components there. Mark the file with the :comonents-v2 option." + [file-data] + (let [components (ctkl/components-seq file-data)] + (if (or (empty? components) + (get-in file-data [:options :components-v2])) + (assoc-in file-data [:options :components-v2] true) + (let [grid-gap 50 + + [file-data page-id start-pos] + (get-or-add-library-page file-data grid-gap) + + add-main-instance + (fn [file-data component position] + (let [page (ctpl/get-page file-data page-id) + + [new-shape new-shapes] + (ctn/make-component-instance page + component + (:id file-data) + position + true) + + 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))] + + (-> file-data + (ctpl/update-page page-id add-shapes) + (ctkl/update-component (:id component) update-component)))) + + add-instance-grid + (fn [file-data components] + (let [position-seq (ctst/generate-shape-grid + (map ctk/get-component-root components) + start-pos + grid-gap)] + (loop [file-data file-data + components-seq (seq components) + position-seq position-seq] + (let [component (first components-seq) + position (first position-seq)] + (if (nil? component) + file-data + (recur (add-main-instance file-data component position) + (rest components-seq) + (rest position-seq)))))))] + + (-> file-data + (add-instance-grid (sort-by :name components)) + (assoc-in [:options :components-v2] true)))))) + (defn- absorb-components [file-data library-data used-components] (let [grid-gap 50 diff --git a/common/test/app/common/pages_test.cljc b/common/test/app/common/pages_test.cljc index 7de27ffae3..89264a93dc 100644 --- a/common/test/app/common/pages_test.cljc +++ b/common/test/app/common/pages_test.cljc @@ -15,7 +15,7 @@ (t/deftest process-change-set-option (let [file-id (uuid/custom 2 2) page-id (uuid/custom 1 1) - data (ctf/make-file-data file-id page-id)] + data (ctf/make-file-data file-id page-id true)] (t/testing "Sets option single" (let [chg {:type :set-option :page-id page-id @@ -81,7 +81,7 @@ (t/deftest process-change-add-obj (let [file-id (uuid/custom 2 2) page-id (uuid/custom 1 1) - data (ctf/make-file-data file-id page-id) + data (ctf/make-file-data file-id page-id true) id-a (uuid/custom 2 1) id-b (uuid/custom 2 2) id-c (uuid/custom 2 3)] @@ -135,7 +135,7 @@ (t/deftest process-change-mod-obj (let [file-id (uuid/custom 2 2) page-id (uuid/custom 1 1) - data (ctf/make-file-data file-id page-id)] + data (ctf/make-file-data file-id page-id true)] (t/testing "simple mod-obj" (let [chg {:type :mod-obj :page-id page-id @@ -162,7 +162,7 @@ (let [file-id (uuid/custom 2 2) page-id (uuid/custom 1 1) id (uuid/custom 2 1) - data (ctf/make-file-data file-id page-id) + data (ctf/make-file-data file-id page-id true) data (-> data (assoc-in [:pages-index page-id :objects uuid/zero :shapes] [id]) (assoc-in [:pages-index page-id :objects id] @@ -206,7 +206,7 @@ file-id (uuid/custom 2 2) page-id (uuid/custom 1 1) - data (ctf/make-file-data file-id page-id) + data (ctf/make-file-data file-id page-id true) data (update-in data [:pages-index page-id :objects] #(-> % @@ -450,7 +450,7 @@ :obj {:type :rect :name "Shape 3"}} ] - data (ctf/make-file-data file-id page-id) + data (ctf/make-file-data file-id page-id true) data (cp/process-changes data changes)] (t/testing "preserve order on multiple shape mov 1" @@ -557,7 +557,7 @@ :parent-id group-1-id :shapes [shape-1-id shape-2-id]}] - data (ctf/make-file-data file-id page-id) + data (ctf/make-file-data file-id page-id true) data (cp/process-changes data changes)] (t/testing "case 1" diff --git a/common/test/app/common/test_helpers/components.cljc b/common/test/app/common/test_helpers/components.cljc index 5cadf9a7d5..843b74a4e7 100644 --- a/common/test/app/common/test_helpers/components.cljc +++ b/common/test/app/common/test_helpers/components.cljc @@ -1,7 +1,6 @@ (ns app.common.test-helpers.components (:require - [cljs.test :as t :include-macros true] - [cljs.pprint :refer [pprint]] + [clojure.test :as t] [app.common.pages.helpers :as cph] [app.common.types.component :as ctk] [app.common.types.container :as ctn])) diff --git a/common/test/app/common/test_helpers/files.cljc b/common/test/app/common/test_helpers/files.cljc index d6b684d25d..85d4b980da 100644 --- a/common/test/app/common/test_helpers/files.cljc +++ b/common/test/app/common/test_helpers/files.cljc @@ -31,7 +31,7 @@ ([file-id page-id props] (merge {:id file-id :name (get props :name "File1") - :data (ctf/make-file-data file-id page-id)} + :data (ctf/make-file-data file-id page-id true)} props))) (defn sample-shape @@ -68,9 +68,10 @@ (let [page (ctpl/get-page file-data page-id) [component-shape component-shapes updated-shapes] - (ctn/make-component-shape (ctn/get-shape page shape-id) + (ctn/make-component-shape (ctn/get-shape page shape-id true) (:objects page) - (:id file))] + (:id file) + true)] (swap! idmap assoc label (:id component-shape)) (-> file-data diff --git a/common/test/app/common/types/file_test.cljc b/common/test/app/common/types/file_test.cljc index 5c5f8527ff..908c2b3e1c 100644 --- a/common/test/app/common/types/file_test.cljc +++ b/common/test/app/common/types/file_test.cljc @@ -110,6 +110,7 @@ (t/is (= (:name p-group) "Group1")) (t/is (ctk/instance-of? p-group file-id (:id component1))) + (t/is (not (:main-instance? p-group))) (t/is (not (ctk/is-main-instance? (:id p-group) file-page-id component1))) (t/is (ctk/is-main-of? c-group1 p-group)) diff --git a/frontend/resources/styles/main/partials/modal.scss b/frontend/resources/styles/main/partials/modal.scss index 4f01615632..98c35746aa 100644 --- a/frontend/resources/styles/main/partials/modal.scss +++ b/frontend/resources/styles/main/partials/modal.scss @@ -179,7 +179,8 @@ } } -.confirm-dialog { +.confirm-dialog, +.alert-dialog { background-color: $color-white; p { diff --git a/frontend/src/app/main.cljs b/frontend/src/app/main.cljs index a7e38e1850..a93eaf2a0d 100644 --- a/frontend/src/app/main.cljs +++ b/frontend/src/app/main.cljs @@ -16,6 +16,7 @@ [app.main.sentry :as sentry] [app.main.store :as st] [app.main.ui :as ui] + [app.main.ui.alert] [app.main.ui.confirm] [app.main.ui.modal :refer [modal]] [app.main.ui.routes :as rt] diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index 93efdb0baa..ba61e65248 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -13,6 +13,7 @@ [app.main.data.fonts :as df] [app.main.data.media :as di] [app.main.data.users :as du] + [app.main.features :as features] [app.main.repo :as rp] [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] @@ -718,12 +719,13 @@ (-deref [_] {:project-id project-id}) ptk/WatchEvent - (watch [it _ _] + (watch [it state _] (let [{:keys [on-success on-error] :or {on-success identity on-error rx/throw}} (meta params) name (name (gensym (str (tr "dashboard.new-file-prefix") " "))) - params (assoc params :name name)] + components-v2 (features/active-feature? state :components-v2) + params (assoc params :name name :components-v2 components-v2)] (->> (rp/mutation! :create-file params) (rx/tap on-success) diff --git a/frontend/src/app/main/data/viewer.cljs b/frontend/src/app/main/data/viewer.cljs index 896c493904..bd3a774570 100644 --- a/frontend/src/app/main/data/viewer.cljs +++ b/frontend/src/app/main/data/viewer.cljs @@ -14,6 +14,7 @@ [app.common.types.shape.interactions :as ctsi] [app.main.data.comments :as dcm] [app.main.data.fonts :as df] + [app.main.features :as features] [app.main.repo :as rp] [app.util.globals :as ug] [app.util.router :as rt] @@ -100,9 +101,15 @@ (us/assert ::fetch-bundle-params params) (ptk/reify ::fetch-file ptk/WatchEvent - (watch [_ _ _] - (let [params' (cond-> {:file-id file-id} - (uuid? share-id) (assoc :share-id share-id))] + (watch [_ state _] + (let [components-v2 (features/active-feature? state :components-v2) + params' (cond-> {:file-id file-id} + (uuid? share-id) + (assoc :share-id share-id) + + :always + (assoc :components-v2 components-v2))] + (->> (rp/query :view-only-bundle params') (rx/mapcat (fn [{:keys [fonts] :as bundle}] diff --git a/frontend/src/app/main/data/workspace/changes.cljs b/frontend/src/app/main/data/workspace/changes.cljs index a1f4da573e..488c5ec2b6 100644 --- a/frontend/src/app/main/data/workspace/changes.cljs +++ b/frontend/src/app/main/data/workspace/changes.cljs @@ -192,6 +192,7 @@ process-page-changes (fn [[page-id _changes]] (update-indices page-id redo-changes))] + (rx/concat (rx/from (map process-page-changes changes-by-pages)) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 8694a905d8..3b05d5f911 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -30,6 +30,7 @@ [app.main.data.workspace.selection :as dws] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.undo :as dwu] + [app.main.features :as features] [app.main.refs :as refs] [app.main.repo :as rp] [app.main.store :as st] @@ -283,7 +284,7 @@ (defn- add-component2 "This is the second step of the component creation." - [selected] + [selected components-v2] (ptk/reify ::add-component2 IDeref (-deref [_] {:num-shapes (count selected)}) @@ -296,7 +297,7 @@ shapes (dwg/shapes-for-grouping objects selected)] (when-not (empty? shapes) (let [[group _ changes] - (dwlh/generate-add-component it shapes objects page-id file-id)] + (dwlh/generate-add-component it shapes objects page-id file-id components-v2)] (when-not (empty? (:redo-changes changes)) (rx/of (dch/commit-changes changes) (dws/select-shapes (d/ordered-set (:id group))))))))))) @@ -310,10 +311,11 @@ (ptk/reify ::add-component ptk/WatchEvent (watch [_ state _] - (let [objects (wsh/lookup-page-objects state) - selected (->> (wsh/lookup-selected state) - (cph/clean-loops objects))] - (rx/of (add-component2 selected)))))) + (let [objects (wsh/lookup-page-objects state) + selected (->> (wsh/lookup-selected state) + (cph/clean-loops objects)) + components-v2 (features/active-feature? state :components-v2)] + (rx/of (add-component2 selected components-v2)))))) (defn rename-component "Rename the component with the given id, in the current file library." @@ -357,8 +359,12 @@ unames (into #{} (map :name) all-components) new-name (ctst/generate-unique-name unames (:name component)) - main-instance-page (wsh/lookup-page state (:main-instance-page component)) - main-instance-shape (ctn/get-shape main-instance-page (:main-instance-id component)) + components-v2 (features/active-feature? state :components-v2) + + main-instance-page (when components-v2 + (wsh/lookup-page state (:main-instance-page component))) + main-instance-shape (when components-v2 + (ctn/get-shape main-instance-page (:main-instance-id component))) [new-component-shape new-component-shapes new-main-instance-shape new-main-instance-shapes] @@ -606,7 +612,11 @@ "Synchronize the given file from the given library. Walk through all shapes in all pages in the file that use some color, typography or component of the library, and copy the new values to the shapes. Do - it also for shapes inside components of the local file library." + it also for shapes inside components of the local file library. + + If it's known that only one asset has changed, you can give its + type and id, and only shapes that use it will be synced, thus avoiding + a lot of unneeded checks." ([file-id library-id] (sync-file file-id library-id nil nil)) ([file-id library-id asset-type asset-id] @@ -752,8 +762,10 @@ [] (ptk/reify ::watch-component-changes ptk/WatchEvent - (watch [_ _ stream] - (let [stopper + (watch [_ state stream] + (let [components-v2 (features/active-feature? state :components-v2) + + stopper (->> stream (rx/filter #(or (= :app.main.data.workspace/finalize-page (ptk/type %)) (= ::watch-component-changes (ptk/type %))))) @@ -775,16 +787,16 @@ components-changed (reduce #(into %1 (ch/components-changed data %2)) #{} changes)] - (js/console.log "components-changed" (clj->js components-changed)) (when (d/not-empty? components-changed) (apply st/emit! (map #(update-component-sync % (:id data)) components-changed)))))] - (->> change-str - (rx/with-latest-from workspace-data-str) - (rx/map check-changes) - (rx/take-until stopper)))))) + (when components-v2 + (->> change-str + (rx/with-latest-from workspace-data-str) + (rx/map check-changes) + (rx/take-until stopper))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Backend interactions diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index 14a57309fa..195caf140d 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -62,7 +62,7 @@ "If there is exactly one id, and it's a group, use it as root. Otherwise, create a group that contains all ids. Then, make a component with it, and link all shapes to their corresponding one in the component." - [it shapes objects page-id file-id] + [it shapes objects page-id file-id components-v2] (if (and (= (count shapes) 1) (:component-id (first shapes))) [(first shapes) (pcb/empty-changes it)] @@ -77,7 +77,7 @@ (dwg/prepare-create-group it objects page-id shapes name true)) [new-shape new-shapes updated-shapes] - (ctn/make-component-shape group objects file-id) + (ctn/make-component-shape group objects file-id components-v2) changes (-> changes (pcb/add-component (:id new-shape) @@ -106,13 +106,14 @@ [new-instance-shape new-instance-shapes] - (ctn/make-component-instance main-instance-page - {:id (:id new-component-shape) - :name (:name new-component-shape) - :objects (d/index-by :id new-component-shapes)} - (:component-file main-instance-shape) - position - false)] + (when (and (some? main-instance-page) (some? main-instance-shape)) + (ctn/make-component-instance main-instance-page + {:id (:id new-component-shape) + :name (:name new-component-shape) + :objects (d/index-by :id new-component-shapes)} + (:component-file main-instance-shape) + position + false))] [new-component-shape new-component-shapes new-instance-shape new-instance-shapes])) @@ -254,7 +255,6 @@ (and (if (nil? component-id) (ctk/uses-library-components? shape library-id) (ctk/instance-of? shape library-id component-id)) - (not (:main-instance? shape)) ; not need to sync the main instance (avoid infinite loop) (or (:component-root? shape) (not page?)))) ; avoid nested components inside pages (defmethod uses-assets? :colors diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index 445f5001c0..1d1e1cc369 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -16,12 +16,15 @@ [app.config :as cf] [app.main.data.dashboard :as dd] [app.main.data.fonts :as df] + [app.main.data.modal :as modal] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.thumbnails :as dwt] + [app.main.features :as features] [app.main.repo :as rp] [app.main.store :as st] [app.util.http :as http] + [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] [app.util.time :as dt] [beicon.core :as rx] @@ -124,8 +127,7 @@ (rx/map persist-synchronous-changes) (rx/take-until (rx/delay 100 stoper)) (rx/finalize (fn [] - (log/debug :hint "finalize persistence: synchronous save loop")))) - ))))) + (log/debug :hint "finalize persistence: synchronous save loop"))))))))) (defn persist-changes [file-id changes] @@ -134,12 +136,14 @@ (ptk/reify ::persist-changes ptk/WatchEvent (watch [_ state _] - (let [sid (:session-id state) - file (get state :workspace-file) - params {:id (:id file) - :revn (:revn file) - :session-id sid - :changes-with-metadata (into [] changes)}] + (let [components-v2 (features/active-feature? state :components-v2) + sid (:session-id state) + file (get state :workspace-file) + params {:id (:id file) + :revn (:revn file) + :session-id sid + :changes-with-metadata (into [] changes) + :components-v2 components-v2}] (when (= file-id (:id params)) (->> (rp/mutation :update-file params) @@ -175,13 +179,15 @@ (ptk/reify ::persist-synchronous-changes ptk/WatchEvent (watch [_ state _] - (let [sid (:session-id state) + (let [components-v2 (features/active-feature? state :components-v2) + sid (:session-id state) file (get-in state [:workspace-libraries file-id]) params {:id (:id file) :revn (:revn file) :session-id sid - :changes changes}] + :changes changes + :components-v2 components-v2}] (when (:id params) (->> (rp/mutation :update-file params) @@ -261,8 +267,9 @@ (ptk/reify ::fetch-bundle ptk/WatchEvent (watch [_ state _] - (let [share-id (-> state :viewer-local :share-id)] - (->> (rx/zip (rp/query :file-raw {:id file-id}) + (let [share-id (-> state :viewer-local :share-id) + components-v2 (features/active-feature? state :components-v2)] + (->> (rx/zip (rp/query :file-raw {:id file-id :components-v2 components-v2}) (rp/query :team-users {:file-id file-id}) (rp/query :project {:id project-id}) (rp/query :file-libraries {:file-id file-id}) @@ -276,8 +283,16 @@ :file-comments-users file-comments-users})) (rx/mapcat (fn [{:keys [project] :as bundle}] (rx/of (ptk/data-event ::bundle-fetched bundle) - (df/load-team-fonts (:team-id project)))))))))) - + (df/load-team-fonts (:team-id project))))) + (rx/catch (fn [err] + (if (and (= (:type err) :restriction) + (= (:code err) :feature-disabled)) + (let [team-id (:current-team-id state)] + (rx/of (modal/show + {:type :alert + :message (tr "errors.components-v2") + :on-accept #(st/emit! (rt/nav :dashboard-projects {:team-id team-id}))}))) + (rx/throw err))))))))) ;; --- Helpers diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index e33522b54c..7269972696 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -13,7 +13,6 @@ [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] @@ -24,6 +23,7 @@ [app.main.data.workspace.selection :as dws] [app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.state-helpers :as wsh] + [app.main.features :as features] [app.main.streams :as ms] [beicon.core :as rx] [cljs.spec.alpha :as s] @@ -148,7 +148,7 @@ ids (cph/clean-loops objects ids) lookup (d/getf objects) - local-library {file-id {:data file}} + components-v2 (features/active-feature? state :components-v2) groups-to-unmask (reduce (fn [group-ids id] @@ -221,23 +221,16 @@ (into (d/ordered-set) (find-all-empty-parents #{})) components-to-delete - (reduce (fn [components id] - (let [shape (get objects id) - - component - (when (and (:component-id shape) (:component-file shape)) - ;; Only local components may have main instances - (cph/get-component local-library (:component-file shape) (:component-id shape))) - - main-instance? - (when component - (ctk/is-main-instance? (:id shape) (:id page) component))] - - (if main-instance? - (conj components (:component-id shape)) - components))) - [] - (into ids all-children)) + (if components-v2 + (reduce (fn [components id] + (let [shape (get objects id)] + (if (and (= (:component-file shape) file-id) ;; Main instances should exist only in local file + (:main-instance? shape)) ;; but check anyway + (conj components (:component-id shape)) + components))) + [] + (into ids all-children)) + []) changes (-> (pcb/empty-changes it page-id) (pcb/with-page page) diff --git a/frontend/src/app/main/ui/features.cljs b/frontend/src/app/main/features.cljs similarity index 85% rename from frontend/src/app/main/ui/features.cljs rename to frontend/src/app/main/features.cljs index 30f0ebb596..236314cb80 100644 --- a/frontend/src/app/main/ui/features.cljs +++ b/frontend/src/app/main/features.cljs @@ -4,7 +4,7 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.main.ui.features +(ns app.main.features (:require [app.common.data :as d] [app.common.logging :as log] @@ -15,9 +15,9 @@ (log/set-level! :debug) -(def features-list #{:auto-layout}) +(def features-list #{:auto-layout :components-v2}) -(defn toggle-feature +(defn- toggle-feature [feature] (ptk/reify ::toggle-feature ptk/UpdateEvent @@ -41,6 +41,13 @@ (assert (contains? features-list feature) "Not supported feature") (st/emit! (toggle-feature feature))) +(defn active-feature? + ([feature] + (active-feature? @st/state feature)) + ([state feature] + (assert (contains? features-list feature) "Not supported feature") + (contains? (get state :features) feature))) + (def features (l/derived :features st/state)) diff --git a/frontend/src/app/main/ui/alert.cljs b/frontend/src/app/main/ui/alert.cljs new file mode 100644 index 0000000000..1dcf786403 --- /dev/null +++ b/frontend/src/app/main/ui/alert.cljs @@ -0,0 +1,78 @@ +;; 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.main.ui.alert + (:require + [app.main.data.modal :as modal] + [app.main.store :as st] + [app.main.ui.icons :as i] + [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [tr t]] + [app.util.keyboard :as k] + [goog.events :as events] + [rumext.alpha :as mf]) + (:import goog.events.EventType)) + +(mf/defc alert-dialog + {::mf/register modal/components + ::mf/register-as :alert} + [{:keys [message + scd-message + title + on-accept + hint + accept-label + accept-style] :as props}] + (let [locale (mf/deref i18n/locale) + + on-accept (or on-accept identity) + message (or message (t locale "ds.alert-title")) + accept-label (or accept-label (tr "ds.alert-ok")) + accept-style (or accept-style :danger) + title (or title (t locale "ds.alert-title")) + + accept-fn + (mf/use-callback + (fn [event] + (dom/prevent-default event) + (st/emit! (modal/hide)) + (on-accept props)))] + + (mf/with-effect + (letfn [(on-keydown [event] + (when (k/enter? event) + (dom/prevent-default event) + (dom/stop-propagation event) + (st/emit! (modal/hide)) + (on-accept props)))] + (->> (events/listen js/document EventType.KEYDOWN on-keydown) + (partial events/unlistenByKey)))) + + [:div.modal-overlay + [:div.modal-container.alert-dialog + [:div.modal-header + [:div.modal-header-title + [:h2 title]] + [:div.modal-close-button + {:on-click accept-fn} i/close]] + + [:div.modal-content + (when (and (string? message) (not= message "")) + [:h3 message]) + (when (and (string? scd-message) (not= scd-message "")) + [:h3 scd-message]) + (when (string? hint) + [:p hint])] + + [:div.modal-footer + [:div.action-buttons + [:input.accept-button + {:class (dom/classnames + :danger (= accept-style :danger) + :primary (= accept-style :primary)) + :type "button" + :value accept-label + :on-click accept-fn}]]]]])) diff --git a/frontend/src/app/main/ui/context.cljs b/frontend/src/app/main/ui/context.cljs index 7f2f191ac7..8ec9e69fa5 100644 --- a/frontend/src/app/main/ui/context.cljs +++ b/frontend/src/app/main/ui/context.cljs @@ -25,3 +25,4 @@ (def scroll-ctx (mf/create-context nil)) (def active-frames-ctx (mf/create-context nil)) (def render-thumbnails (mf/create-context nil)) +(def components-v2 (mf/create-context nil)) diff --git a/frontend/src/app/main/ui/dashboard/grid.cljs b/frontend/src/app/main/ui/dashboard/grid.cljs index f4057a60ae..d852322f9c 100644 --- a/frontend/src/app/main/ui/dashboard/grid.cljs +++ b/frontend/src/app/main/ui/dashboard/grid.cljs @@ -10,6 +10,7 @@ [app.common.math :as mth] [app.main.data.dashboard :as dd] [app.main.data.messages :as dm] + [app.main.features :as features] [app.main.fonts :as fonts] [app.main.refs :as refs] [app.main.store :as st] @@ -36,9 +37,11 @@ (defn ask-for-thumbnail "Creates some hooks to handle the files thumbnails cache" [file] - (wrk/ask! {:cmd :thumbnails/generate - :revn (:revn file) - :file-id (:id file)})) + (let [components-v2 (features/active-feature? :components-v2)] + (wrk/ask! {:cmd :thumbnails/generate + :revn (:revn file) + :file-id (:id file) + :components-v2 components-v2}))) (mf/defc grid-item-thumbnail {::mf/wrap [mf/memo]} diff --git a/frontend/src/app/main/ui/workspace.cljs b/frontend/src/app/main/ui/workspace.cljs index 6068c1799a..18cc781186 100644 --- a/frontend/src/app/main/ui/workspace.cljs +++ b/frontend/src/app/main/ui/workspace.cljs @@ -11,6 +11,7 @@ [app.main.data.messages :as msg] [app.main.data.workspace :as dw] [app.main.data.workspace.persistence :as dwp] + [app.main.features :as features] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.context :as ctx] @@ -114,13 +115,12 @@ (mf/defc workspace {::mf/wrap [mf/memo]} [{:keys [project-id file-id page-id layout-name] :as props}] - (let [file (mf/deref refs/workspace-file) - project (mf/deref refs/workspace-project) - layout (mf/deref refs/workspace-layout) - wglobal (mf/deref refs/workspace-global) - wglobal (mf/deref refs/workspace-global) - libraries (mf/deref refs/workspace-libraries) - local-library (mf/deref refs/workspace-local-library) + (let [file (mf/deref refs/workspace-file) + project (mf/deref refs/workspace-project) + layout (mf/deref refs/workspace-layout) + wglobal (mf/deref refs/workspace-global) + + components-v2 (features/use-feature :components-v2) background-color (:background-color wglobal)] @@ -148,9 +148,7 @@ [:& (mf/provider ctx/current-team-id) {:value (:team-id project)} [:& (mf/provider ctx/current-project-id) {:value (:id project)} [:& (mf/provider ctx/current-page-id) {:value page-id} - [:& (mf/provider ctx/libraries) {:value (assoc libraries - (:id local-library) - {:data local-library})} + [:& (mf/provider ctx/components-v2) {:value components-v2} [:section#workspace {:style {:background-color background-color}} (when (not (:hide-ui layout)) [:& header {:file file @@ -169,5 +167,3 @@ :layout layout}] [:& workspace-loader])]]]]]])) - - diff --git a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs index 0cdd5193ce..82d001351e 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs @@ -9,7 +9,6 @@ [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] @@ -87,7 +86,7 @@ (when (seq (:touched shape)) " *")]))) (mf/defc layer-item - [{:keys [index page item selected objects] :as props}] + [{:keys [index item selected objects] :as props}] (let [id (:id item) blocked? (:blocked item) hidden? (:hidden item) @@ -103,11 +102,10 @@ container? (or (cph/frame-shape? item) (cph/group-shape? item)) - libraries (mf/use-ctx ctx/libraries) - component (when (and (:component-id item) (:component-file item)) - (cph/get-component libraries (:component-file item) (:component-id item))) - main-instance? (when component - (ctk/is-main-instance? (:id item) (:id page) component)) + components-v2 (mf/use-ctx ctx/components-v2) + main-instance? (if components-v2 + (:main-instance? item) + true) toggle-collapse (mf/use-fn @@ -277,8 +275,7 @@ (for [[index id] (reverse (d/enumerate (:shapes item)))] (when-let [item (get objects id)] [:& layer-item - {:page page - :item item + {:item item :selected selected :index index :objects objects @@ -299,9 +296,8 @@ {::mf/wrap [#(mf/memo % =) #(mf/throttle % 200)]} [{:keys [objects] :as props}] - (let [page (mf/deref refs/workspace-page) - selected (mf/deref refs/selected-shapes) - selected (hooks/use-equal-memo selected) + (let [selected (mf/deref refs/selected-shapes) + selected (hooks/use-equal-memo selected) root (get objects uuid/zero)] [:ul.element-list [:& hooks/sortable-container {} @@ -315,8 +311,7 @@ :objects objects :key id}] [:& layer-item - {:page page - :item obj + {:item obj :selected selected :index index :objects objects @@ -326,16 +321,14 @@ {::mf/wrap [#(mf/memo % =) #(mf/throttle % 200)]} [{:keys [objects] :as props}] - (let [page (mf/deref refs/workspace-page) - selected (mf/deref refs/selected-shapes) + (let [selected (mf/deref refs/selected-shapes) selected (hooks/use-equal-memo selected) root (get objects uuid/zero)] [:ul.element-list (for [[index id] (d/enumerate (:shapes root))] (when-let [obj (get objects id)] [:& layer-item - {:page page - :item obj + {:item obj :selected selected :index index :objects objects 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 4f4ed16074..34e044ae92 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,8 +6,6 @@ (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] @@ -19,13 +17,12 @@ [app.util.i18n :as i18n :refer [tr]] [rumext.alpha :as mf])) -(def component-attrs [:component-id :component-file :shape-ref]) +(def component-attrs [:component-id :component-file :shape-ref :main-instance?]) (mf/defc component-menu [{:keys [ids values shape-name] :as props}] (let [current-file-id (mf/use-ctx ctx/current-file-id) - current-page-id (mf/use-ctx ctx/current-page-id) - libraries (mf/use-ctx ctx/libraries) + components-v2 (mf/use-ctx ctx/components-v2) id (first ids) local (mf/use-state {:menu-open false}) @@ -33,10 +30,9 @@ component-id (:component-id values) library-id (:component-file values) show? (some? component-id) - - component (when (and component-id library-id) - (cph/get-component libraries library-id component-id)) - main-instance? (ctk/is-main-instance? id current-page-id component) + main-instance? (if components-v2 + (:main-instance? values) + true) on-menu-click (mf/use-callback diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs index 6a6957c255..cbd5d7115b 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs @@ -6,8 +6,8 @@ (ns app.main.ui.workspace.sidebar.options.shapes.frame (:require + [app.main.features :as features] [app.main.refs :as refs] - [app.main.ui.features :as features] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs-shape fill-menu]] diff --git a/frontend/src/app/render.cljs b/frontend/src/app/render.cljs index 872e0f2476..8e4f98182d 100644 --- a/frontend/src/app/render.cljs +++ b/frontend/src/app/render.cljs @@ -13,6 +13,7 @@ [app.common.uri :as u] [app.config :as cf] [app.main.data.fonts :as df] + [app.main.features :as features] [app.main.render :as render] [app.main.repo :as repo] [app.main.store :as st] @@ -99,22 +100,24 @@ (mf/defc object-svg [{:keys [page-id file-id object-id render-embed?]}] - (let [fetch-state (mf/use-fn - (mf/deps file-id page-id object-id) - (fn [] - (->> (rx/zip - (repo/query! :font-variants {:file-id file-id}) - (repo/query! :page {:file-id file-id - :page-id page-id - :object-id object-id})) - (rx/tap (fn [[fonts]] - (when (seq fonts) - (st/emit! (df/fonts-fetched fonts))))) - (rx/map (comp :objects second)) - (rx/map (fn [objects] - (let [objects (render/adapt-objects-for-shape objects object-id)] - {:objects objects - :object (get objects object-id)})))))) + (let [components-v2 (features/use-feature :components-v2) + fetch-state (mf/use-fn + (mf/deps file-id page-id object-id) + (fn [] + (->> (rx/zip + (repo/query! :font-variants {:file-id file-id}) + (repo/query! :page {:file-id file-id + :page-id page-id + :object-id object-id + :components-v2 components-v2})) + (rx/tap (fn [[fonts]] + (when (seq fonts) + (st/emit! (df/fonts-fetched fonts))))) + (rx/map (comp :objects second)) + (rx/map (fn [objects] + (let [objects (render/adapt-objects-for-shape objects object-id)] + {:objects objects + :object (get objects object-id)})))))) {:keys [objects object]} (use-resource fetch-state)] @@ -124,8 +127,8 @@ (when object (dom/set-page-style! {:size (str/concat - (mth/ceil (:width object)) "px " - (mth/ceil (:height object)) "px")}))) + (mth/ceil (:width object)) "px " + (mth/ceil (:height object)) "px")}))) (when objects [:& render/object-svg @@ -135,17 +138,19 @@ (mf/defc objects-svg [{:keys [page-id file-id object-ids render-embed?]}] - (let [fetch-state (mf/use-fn - (mf/deps file-id page-id) - (fn [] - (->> (rx/zip - (repo/query! :font-variants {:file-id file-id}) - (repo/query! :page {:file-id file-id - :page-id page-id})) - (rx/tap (fn [[fonts]] - (when (seq fonts) - (st/emit! (df/fonts-fetched fonts))))) - (rx/map (comp :objects second))))) + (let [components-v2 (features/use-feature :components-v2) + fetch-state (mf/use-fn + (mf/deps file-id page-id) + (fn [] + (->> (rx/zip + (repo/query! :font-variants {:file-id file-id}) + (repo/query! :page {:file-id file-id + :page-id page-id + :components-v2 components-v2})) + (rx/tap (fn [[fonts]] + (when (seq fonts) + (st/emit! (df/fonts-fetched fonts))))) + (rx/map (comp :objects second))))) objects (use-resource fetch-state)] diff --git a/frontend/src/app/worker/thumbnails.cljs b/frontend/src/app/worker/thumbnails.cljs index cc28169be8..1dc58811c6 100644 --- a/frontend/src/app/worker/thumbnails.cljs +++ b/frontend/src/app/worker/thumbnails.cljs @@ -48,11 +48,12 @@ (= :request-body-too-large code))) (defn- request-data-for-thumbnail - [file-id revn] + [file-id revn components-v2] (let [path "api/rpc/query/file-data-for-thumbnail" params {:file-id file-id :revn revn - :strip-frames-with-thumbnails true} + :strip-frames-with-thumbnails true + :components-v2 components-v2} request {:method :get :uri (u/join (cfg/get-public-uri) path) :credentials "include" @@ -107,18 +108,18 @@ (rx/map (constantly params))))) (defmethod impl/handler :thumbnails/generate - [{:keys [file-id revn] :as message}] + [{:keys [file-id revn components-v2] :as message}] (letfn [(on-result [{:keys [data props]}] {:data data :fonts (:fonts props)}) (on-cache-miss [_] - (->> (request-data-for-thumbnail file-id revn) + (->> (request-data-for-thumbnail file-id revn components-v2) (rx/map render-thumbnail) (rx/mapcat persist-thumbnail)))] (if (debug? :disable-thumbnail-cache) - (->> (request-data-for-thumbnail file-id revn) + (->> (request-data-for-thumbnail file-id revn components-v2) (rx/map render-thumbnail)) (->> (request-thumbnail file-id revn) (rx/catch not-found? on-cache-miss) diff --git a/frontend/src/features.cljs b/frontend/src/features.cljs index 353504a1e3..f0e9af722c 100644 --- a/frontend/src/features.cljs +++ b/frontend/src/features.cljs @@ -7,8 +7,11 @@ ;; This namespace is only to export the functions for toggle features (ns features (:require - [app.main.ui.features :as features])) + [app.main.features :as features])) (defn ^:export autolayout [] (features/toggle-feature! :auto-layout)) +(defn ^:export components-v2 [] + (features/toggle-feature! :components-v2)) + diff --git a/frontend/test/app/test_helpers/pages.cljs b/frontend/test/app/test_helpers/pages.cljs index a061ecc43e..e66974d019 100644 --- a/frontend/test/app/test_helpers/pages.cljs +++ b/frontend/test/app/test_helpers/pages.cljs @@ -105,7 +105,8 @@ shapes (:objects page) (:id page) - current-file-id)] + current-file-id + true)] (swap! idmap assoc instance-label (:id group) component-label (:id component-root)) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 172fe1f446..2bd88884b6 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -639,6 +639,14 @@ msgstr "Your name" msgid "dashboard.your-penpot" msgstr "Your Penpot" +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "Ok" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "Attention" + #: src/app/main/ui/confirm.cljs msgid "ds.component-subtitle" msgstr "Components to update:" @@ -718,6 +726,10 @@ msgstr "LDAP authentication is disabled." msgid "errors.media-format-unsupported" msgstr "The image format is not supported (must be svg, jpg or png)." +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.components-v2" +msgstr "This file has already used with Components V2 enabled." + #: src/app/main/data/workspace/persistence.cljs msgid "errors.media-too-large" msgstr "The image is too large to be inserted (must be under 5mb)." diff --git a/frontend/translations/es.po b/frontend/translations/es.po index e000b6145a..a6c55af0dd 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -656,6 +656,14 @@ msgstr "Tu nombre" msgid "dashboard.your-penpot" msgstr "Tu Penpot" +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "Ok" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "Atención" + #: src/app/main/ui/confirm.cljs msgid "ds.component-subtitle" msgstr "Componentes a actualizar:" @@ -736,6 +744,10 @@ msgstr "La autheticacion via LDAP esta deshabilitada." msgid "errors.media-format-unsupported" msgstr "No se reconoce el formato de imagen (debe ser svg, jpg o png)." +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.components-v2" +msgstr "Este fichero ya se ha usado con los Componentes V2 activados." + #: src/app/main/data/workspace/persistence.cljs msgid "errors.media-too-large" msgstr "La imagen es demasiado grande (debe tener menos de 5mb)." From c108974ad20a51b022b7f949ee2af4de94e3f9e0 Mon Sep 17 00:00:00 2001 From: Eva Date: Fri, 1 Jul 2022 07:50:44 +0200 Subject: [PATCH 12/14] :sparkles: Add info in modal --- backend/src/app/rpc/queries/files.clj | 20 ++- common/src/app/common/pages/migrations.cljc | 7 -- common/src/app/common/types/colors_list.cljc | 4 +- common/src/app/common/types/file.cljc | 18 +-- .../app/common/types/typographies_list.cljc | 4 +- .../resources/styles/main/partials/modal.scss | 6 + frontend/src/app/main.cljs | 1 + frontend/src/app/main/data/dashboard.cljs | 26 ++++ .../main/data/workspace/drawing/common.cljs | 1 - .../app/main/data/workspace/libraries.cljs | 2 +- .../src/app/main/data/workspace/shapes.cljs | 3 +- .../src/app/main/ui/dashboard/file_menu.cljs | 44 ++++--- frontend/src/app/main/ui/dashboard/files.cljs | 3 +- frontend/src/app/main/ui/dashboard/grid.cljs | 22 ++-- .../src/app/main/ui/dashboard/libraries.cljs | 3 +- .../src/app/main/ui/dashboard/projects.cljs | 3 +- frontend/src/app/main/ui/delete_shared.cljs | 117 ++++++++++++++++++ frontend/translations/en.po | 51 ++++++++ frontend/translations/es.po | 50 ++++++++ 19 files changed, 327 insertions(+), 58 deletions(-) create mode 100644 frontend/src/app/main/ui/delete_shared.cljs diff --git a/backend/src/app/rpc/queries/files.clj b/backend/src/app/rpc/queries/files.clj index 95ae1c0dc4..838f35d314 100644 --- a/backend/src/app/rpc/queries/files.clj +++ b/backend/src/app/rpc/queries/files.clj @@ -384,7 +384,7 @@ (s/def ::file-data-for-thumbnail (s/keys :req-un [::profile-id ::file-id] - :opt-in [::components-v2])) + :opt-un [::components-v2])) (sv/defmethod ::file-data-for-thumbnail "Retrieves the data for generate the thumbnail of the file. Used @@ -464,6 +464,24 @@ (check-read-permissions! pool profile-id file-id) (retrieve-file-libraries cfg false file-id)) + +;; --- Query: Files that use this File library + +(def ^:private sql:library-using-files + "SELECT f.id, + f.name + FROM file_library_rel AS flr + INNER JOIN file AS f ON f.id = flr.file_id + WHERE flr.library_file_id = ?;") + +(s/def ::library-using-files + (s/keys :req-un [::profile-id ::file-id])) + +(sv/defmethod ::library-using-files + [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] + (check-read-permissions! pool profile-id file-id) + (db/exec! pool [sql:library-using-files file-id])) + ;; --- QUERY: team-recent-files (def sql:team-recent-files diff --git a/common/src/app/common/pages/migrations.cljc b/common/src/app/common/pages/migrations.cljc index c1ee1dbd68..7807ea3ee0 100644 --- a/common/src/app/common/pages/migrations.cljc +++ b/common/src/app/common/pages/migrations.cljc @@ -15,14 +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] - [app.common.types.page :as ctp] - [app.common.types.pages-list :as ctpl] [app.common.types.shape :as cts] - [app.common.types.shape-tree :as ctst] [app.common.uuid :as uuid] [cuerdas.core :as str])) diff --git a/common/src/app/common/types/colors_list.cljc b/common/src/app/common/types/colors_list.cljc index d9378b1b38..7c1b7ba0a9 100644 --- a/common/src/app/common/types/colors_list.cljc +++ b/common/src/app/common/types/colors_list.cljc @@ -4,9 +4,7 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.common.types.colors-list - (:require - [app.common.data :as d])) +(ns app.common.types.colors-list) (defn colors-seq [file-data] diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index 4a84cf67eb..d81361a023 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -20,8 +20,8 @@ [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.types.typography :as cty] [app.common.uuid :as uuid] [clojure.spec.alpha :as s] [cuerdas.core :as str])) @@ -155,11 +155,11 @@ [[container instances]]))) find-asset-usages - (fn [file-data library-id asset-type asset] + (fn [file-data 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)] + (let [instances (find-asset-usages file-data asset)] (when (d/not-empty? instances) [[asset instances]]))) assets-seq))) @@ -252,7 +252,7 @@ (assoc-in [:options :components-v2] true)))))) (defn- absorb-components - [file-data library-data used-components] + [file-data used-components] (let [grid-gap 50 ; Search for the library page. If not exists, create it. @@ -336,7 +336,7 @@ (add-component-grid file-data (sort-by #(:name (first %)) used-components)))) (defn- absorb-colors - [file-data library-data used-colors] + [file-data used-colors] (let [absorb-color (fn [file-data [color usages]] (let [remap-shape #(ctc/remap-colors % (:id file-data) color) @@ -360,7 +360,7 @@ used-colors))) (defn- absorb-typographies - [file-data library-data used-typographies] + [file-data used-typographies] (let [absorb-typography (fn [file-data [typography usages]] (let [remap-shape #(cty/remap-typographies % (:id file-data) typography) @@ -393,13 +393,13 @@ (cond-> file-data (d/not-empty? used-components) - (absorb-components library-data used-components) + (absorb-components used-components) (d/not-empty? used-colors) - (absorb-colors library-data used-colors) + (absorb-colors used-colors) (d/not-empty? used-typographies) - (absorb-typographies library-data used-typographies)))) + (absorb-typographies used-typographies)))) ;; Debug helpers diff --git a/common/src/app/common/types/typographies_list.cljc b/common/src/app/common/types/typographies_list.cljc index ae3e794527..1e7dace0d1 100644 --- a/common/src/app/common/types/typographies_list.cljc +++ b/common/src/app/common/types/typographies_list.cljc @@ -4,9 +4,7 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.common.types.typographies-list - (:require - [app.common.data :as d])) +(ns app.common.types.typographies-list) (defn typographies-seq [file-data] diff --git a/frontend/resources/styles/main/partials/modal.scss b/frontend/resources/styles/main/partials/modal.scss index 98c35746aa..2c69b0eaee 100644 --- a/frontend/resources/styles/main/partials/modal.scss +++ b/frontend/resources/styles/main/partials/modal.scss @@ -134,6 +134,12 @@ font-size: $fs16; font-weight: 400; } + &.delete-shared { + padding: 15px 32px; + .modal-item-element { + font-size: $fs16; + } + } } .modal-footer { diff --git a/frontend/src/app/main.cljs b/frontend/src/app/main.cljs index a93eaf2a0d..a72475f79d 100644 --- a/frontend/src/app/main.cljs +++ b/frontend/src/app/main.cljs @@ -18,6 +18,7 @@ [app.main.ui :as ui] [app.main.ui.alert] [app.main.ui.confirm] + [app.main.ui.delete-shared] [app.main.ui.modal :refer [modal]] [app.main.ui.routes :as rt] [app.main.worker :as worker] diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index ba61e65248..24f6293435 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -247,6 +247,32 @@ (->> (rp/query :team-shared-files {:team-id team-id}) (rx/map shared-files-fetched)))))) +;; --- EVENT: Get files that use this shared-file + +(defn clean-temp-shared + [] + (ptk/reify ::clean-temp-shared + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:dashboard-local :files-with-shared] nil)))) + +(defn library-using-files-fetched + [files] + (ptk/reify ::library-using-files-fetched + ptk/UpdateEvent + (update [_ state] + (let [files (d/index-by :id files)] + (assoc-in state [:dashboard-local :files-with-shared] files))))) + +(defn fetch-library-using-files + [file] + (ptk/reify ::fetch-library-using-files + ptk/WatchEvent + (watch [_ _ _] + (let [file-id (:id file)] + (->> (rp/query :library-using-files {:file-id file-id}) + (rx/map library-using-files-fetched)))))) + ;; --- EVENT: recent-files (defn recent-files-fetched diff --git a/frontend/src/app/main/data/workspace/drawing/common.cljs b/frontend/src/app/main/data/workspace/drawing/common.cljs index 95429a6e66..98543f82bb 100644 --- a/frontend/src/app/main/data/workspace/drawing/common.cljs +++ b/frontend/src/app/main/data/workspace/drawing/common.cljs @@ -11,7 +11,6 @@ [app.common.math :as mth] [app.common.pages.helpers :as cph] [app.common.types.shape :as cts] - [app.main.data.workspace.common :as dwc] [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.undo :as dwu] diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 3b05d5f911..4ea4eab752 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -686,7 +686,7 @@ :library-id library-id}))) (when (and (seq (:redo-changes library-changes)) sync-components?) - (rx/of (sync-file-2nd-stage file-id library-id)))))))))) + (rx/of (sync-file-2nd-stage file-id library-id asset-id)))))))))) (defn- sync-file-2nd-stage "If some components have been modified, we need to launch another synchronization diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index 7269972696..d6022f7046 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -9,14 +9,13 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.geom.proportions :as gpr] - [app.common.pages :as cp] [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] [app.common.spec :as us] [app.common.types.page :as ctp] [app.common.types.shape :as cts] - [app.common.types.shape.interactions :as ctsi] [app.common.types.shape-tree :as ctst] + [app.common.types.shape.interactions :as ctsi] [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.edition :as dwe] diff --git a/frontend/src/app/main/ui/dashboard/file_menu.cljs b/frontend/src/app/main/ui/dashboard/file_menu.cljs index 7903f591ae..3091904e1d 100644 --- a/frontend/src/app/main/ui/dashboard/file_menu.cljs +++ b/frontend/src/app/main/ui/dashboard/file_menu.cljs @@ -92,19 +92,26 @@ on-delete (fn [event] (dom/stop-propagation event) - (if multi? - (st/emit! (modal/show - {:type :confirm - :title (tr "modals.delete-file-multi-confirm.title" file-count) - :message (tr "modals.delete-file-multi-confirm.message" file-count) - :accept-label (tr "modals.delete-file-multi-confirm.accept" file-count) - :on-accept delete-fn})) - (st/emit! (modal/show - {:type :confirm - :title (tr "modals.delete-file-confirm.title") - :message (tr "modals.delete-file-confirm.message") - :accept-label (tr "modals.delete-file-confirm.accept") - :on-accept delete-fn})))) + (if (:is-shared file) + (do (st/emit! (dd/fetch-library-using-files file)) + (st/emit! (modal/show + {:type :delete-shared + :origin :delete + :on-accept delete-fn}))) + + (if multi? + (st/emit! (modal/show + {:type :confirm + :title (tr "modals.delete-file-multi-confirm.title" file-count) + :message (tr "modals.delete-file-multi-confirm.message" file-count) + :accept-label (tr "modals.delete-file-multi-confirm.accept" file-count) + :on-accept delete-fn})) + (st/emit! (modal/show + {:type :confirm + :title (tr "modals.delete-file-confirm.title") + :message (tr "modals.delete-file-confirm.message") + :accept-label (tr "modals.delete-file-confirm.accept") + :on-accept delete-fn}))))) on-move-success (fn [team-id project-id] @@ -148,13 +155,10 @@ (fn [event] (dom/prevent-default event) (dom/stop-propagation event) + (st/emit! (dd/fetch-library-using-files file)) (st/emit! (modal/show - {:type :confirm - :message "" - :title (tr "modals.remove-shared-confirm.message" (:name file)) - :hint (tr "modals.remove-shared-confirm.hint") - :cancel-label :omit - :accept-label (tr "modals.remove-shared-confirm.accept") + {:type :delete-shared + :origin :unpublish :on-accept del-shared}))) on-export-files @@ -233,7 +237,7 @@ (when (or (seq current-projects) (seq other-teams)) [(tr "dashboard.move-to") nil sub-options "file-move-to"]) (if (:is-shared file) - [(tr "dashboard.remove-shared") on-del-shared nil "file-del-shared"] + [(tr "dashboard.unpublish-shared") on-del-shared nil "file-del-shared"] [(tr "dashboard.add-shared") on-add-shared nil "file-add-shared"]) [:separator] [(tr "dashboard.download-binary-file") on-export-binary-files nil "download-binary-file"] diff --git a/frontend/src/app/main/ui/dashboard/files.cljs b/frontend/src/app/main/ui/dashboard/files.cljs index 99678a6c38..251be9a693 100644 --- a/frontend/src/app/main/ui/dashboard/files.cljs +++ b/frontend/src/app/main/ui/dashboard/files.cljs @@ -127,5 +127,6 @@ [:section.dashboard-container [:& grid {:project project :files files - :on-create-clicked on-create-clicked}]]])) + :on-create-clicked on-create-clicked + :origin :files}]]])) diff --git a/frontend/src/app/main/ui/dashboard/grid.cljs b/frontend/src/app/main/ui/dashboard/grid.cljs index d852322f9c..de1ac3b5cc 100644 --- a/frontend/src/app/main/ui/dashboard/grid.cljs +++ b/frontend/src/app/main/ui/dashboard/grid.cljs @@ -75,12 +75,13 @@ (mf/defc grid-item {:wrap [mf/memo]} - [{:keys [file navigate?] :as props}] + [{:keys [file navigate? origin] :as props}] (let [file-id (:id file) local (mf/use-state {:menu-open false :menu-pos nil :edition false}) selected-files (mf/deref refs/dashboard-selected-files) + dashboard-local (mf/deref refs/dashboard-local) item-ref (mf/use-ref) menu-ref (mf/use-ref) selected? (contains? selected-files file-id) @@ -205,10 +206,12 @@ :top (:y (:menu-pos @local)) :navigate? navigate? :on-edit on-edit - :on-menu-close on-menu-close}])]]])) + :on-menu-close on-menu-close + :origin origin + :dashboard-local dashboard-local}])]]])) (mf/defc grid - [{:keys [files project on-create-clicked] :as props}] + [{:keys [files project on-create-clicked origin] :as props}] (let [dragging? (mf/use-state false) project-id (:id project) @@ -268,7 +271,8 @@ [:& grid-item {:file item :key (:id item) - :navigate? true}])] + :navigate? true + :origin origin}])] :else [:& empty-placeholder {:default? (:is-default project) @@ -276,7 +280,7 @@ :project project}])])) (mf/defc line-grid-row - [{:keys [files selected-files on-load-more dragging?] :as props}] + [{:keys [files selected-files on-load-more dragging? origin] :as props}] (let [rowref (mf/use-ref) width (mf/use-state nil) @@ -322,7 +326,8 @@ :file item :selected-files selected-files :key (:id item) - :navigate? false}]) + :navigate? false + :origin origin}]) (when (and (> limit 0) (> (count files) limit)) [:div.grid-item.placeholder {:on-click on-load-more} @@ -331,7 +336,7 @@ (tr "dashboard.show-all-files")]])])) (mf/defc line-grid - [{:keys [project team files on-load-more on-create-clicked] :as props}] + [{:keys [project team files on-load-more on-create-clicked origin] :as props}] (let [dragging? (mf/use-state false) project-id (:id project) team-id (:id team) @@ -415,7 +420,8 @@ :team-id team-id :selected-files selected-files :on-load-more on-load-more - :dragging? @dragging?}] + :dragging? @dragging? + :origin origin}] :else [:& empty-placeholder {:dragging? @dragging? diff --git a/frontend/src/app/main/ui/dashboard/libraries.cljs b/frontend/src/app/main/ui/dashboard/libraries.cljs index 80245771e6..df8e838fd6 100644 --- a/frontend/src/app/main/ui/dashboard/libraries.cljs +++ b/frontend/src/app/main/ui/dashboard/libraries.cljs @@ -42,5 +42,6 @@ [:h1 (tr "dashboard.libraries-title")]]] [:section.dashboard-container [:& grid {:files files - :project default-project}]]])) + :project default-project + :origin :libraries}]]])) diff --git a/frontend/src/app/main/ui/dashboard/projects.cljs b/frontend/src/app/main/ui/dashboard/projects.cljs index d23ffc7894..1622eb3f49 100644 --- a/frontend/src/app/main/ui/dashboard/projects.cljs +++ b/frontend/src/app/main/ui/dashboard/projects.cljs @@ -153,7 +153,8 @@ :team team :on-load-more on-nav :files files - :on-create-clicked (partial create-file "dashboard:empty-folder-placeholder")}]])) + :on-create-clicked (partial create-file "dashboard:empty-folder-placeholder") + :origin :project}]])) (def recent-files-ref (l/derived :dashboard-recent-files st/state)) diff --git a/frontend/src/app/main/ui/delete_shared.cljs b/frontend/src/app/main/ui/delete_shared.cljs new file mode 100644 index 0000000000..d1fbe026ab --- /dev/null +++ b/frontend/src/app/main/ui/delete_shared.cljs @@ -0,0 +1,117 @@ +;; 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.main.ui.delete-shared + (:require + [app.main.data.dashboard :as dd] + [app.main.data.modal :as modal] + [app.main.refs :as refs] + [app.main.store :as st] + [app.main.ui.icons :as i] + [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [tr]] + [app.util.keyboard :as k] + [goog.events :as events] + [rumext.alpha :as mf]) + (:import goog.events.EventType)) + +(mf/defc delete-shared-dialog + {::mf/register modal/components + ::mf/register-as :delete-shared} + [{:keys [on-accept + on-cancel + accept-style + origin] :as props}] + (let [on-accept (or on-accept identity) + on-cancel (or on-cancel identity) + cancel-label (tr "labels.cancel") + accept-style (or accept-style :danger) + is-delete? (= origin :delete) + dashboard-local (mf/deref refs/dashboard-local) + files->shared (:files-with-shared dashboard-local) + count-files (count (keys files->shared)) + title (if is-delete? + (tr "modals.delete-shared-confirm.title") + (tr "modals.unpublish-shared-confirm.title")) + message (if is-delete? + (tr "modals.delete-shared-confirm.message") + (tr "modals.unpublish-shared-confirm.message")) + + accept-label (if is-delete? + (tr "modals.delete-shared-confirm.accept") + (tr "modals.unpublish-shared-confirm.accept")) + scd-message (if is-delete? + (tr "modals.delete-shared-confirm.scd-message" (i18n/c count-files)) + (tr "modals.unpublish-shared-confirm.scd-message" (i18n/c count-files))) + hint (if is-delete? + "" + (tr "modals.unpublish-shared-confirm.hint" (i18n/c count-files))) + + accept-fn + (mf/use-callback + (fn [event] + (dom/prevent-default event) + (st/emit! (modal/hide)) + (on-accept props))) + + cancel-fn + (mf/use-callback + (fn [event] + (dom/prevent-default event) + (st/emit! (modal/hide)) + (on-cancel props)))] + + (mf/with-effect + (letfn [(on-keydown [event] + (when (k/enter? event) + (dom/prevent-default event) + (dom/stop-propagation event) + (st/emit! (modal/hide)) + (on-accept props)))] + (->> (events/listen js/document EventType.KEYDOWN on-keydown) + (partial events/unlistenByKey))) + #(st/emit! (dd/clean-temp-shared))) + + [:div.modal-overlay + [:div.modal-container.confirm-dialog + [:div.modal-header + [:div.modal-header-title + [:h2 title]] + [:div.modal-close-button + {:on-click cancel-fn} i/close]] + + [:div.modal-content.delete-shared + (when (and (string? message) (not= message "")) + [:h3 message]) + + (when (> (count files->shared) 0) + [:* + [:div + (when (and (string? scd-message) (not= scd-message "")) + [:h3 scd-message]) + [:ul.file-list + (for [[id file] files->shared] + [:li.modal-item-element + {:key id} + [:span "- " (:name file)]])]] + (when (and (string? hint) (not= hint "")) + [:h3 hint])])] + + [:div.modal-footer + [:div.action-buttons + (when-not (= cancel-label :omit) + [:input.cancel-button + {:type "button" + :value cancel-label + :on-click cancel-fn}]) + + [:input.accept-button + {:class (dom/classnames + :danger (= accept-style :danger) + :primary (= accept-style :primary)) + :type "button" + :value accept-label + :on-click accept-fn}]]]]])) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 2bd88884b6..c4f33a3b7f 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -539,6 +539,10 @@ msgstr "Want to remove your account?" msgid "dashboard.remove-shared" msgstr "Remove as Shared Library" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "Unpublish Library" + #: src/app/main/ui/settings/profile.cljs msgid "dashboard.save-settings" msgstr "Save settings" @@ -1775,6 +1779,49 @@ msgstr "" msgid "modals.remove-shared-confirm.message" msgstr "Remove “%s” as Shared Library" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.title" +msgstr "Unpublish library" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.message" +msgstr "Are you sure you want to unpublish this library?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.scd-message" +msgid_plural "modals.unpublish-shared-confirm.scd-message" +msgstr[0] "It's in use in this file:" +msgstr[1] "It's in use in these files:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.hint" +msgid_plural "modals.unpublish-shared-confirm.hint" +msgstr[0] "If you unpublish it, the assets in it became a library of this file." +msgstr[1] "If you unpublish it, the assets in it became a library of these files." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.accept" +msgstr "Unpublish" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.title" +msgstr "Deleting file" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.message" +msgstr "Are you sure you want to delete this file?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message" +msgid_plural "modals.delete-shared-confirm.scd-message" +msgstr[0] "This file has libraries that are being used in this file:" +msgstr[1] "This file has libraries that are being used in these files:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.accept" +msgstr "Delete file" + + #: src/app/main/ui/workspace/nudge.cljs msgid "modals.small-nudge" msgstr "Small nudge" @@ -1807,6 +1854,10 @@ msgstr "" msgid "modals.update-remote-component.message" msgstr "Update a component in a shared library" +#: src/app/main/ui/delete_shared.cljs +msgid "modals.delete-shared.title" +msgstr "Deleting file" + #: src/app/main/ui/dashboard/team.cljs msgid "notifications.invitation-email-sent" msgstr "Invitation sent successfully" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index a6c55af0dd..72ed97ec2b 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -551,6 +551,10 @@ msgstr "¿Quieres borrar tu cuenta?" msgid "dashboard.remove-shared" msgstr "Eliminar como Biblioteca Compartida" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "Despublicar Biblioteca" + #: src/app/main/ui/settings/profile.cljs msgid "dashboard.save-settings" msgstr "Guardar opciones" @@ -1849,6 +1853,48 @@ msgstr "" msgid "modals.remove-shared-confirm.message" msgstr "Añadir “%s” como Biblioteca Compartida" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.title" +msgstr "Despublicar biblioteca" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.message" +msgstr "¿Seguro que quieres despublicar esta biblioteca?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.scd-message" +msgid_plural "modals.unpublish-shared-confirm.scd-message" +msgstr[0] "Está siendo usada en este archivo:" +msgstr[1] "Está siendo usada en estos archivos:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.hint" +msgid_plural "modals.unpublish-shared-confirm.hint" +msgstr[0] "Si la despublicas, los elementos pasarán a formar parte de la biblioteca del archivo." +msgstr[1] "Si la despublicas, los elementos pasarán a formar parte de la biblioteca de los archivos." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.accept" +msgstr "Despublicar" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.title" +msgstr "Borrar archivo" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.message" +msgstr "¿Seguro que quieres borrar este archivo?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message" +msgid_plural "modals.delete-shared-confirm.scd-message" +msgstr[0] "El archivo que quieres borrar tiene una librería que se está usando en este archivo:" +msgstr[1] "El archivo que quieres borrar tiene una librería que se está usando en estos archivos:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.accept" +msgstr "Borrar archivo" + #: src/app/main/ui/workspace/nudge.cljs msgid "modals.small-nudge" msgstr "Mínimo" @@ -1887,6 +1933,10 @@ msgstr "" msgid "modals.update-remote-component.message" msgstr "Actualizar un componente en librería" +#: src/app/main/ui/delete_shared.cljs +msgid "modals.delete-shared.title" +msgstr "Borrar archivo" + #: src/app/main/ui/dashboard/team.cljs msgid "notifications.invitation-email-sent" msgstr "Invitación enviada con éxito" From 0667089833300153455cdde6c7a966525583c15d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 28 Jul 2022 15:10:11 +0200 Subject: [PATCH 13/14] :wrench: Some style enhancements and mini bug fix --- backend/src/app/rpc/mutations/files.clj | 18 +--- common/src/app/common/geom/shapes.cljc | 4 +- common/src/app/common/pages/changes.cljc | 6 +- common/src/app/common/types/color.cljc | 86 +++++++++---------- .../src/app/common/types/components_list.cljc | 5 +- common/src/app/common/types/container.cljc | 2 +- common/src/app/common/types/file.cljc | 8 +- common/test/app/common/types/file_test.cljc | 2 +- .../app/main/data/workspace/libraries.cljs | 21 ++--- .../src/app/main/ui/dashboard/export.cljs | 5 +- frontend/src/app/worker/export.cljs | 16 ++-- 11 files changed, 83 insertions(+), 90 deletions(-) diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index e4768e78e7..ff45fb6c6f 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -123,17 +123,13 @@ (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)) + (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])) + (db/delete! conn :file-library-rel {:library-file-id id})) (defn- set-file-shared [conn {:keys [id is-shared] :as params}] @@ -162,11 +158,6 @@ {: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" @@ -189,8 +180,7 @@ :modified-at ts} {:id (:id file)})))] - (dorun (->> (db/exec! conn [sql:find-files id]) - (map process-file))))))) + (run! process-file (db/query conn :file-library-rel {:library-file-id id})))))) ;; --- Mutation: Link file to library diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index c75f47e52d..a08fe8a21a 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -52,12 +52,12 @@ ; TODO: perhaps some day we want after transformations, but for the ; moment it's enough as is now. [shape] - (get shape :x (:x (:selrect shape)))) ; Paths don't have :x attribute + (or (:x shape) (:x (:selrect shape)))) ; Paths don't have :x attribute (defn top-bound "Returns the lowest y coord of the shape BEFORE applying transformations." [shape] - (get shape :y (:y (:selrect shape)))) ; Paths don't have :y attribute + (or (:y shape) (:y (:selrect shape)))) ; Paths don't have :y attribute (defn fully-contained? "Checks if one rect is fully inside the other" diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc index 6916500a17..152612769b 100644 --- a/common/src/app/common/pages/changes.cljc +++ b/common/src/app/common/pages/changes.cljc @@ -477,9 +477,9 @@ (component-sync-attrs (:attr operation)))) any-sync? (some need-sync? operations)] (when any-sync? - (into #{} (->> shape-and-parents - (filter #(:main-instance? %)) ; Select shapes that are main component instances - (map :id))))))) + (let [xform (comp (filter :main-instance?) ; Select shapes that are main component instances + (map :id))] + (into #{} xform shape-and-parents)))))) (defmethod components-changed :default [_ _] diff --git a/common/src/app/common/types/color.cljc b/common/src/app/common/types/color.cljc index 5f462796c3..06c5c497ba 100644 --- a/common/src/app/common/types/color.cljc +++ b/common/src/app/common/types/color.cljc @@ -246,38 +246,38 @@ (defn- process-shape-colors "Execute an update function on all colors of a shape." - [shape func] + [shape process-fn] (let [process-fill (fn [shape [position fill]] - (func shape - position - (fill->shape-color fill) - set-fill-color - attach-fill-color - detach-fill-color)) + (process-fn 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-fn 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-fn 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-fn shape + position + (grid->shape-color grid) + set-grid-color + attach-grid-color + detach-grid-color)) process-text-node (fn [node] (as-> node $ @@ -302,13 +302,13 @@ "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))] + (letfn [(remap-color [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))) @@ -316,17 +316,17 @@ "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] - (if (= (:ref-file shape-color) library-id) - (let [library-color (get library-colors (:ref-id shape-color))] - (if (some? library-color) - (set-fn shape - position - (:color library-color) - (:opacity library-color) - (:gradient library-color)) - (detach-fn shape position))) - shape))] + (letfn [(sync-color [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) + (set-fn shape + position + (:color library-color) + (:opacity library-color) + (:gradient library-color)) + (detach-fn shape position))) + shape))] (process-shape-colors shape sync-color))) diff --git a/common/src/app/common/types/components_list.cljc b/common/src/app/common/types/components_list.cljc index cb4bd70ae2..d2bbff58f2 100644 --- a/common/src/app/common/types/components_list.cljc +++ b/common/src/app/common/types/components_list.cljc @@ -24,9 +24,8 @@ :objects (d/index-by :id shapes)}) components-v2 - (update-in [:components id] #(assoc % - :main-instance-id main-instance-id - :main-instance-page main-instance-page))))) + (update-in [:components id] assoc :main-instance-id main-instance-id + :main-instance-page main-instance-page)))) (defn get-component [file-data component-id] diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index d58c05e3a4..b6ea7b9176 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -96,7 +96,7 @@ :component-file file-id :component-root? true) - components-v2 + (and (nil? (:parent-id new-shape)) components-v2) (assoc :main-instance? true) (some? (:parent-id new-shape)) diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index d81361a023..7653d99545 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -165,10 +165,10 @@ assets-seq))) (defn get-or-add-library-page - "If exists a page named 'Library page', get the id and calculate the position to start + "If exists a page named 'Library backup', get the id and calculate the position to start adding new components. If not, create it and start at (0, 0)." [file-data grid-gap] - (let [library-page (d/seek #(= (:name %) "Library page") (ctpl/pages-seq file-data))] + (let [library-page (d/seek #(= (:name %) "Library backup") (ctpl/pages-seq file-data))] (if (some? library-page) (let [compare-pos (fn [pos shape] (let [bounds (gsh/bounding-box shape)] @@ -180,11 +180,11 @@ (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")] + (let [library-page (ctp/make-empty-page (uuid/next) "Library backup")] [(ctpl/add-page file-data library-page) (:id library-page) (gpt/point 0 0)])))) (defn migrate-to-components-v2 - "If there is any component in the file library, add a new 'Library Page' and generate + "If there is any component in the file library, add a new 'Library backup' and generate main instances for all components there. Mark the file with the :comonents-v2 option." [file-data] (let [components (ctkl/components-seq file-data)] diff --git a/common/test/app/common/types/file_test.cljc b/common/test/app/common/types/file_test.cljc index 908c2b3e1c..70f14a36a6 100644 --- a/common/test/app/common/types/file_test.cljc +++ b/common/test/app/common/types/file_test.cljc @@ -104,7 +104,7 @@ (t/is (= (count pages) 2)) (t/is (= (:name (first pages)) "Page-1")) - (t/is (= (:name (second pages)) "Library page")) + (t/is (= (:name (second pages)) "Library backup")) (t/is (= (count components) 1)) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 4ea4eab752..d460cbf896 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -770,12 +770,12 @@ (rx/filter #(or (= :app.main.data.workspace/finalize-page (ptk/type %)) (= ::watch-component-changes (ptk/type %))))) - workspace-data-str + workspace-data-s (->> (rx/concat (rx/of nil) (rx/from-atom refs/workspace-data {:emit-current-value? true}))) - change-str + change-s (->> stream (rx/filter #(or (dch/commit-changes? %) (= (ptk/type %) :app.main.data.workspace.notifications/handle-file-change))) @@ -788,13 +788,13 @@ #{} changes)] (when (d/not-empty? components-changed) - (apply st/emit! + (run! st/emit! (map #(update-component-sync % (:id data)) components-changed)))))] (when components-v2 - (->> change-str - (rx/with-latest-from workspace-data-str) + (->> change-s + (rx/with-latest-from workspace-data-s) (rx/map check-changes) (rx/take-until stopper))))))) @@ -844,12 +844,13 @@ [file-id library-id] (ptk/reify ::attach-library ptk/WatchEvent - (watch [_ _ _] - (let [fetched #(assoc-in %2 [:workspace-libraries (:id %1)] %1) - params {:file-id file-id - :library-id library-id}] + (watch [_ state _] + (let [components-v2 (features/active-feature? state :components-v2) + fetched #(assoc-in %2 [:workspace-libraries (:id %1)] %1) + params {:file-id file-id + :library-id library-id}] (->> (rp/mutation :link-file-to-library params) - (rx/mapcat #(rp/query :file {:id library-id})) + (rx/mapcat #(rp/query :file {:id library-id :components-v2 components-v2})) (rx/map #(partial fetched %))))))) (defn unlink-file-from-library diff --git a/frontend/src/app/main/ui/dashboard/export.cljs b/frontend/src/app/main/ui/dashboard/export.cljs index 58f8d957d5..998e558039 100644 --- a/frontend/src/app/main/ui/dashboard/export.cljs +++ b/frontend/src/app/main/ui/dashboard/export.cljs @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.main.data.modal :as modal] + [app.main.features :as features] [app.main.store :as st] [app.main.ui.icons :as i] [app.main.worker :as uw] @@ -56,6 +57,8 @@ :files (->> files (mapv #(assoc % :loading? true)))}) selected-option (mf/use-state :all) + components-v2 (features/use-feature :components-v2) + start-export (fn [] (swap! state assoc :status :exporting) @@ -64,7 +67,7 @@ :team-id team-id :export-type @selected-option :files files - }) + :components-v2 components-v2}) (rx/delay-emit 1000) (rx/subs (fn [msg] diff --git a/frontend/src/app/worker/export.cljs b/frontend/src/app/worker/export.cljs index bd55211100..1427ebd8a4 100644 --- a/frontend/src/app/worker/export.cljs +++ b/frontend/src/app/worker/export.cljs @@ -149,8 +149,8 @@ (->> (r/render-components (:data file)) (rx/map #(vector (str (:id file) "/components.svg") %)))) -(defn fetch-file-with-libraries [file-id] - (->> (rx/zip (rp/query :file {:id file-id}) +(defn fetch-file-with-libraries [file-id components-v2] + (->> (rx/zip (rp/query :file {:id file-id :components-v2 components-v2}) (rp/query :file-libraries {:file-id file-id})) (rx/map (fn [[file file-libraries]] @@ -351,7 +351,7 @@ (update file-id dissoc :libraries)))) (defn collect-files - [file-id export-type] + [file-id export-type components-v2] (letfn [(fetch-dependencies [[files pending]] (if (empty? pending) @@ -365,7 +365,7 @@ ;; The file is already in the result (rx/of [files pending]) - (->> (fetch-file-with-libraries next) + (->> (fetch-file-with-libraries next components-v2) (rx/map (fn [file] [(-> files @@ -381,9 +381,9 @@ (rx/map #(process-export file-id export-type %)))))) (defn export-file - [team-id file-id export-type] + [team-id file-id export-type components-v2] - (let [files-stream (->> (collect-files file-id export-type) + (let [files-stream (->> (collect-files file-id export-type components-v2) (rx/share)) manifest-stream @@ -471,12 +471,12 @@ :file-id (:id file)})))))))) (defmethod impl/handler :export-standard-file - [{:keys [team-id files export-type] :as message}] + [{:keys [team-id files export-type components-v2] :as message}] (->> (rx/from files) (rx/mapcat (fn [file] - (->> (export-file team-id (:id file) export-type) + (->> (export-file team-id (:id file) export-type components-v2) (rx/map (fn [value] (if (contains? value :type) From f4482eb5a7896a35a8ad7572b0ae6a38483cbfd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Fri, 29 Jul 2022 10:54:52 +0200 Subject: [PATCH 14/14] :sparkles: Allow to set features by config file --- frontend/src/app/config.cljs | 6 ++++++ frontend/src/app/main/features.cljs | 18 ++++++++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/config.cljs b/frontend/src/app/config.cljs index 1de776e7c6..a9e99252bb 100644 --- a/frontend/src/app/config.cljs +++ b/frontend/src/app/config.cljs @@ -63,6 +63,11 @@ flags (sequence (map keyword) (str/words flags))] (flags/parse flags/default default-flags flags))) +(defn- parse-features + [global] + (when-let [features-str (obj/get global "penpotFeatures")] + (map keyword (str/words features-str)))) + (defn- parse-version [global] (-> (obj/get global "penpotVersion") @@ -88,6 +93,7 @@ (def build-date (parse-build-date global)) (def flags (atom (parse-flags global))) +(def features (atom (parse-features global))) (def version (atom (parse-version global))) (def target (atom (parse-target global))) (def browser (atom (parse-browser))) diff --git a/frontend/src/app/main/features.cljs b/frontend/src/app/main/features.cljs index 236314cb80..5239bdd39a 100644 --- a/frontend/src/app/main/features.cljs +++ b/frontend/src/app/main/features.cljs @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.logging :as log] + [app.config :as cfg] [app.main.store :as st] [okulary.core :as l] [potok.core :as ptk] @@ -27,7 +28,6 @@ :result (if (not (contains? (:features state) feature)) "enabled" "disabled")) - (-> state (update :features (fn [features] @@ -62,8 +62,14 @@ active-feature? (mf/deref active-feature-ref)] active-feature?)) -;; By default the features are active in local environments -(when *assert* - ;; Activate all features in local environment - (doseq [f features-list] - (toggle-feature! f))) +;; Read initial enabled features from config, if set +(if-let [enabled-features @cfg/features] + (doseq [f enabled-features] + (toggle-feature! f)) + (when *assert* + ;; By default, all features disabled, except in development + ;; environment, that are enabled except components-v2 + (doseq [f features-list] + (when (not= f :components-v2) + (toggle-feature! f))))) +