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 36a8a576c..6e0c74115 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 25808b136..13e9d710f 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 044d97346..52fa4748d 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 000000000..ab45fb4a8 --- /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 d9671bfed..6f9ac6d35 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 7663a0585..6e4fa70ca 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 a1e50c89f..1c74d9d1e 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 28adef987..77d526f4b 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 0a54feeae..21b5e1e32 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 34a3518c9..7f2f191ac 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 b8232b043..ae5d38542 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 17b1fce61..6068c1799 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 32b19d0fa..27a9fe468 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 0a28635ce..3e19ec5f9 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 18ec92848..7adc19fbd 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 29ad2eeb8..1f9d5042e 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 882991a1f..5fcc64c0f 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 a7ca0f495..57a048b2e 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 52fa4748d..5984cd46a 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 e596179ad..a1fb2fd72 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 000000000..168cfe8ca --- /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 0ed513dc0..b86092f35 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 000000000..3f037ece2 --- /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 000000000..c9bcef507 --- /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 54c042222..896c49390 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 62dc383ad..d778cdb0b 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 771c06bb2..70fe8d222 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 209c8f549..a1f4da573 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 6f9ac6d35..39724ee8b 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 bdffbe1f7..aa550f98c 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 d60cc040c..66062fd16 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 2b315fc5e..d5533bc98 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 40c6a80f9..e1a653be7 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 6e4fa70ca..9a8acafb0 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 1c74d9d1e..2914f4095 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 ff61c094a..fe2f4f55e 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 dbc2a68ba..f26c3400e 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 77d526f4b..237ce6029 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 3c830bd59..683bc946f 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 e8e9268fa..34b2ef920 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 46302a4a1..35edbc26c 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 240a44201..66b8ee2c4 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 18035e283..5efd82332 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 ece6b20b2..6f1691283 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 ed338e282..067db2303 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 a6f488149..2909ab0b4 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 56725968f..1ae92aa12 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 1940deaf7..8a7a1cbe6 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 6a2288fcd..000000000 --- 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 81f60b952..916f1b992 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 b392e7100..bc0d2b744 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 b3d754a37..442b8c60f 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 f3644d8e3..428653234 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 baa0f2fe3..d03a935a2 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 5fcc64c0f..d744e41a9 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 31c103779..c75f47e52 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 6e0c74115..2d912f7b0 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 1b73deac2..cd9e94ee7 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 3b1e85b04..000000000 --- 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 d2beb3ce0..512df7092 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 b86092f35..2aee3761a 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 c9bcef507..4fd9cb0a3 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 13e9d710f..bdf4a1a96 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 9a8acafb0..2217af1dd 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 2914f4095..39885b847 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 bef2d105d..09d651d14 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 dba09a40a..b3ee8cb3b 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 7adc19fbd..0fd9431e0 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 1f9d5042e..ec8b0a2e8 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 d744e41a9..a924186b3 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 e500d372d..b4c43458c 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 2d912f7b0..139d37e65 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 bdf4a1a96..50891acc1 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 512df7092..601671bfd 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 000000000..6f5643e67 --- /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 168cfe8ca..7f7e8d985 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 5c7475d60..b999f3ccd 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 3f037ece2..5275e480e 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 a5b182f64..249c589ce 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 4fd9cb0a3..c7294699a 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 079ecafd6..b7307b3c1 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 abfa5f8f3..7de27ffae 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 000000000..bbb9c58dd --- /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 000000000..0049732ab --- /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 7c90b625c..a84019496 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 d778cdb0b..8407df47f 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 39724ee8b..6f9ac6d35 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 cb75f95cf..06384d41c 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 aa550f98c..1868eb61b 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 03f956aa5..95429a6e6 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 d5533bc98..5906db729 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 39885b847..70000e1e0 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 683bc946f..2f3e5040e 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 4a1e4f7de..2f774d0d2 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 aff53ab5b..b606ca3db 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 09d651d14..077993bc3 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 bc0d2b744..05474e551 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 442b8c60f..05bf78ea2 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 428653234..6e0142b3e 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 6156a02ce..a061ecc43 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 6982d4f6f..9f18e5a1c 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 b3ee8cb3b..ccdacf86a 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -111,16 +111,29 @@ ;; --- Mutation: Set File shared (declare set-file-shared) +(declare unlink-files) +(declare absorb-library) (s/def ::set-file-shared (s/keys :req-un [::profile-id ::id ::is-shared])) (sv/defmethod ::set-file-shared - [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] + [{:keys [pool] :as cfg} {:keys [id profile-id is-shared] :as params}] (db/with-atomic [conn pool] (files/check-edition-permissions! conn profile-id id) + (when-not is-shared + (absorb-library conn params) + (unlink-files conn params)) (set-file-shared conn params))) +(def sql:unlink-files + "delete from file_library_rel + where library_file_id = ?") + +(defn- unlink-files + [conn {:keys [id] :as params}] + (db/exec-one! conn [sql:unlink-files id])) + (defn- set-file-shared [conn {:keys [id is-shared] :as params}] (db/update! conn :file @@ -138,6 +151,7 @@ [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] (db/with-atomic [conn pool] (files/check-edition-permissions! conn profile-id id) + (absorb-library conn params) (mark-file-deleted conn params))) (defn mark-file-deleted @@ -147,6 +161,35 @@ {:id id}) nil) +(def sql:find-files + "select file_id + from file_library_rel + where library_file_id=?") + +(defn absorb-library + "Find all files using a shared library, and absorb all library assets + into the file local libraries" + [conn {:keys [id] :as params}] + (let [library (->> (db/get-by-id conn :file id) + (files/decode-row) + (pmg/migrate-file))] + (when (:is-shared library) + (let [process-file + (fn [row] + (let [ts (dt/now) + file (->> (db/get-by-id conn :file (:file-id row)) + (files/decode-row) + (pmg/migrate-file)) + updated-data (ctf/absorb-assets (:data file) (:data library))] + + (db/update! conn :file + {:revn (inc (:revn file)) + :data (blob/encode updated-data) + :modified-at ts} + {:id (:id file)})))] + + (dorun (->> (db/exec! conn [sql:find-files id]) + (map process-file))))))) ;; --- Mutation: Link file to library diff --git a/common/src/app/common/pages/migrations.cljc b/common/src/app/common/pages/migrations.cljc index 601671bfd..cb186933f 100644 --- a/common/src/app/common/pages/migrations.cljc +++ b/common/src/app/common/pages/migrations.cljc @@ -15,7 +15,9 @@ [app.common.math :as mth] [app.common.pages :as cp] [app.common.pages.helpers :as cph] + [app.common.types.components-list :as ctkl] [app.common.types.container :as ctn] + [app.common.types.file :as ctf] [app.common.types.page :as ctp] [app.common.types.pages-list :as ctpl] [app.common.types.shape :as cts] @@ -439,70 +441,65 @@ (defmethod migrate 20 [data] - (let [page-id (uuid/next) - - components (->> (:components data) - vals - (sort-by :name)) - - add-library-page - (fn [data] - (let [page (ctp/make-empty-page page-id "Library page")] - (-> data - (ctpl/add-page page)))) - - add-main-instance - (fn [data component position] - (let [page (ctpl/get-page data page-id) - - [new-shape new-shapes] - (ctn/instantiate-component page - component - (:id data) - position) - - add-shape - (fn [data shape] - (update-in data [:pages-index page-id] - #(ctst/add-shape (:id shape) - shape - % - (:frame-id shape) - (:parent-id shape) - nil ; <- As shapes are ordered, we can safely add each - true))) ; one at the end of the parent's children list. - - update-component - (fn [component] - (assoc component - :main-instance-id (:id new-shape) - :main-instance-page page-id))] - - (as-> data $ - (reduce add-shape $ new-shapes) - (update-in $ [:components (:id component)] update-component)))) - - add-instance-grid - (fn [data components] - (let [position-seq (ctst/generate-shape-grid - (map cph/get-component-root components) - 50)] - (loop [data data - components-seq (seq components) - position-seq position-seq] - (let [component (first components-seq) - position (first position-seq)] - (if (nil? component) - data - (recur (add-main-instance data component position) - (rest components-seq) - (rest position-seq)))))))] - + (let [components (ctkl/components-seq data)] (if (empty? components) data - (-> data - (add-library-page) - (add-instance-grid components))))) + (let [grid-gap 50 + + [data page-id start-pos] + (ctf/get-or-add-library-page data grid-gap) + + add-main-instance + (fn [data component position] + (let [page (ctpl/get-page data page-id) + + [new-shape new-shapes] + (ctn/instantiate-component page + component + (:id data) + position) + + add-shapes + (fn [page] + (reduce (fn [page shape] + (ctst/add-shape (:id shape) + shape + page + (:frame-id shape) + (:parent-id shape) + nil ; <- As shapes are ordered, we can safely add each + true)) ; one at the end of the parent's children list. + page + new-shapes)) + + update-component + (fn [component] + (assoc component + :main-instance-id (:id new-shape) + :main-instance-page page-id))] + + (-> data + (ctpl/update-page page-id add-shapes) + (ctkl/update-component (:id component) update-component)))) + + add-instance-grid + (fn [data components] + (let [position-seq (ctst/generate-shape-grid + (map cph/get-component-root components) + start-pos + grid-gap)] + (loop [data data + components-seq (seq components) + position-seq position-seq] + (let [component (first components-seq) + position (first position-seq)] + (if (nil? component) + data + (recur (add-main-instance data component position) + (rest components-seq) + (rest position-seq)))))))] + + (add-instance-grid data (sort-by :name components)))))) ;; TODO: pending to do a migration for delete already not used fill ;; and stroke props. This should be done for >1.14.x version. diff --git a/common/src/app/common/types/component.cljc b/common/src/app/common/types/component.cljc new file mode 100644 index 000000000..5dbe23865 --- /dev/null +++ b/common/src/app/common/types/component.cljc @@ -0,0 +1,13 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.common.types.component) + +(defn instance-of? + [shape component] + (and (some? (:component-id shape)) + (= (:component-id shape) (:id component)))) + diff --git a/common/src/app/common/types/components_list.cljc b/common/src/app/common/types/components_list.cljc index 6f5643e67..6fb2b0872 100644 --- a/common/src/app/common/types/components_list.cljc +++ b/common/src/app/common/types/components_list.cljc @@ -26,3 +26,7 @@ [file-data component-id] (get-in file-data [:components component-id])) +(defn update-component + [file-data component-id f] + (update-in file-data [:components component-id] f)) + diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index 7f7e8d985..63a598504 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -56,6 +56,10 @@ [container] (vals (:objects container))) +(defn update-shape + [container shape-id f] + (update-in container [:objects shape-id] f)) + (defn make-component-shape "Clone the shape and all children. Generate new ids and detach from parent and frame. Update the original shapes to have links diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index b999f3ccd..e9543453d 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -6,18 +6,22 @@ (ns app.common.types.file (:require - [app.common.data :as d] - [app.common.pages.common :refer [file-version]] - [app.common.pages.helpers :as cph] - [app.common.spec :as us] - [app.common.types.color :as ctc] - [app.common.types.components-list :as ctkl] - [app.common.types.container :as ctn] - [app.common.types.page :as ctp] - [app.common.types.pages-list :as ctpl] - [app.common.uuid :as uuid] - [clojure.spec.alpha :as s] - [cuerdas.core :as str])) + [app.common.data :as d] + [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] + [app.common.pages.common :refer [file-version]] + [app.common.pages.helpers :as cph] + [app.common.spec :as us] + [app.common.types.color :as ctc] + [app.common.types.component :as ctk] + [app.common.types.components-list :as ctkl] + [app.common.types.container :as ctn] + [app.common.types.page :as ctp] + [app.common.types.pages-list :as ctpl] + [app.common.types.shape-tree :as ctst] + [app.common.uuid :as uuid] + [clojure.spec.alpha :as s] + [cuerdas.core :as str])) ;; Specs @@ -97,48 +101,270 @@ (concat (map #(ctn/make-container % :page) (ctpl/pages-seq file-data)) (map #(ctn/make-container % :component) (ctkl/components-seq file-data)))) +(defn update-container + "Update a container inside the file, it can be a page or a component" + [file-data container f] + (if (ctn/page? container) + (ctpl/update-page file-data (:id container) f) + (ctkl/update-component file-data (:id container) f))) + +(defn find-instances + "Find all uses of a component in a file (may be in pages or in the components + of the local library). + + Returns a vector [[container shapes] [container shapes]...]" + [file-data component] + (let [find-instances-in-container + (fn [container component] + (let [instances (filter #(ctk/instance-of? % component) (ctn/shapes-seq container))] + (when (d/not-empty? instances) + [[container instances]])))] + + (mapcat #(find-instances-in-container % component) (containers-seq file-data)))) + +(defn get-or-add-library-page + [file-data grid-gap] + "If exists a page named 'Library page', get the id and calculate the position to start + adding new components. If not, create it and start at (0, 0)." + (let [library-page (d/seek #(= (:name %) "Library page") (ctpl/pages-seq file-data))] + (if (some? library-page) + (let [compare-pos (fn [pos shape] + (let [bounds (gsh/bounding-box shape)] + (gpt/point (min (:x pos) (get bounds :x 0)) + (max (:y pos) (+ (get bounds :y 0) + (get bounds :height 0) + grid-gap))))) + position (reduce compare-pos + (gpt/point 0 0) + (ctn/shapes-seq library-page))] + [file-data (:id library-page) position]) + (let [library-page (ctp/make-empty-page (uuid/next) "Library page")] + [(ctpl/add-page file-data library-page) (:id library-page) (gpt/point 0 0)])))) + +(defn- absorb-components + [file-data library-data used-components] + (let [grid-gap 50 + + ; Search for the library page. If not exists, create it. + [file-data page-id start-pos] + (get-or-add-library-page file-data grid-gap) + + absorb-component + (fn [file-data [component instances] position] + (let [page (ctpl/get-page file-data page-id) + + ; Make a new main instance for the component + [main-instance-shape main-instance-shapes] + (ctn/instantiate-component page + component + (:id file-data) + position) + + ; Add all shapes of the main instance to the library page + add-main-instance-shapes + (fn [page] + (reduce (fn [page shape] + (ctst/add-shape (:id shape) + shape + page + (:frame-id shape) + (:parent-id shape) + nil ; <- As shapes are ordered, we can safely add each + true)) ; one at the end of the parent's children list. + page + main-instance-shapes)) + + ; Copy the component in the file local library + copy-component + (fn [file-data] + (ctkl/add-component file-data + (:id component) + (:name component) + (:path component) + (:id main-instance-shape) + page-id + (vals (:objects component)))) + + ; Change all existing instances to point to the local file + remap-instances + (fn [file-data [container shapes]] + (let [remap-instance #(assoc % :component-file (:id file-data))] + (update-container file-data + container + #(reduce (fn [container shape] + (ctn/update-shape container + (:id shape) + remap-instance)) + % + shapes))))] + + (as-> file-data $ + (ctpl/update-page $ page-id add-main-instance-shapes) + (copy-component $) + (reduce remap-instances $ instances)))) + + ; Absorb all used components into the local library. Position + ; the main instances in a grid in the library page. + add-component-grid + (fn [data used-components] + (let [position-seq (ctst/generate-shape-grid + (map #(ctk/get-component-root (first %)) used-components) + start-pos + grid-gap)] + (loop [data data + components-seq (seq used-components) + position-seq position-seq] + (let [used-component (first components-seq) + position (first position-seq)] + (if (nil? used-component) + data + (recur (absorb-component data used-component position) + (rest components-seq) + (rest position-seq)))))))] + + (add-component-grid file-data (sort-by #(:name (first %)) used-components)))) + +(defn- absorb-colors + [file-data library-data used-colors] + (let [absorb-color + (fn [file-data [color usages]] + (let [remap-shape #(ctc/remap-colors % (:id file-data) color) + + remap-shapes + (fn [file-data [container shapes]] + (update-container file-data + container + #(reduce (fn [container shape] + (ctn/update-shape container + (:id shape) + remap-shape)) + % + shapes)))] + (as-> file-data $ + (ctcl/add-color $ color) + (reduce remap-shapes $ usages))))] + + (reduce absorb-color + file-data + used-colors))) + +(defn- absorb-typographies + [file-data library-data used-typographies] + (let [absorb-typography + (fn [file-data [typography usages]] + (let [remap-shape #(cty/remap-typographies % (:id file-data) typography) + + remap-shapes + (fn [file-data [container shapes]] + (update-container file-data + container + #(reduce (fn [container shape] + (ctn/update-shape container + (:id shape) + remap-shape)) + % + shapes)))] + (as-> file-data $ + (ctyl/add-typography $ typography) + (reduce remap-shapes $ usages))))] + + (reduce absorb-typography + file-data + used-typographies))) + (defn absorb-assets "Find all assets of a library that are used in the file, and move them to the file local library." [file-data library-data] - (let [library-page-id (uuid/next) - - add-library-page - (fn [file-data] - (let [page (ctp/make-empty-page library-page-id "Library page")] - (-> file-data - (ctpl/add-page page)))) - - find-instances-in-container - (fn [container component] - (let [instances (filter #(= (:component-id %) (:id component)) - (ctn/shapes-seq container))] - (when (d/not-empty? instances) - [[container instances]]))) - - find-instances - (fn [file-data component] - (mapcat #(find-instances-in-container % component) (containers-seq file-data))) - - absorb-component - (fn [file-data _component] - ;; TODO: complete this - file-data) - - used-components + (let [; Build a list of all components in the library used in the file + ; The list is in the form [[component [[container shapes] [container shapes]...]]...] + used-components ; A vector of pair [component instances], where instances is non-empty (mapcat (fn [component] (let [instances (find-instances file-data component)] - (when instances + (when (d/not-empty? instances) [[component instances]]))) (ctkl/components-seq library-data))] (if (empty? used-components) file-data - (as-> file-data $ - (add-library-page $) - (reduce absorb-component - $ - used-components))))) + (let [; Search for the library page. If not exists, create it. + [file-data page-id start-pos] + (get-or-add-library-page file-data) + + absorb-component + (fn [file-data [component instances] position] + (let [page (ctpl/get-page file-data page-id) + + ; Make a new main instance for the component + [main-instance-shape main-instance-shapes] + (ctn/instantiate-component page + component + (:id file-data) + position) + + ; Add all shapes of the main instance to the library page + add-main-instance-shapes + (fn [page] + (reduce (fn [page shape] + (ctst/add-shape (:id shape) + shape + page + (:frame-id shape) + (:parent-id shape) + nil ; <- As shapes are ordered, we can safely add each + true)) ; one at the end of the parent's children list. + page + main-instance-shapes)) + + ; Copy the component in the file local library + copy-component + (fn [file-data] + (ctkl/add-component file-data + (:id component) + (:name component) + (:path component) + (:id main-instance-shape) + page-id + (vals (:objects component)))) + + ; Change all existing instances to point to the local file + redirect-instances + (fn [file-data [container shapes]] + (let [redirect-instance #(assoc % :component-file (:id file-data))] + (update-container file-data + container + #(reduce (fn [container shape] + (ctn/update-shape container + (:id shape) + redirect-instance)) + % + shapes))))] + + (as-> file-data $ + (ctpl/update-page $ page-id add-main-instance-shapes) + (copy-component $) + (reduce redirect-instances $ instances)))) + + ; Absorb all used components into the local library. Position + ; the main instances in a grid in the library page. + add-component-grid + (fn [data used-components] + (let [position-seq (ctst/generate-shape-grid + (map #(cph/get-component-root (first %)) used-components) + start-pos + 50)] + (loop [data data + components-seq (seq used-components) + position-seq position-seq] + (let [used-component (first components-seq) + position (first position-seq)] + (if (nil? used-component) + data + (recur (absorb-component data used-component position) + (rest components-seq) + (rest position-seq)))))))] + + (add-component-grid file-data (sort-by #(:name (first %)) used-components)))))) ;; Debug helpers diff --git a/common/src/app/common/types/shape_tree.cljc b/common/src/app/common/types/shape_tree.cljc index c7294699a..b03054e24 100644 --- a/common/src/app/common/types/shape_tree.cljc +++ b/common/src/app/common/types/shape_tree.cljc @@ -326,7 +326,7 @@ (defn generate-shape-grid "Generate a sequence of positions that lays out the list of shapes in a grid of equal-sized rows and columns." - [shapes gap] + [shapes start-pos gap] (let [shapes-bounds (map gsh/bounding-box shapes) grid-size (mth/ceil (mth/sqrt (count shapes))) @@ -339,11 +339,12 @@ (let [counter (inc (:counter (meta position))) row (quot counter grid-size) column (mod counter grid-size) - new-pos (gpt/point (* column column-size) - (* row row-size))] + new-pos (gpt/add start-pos + (gpt/point (* column column-size) + (* row row-size)))] (with-meta new-pos {:counter counter})))] (iterate next-pos - (with-meta (gpt/point 0 0) + (with-meta start-pos {:counter 0})))) diff --git a/common/test/app/common/types/file_test.cljc b/common/test/app/common/types/file_test.cljc index 0049732ab..76764c69d 100644 --- a/common/test/app/common/types/file_test.cljc +++ b/common/test/app/common/types/file_test.cljc @@ -54,25 +54,23 @@ file #(ctf/absorb-assets % (:data library)))] - (println "\n===== library") - (ctf/dump-tree (:data library) - library-page-id - {} - true) + ;; (println "\n===== library") + ;; (ctf/dump-tree (:data library) + ;; library-page-id + ;; {} + ;; true) - (println "\n===== file") - (ctf/dump-tree (:data file) - file-page-id - {library-id {:id library-id - :name "Library 1" - :data library}} - true) + ;; (println "\n===== file") + ;; (ctf/dump-tree (:data file) + ;; file-page-id + ;; {library-id library} + ;; true) - (println "\n===== absorbed file") - (ctf/dump-tree (:data absorbed-file) - file-page-id - {} - true) + ;; (println "\n===== absorbed file") + ;; (ctf/dump-tree (:data absorbed-file) + ;; file-page-id + ;; {} + ;; true) (t/is (= library-id (:id library))) (t/is (= file-id (:id absorbed-file))))) diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index 237ce6029..880db210c 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -13,10 +13,10 @@ [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] [app.common.spec :as us] - [app.common.types.page :as csp] - [app.common.types.shape :as spec.shape] - [app.common.types.shape.interactions :as csi] - [app.common.types.shape-tree :as ctt] + [app.common.types.page :as ctp] + [app.common.types.shape :as cts] + [app.common.types.shape.interactions :as ctsi] + [app.common.types.shape-tree :as ctst] [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.edition :as dwe] @@ -28,14 +28,14 @@ [cljs.spec.alpha :as s] [potok.core :as ptk])) -(s/def ::shape-attrs ::spec.shape/shape-attrs) +(s/def ::shape-attrs ::cts/shape-attrs) (defn get-shape-layer-position [objects selected attrs] ;; Calculate the frame over which we're drawing (let [position @ms/mouse-position - frame-id (:frame-id attrs (cph/frame-id-by-position objects position)) + frame-id (:frame-id attrs (ctst/frame-id-by-position objects position)) shape (when-not (empty? selected) (cph/get-base-shape objects selected))] @@ -52,8 +52,8 @@ (defn make-new-shape [attrs objects selected] (let [default-attrs (if (= :frame (:type attrs)) - cp/default-frame-attrs - cp/default-shape-attrs) + cts/default-frame-attrs + cts/default-shape-attrs) selected-non-frames (into #{} (comp (map (d/getf objects)) @@ -117,7 +117,7 @@ to-move-shapes (into [] (map (d/getf objects)) - (reverse (cph/sort-z-index objects shapes))) + (reverse (ctst/sort-z-index objects shapes))) changes (when (d/not-empty? to-move-shapes) @@ -289,10 +289,10 @@ y (:y data (- vbc-y (/ height 2))) page-id (:current-page-id state) frame-id (-> (wsh/lookup-page-objects state page-id) - (cph/frame-id-by-position {:x frame-x :y frame-y})) - shape (-> (cp/make-minimal-shape type) + (ctst/frame-id-by-position {:x frame-x :y frame-y})) + shape (-> (cts/make-minimal-shape type) (merge data) (merge {:x x :y y}) (assoc :frame-id frame-id) - (cp/setup-rect-selrect))] + (cts/setup-rect-selrect))] (rx/of (add-shape shape)))))) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs index 3e19ec5f9..0e6debc1a 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs @@ -6,16 +6,17 @@ (ns app.main.ui.workspace.sidebar.options.menus.component (:require - [app.main.data.modal :as modal] - [app.main.data.workspace :as dw] - [app.main.data.workspace.libraries :as dwl] - [app.main.store :as st] - [app.main.ui.components.context-menu :refer [context-menu]] - [app.main.ui.context :as ctx] - [app.main.ui.icons :as i] - [app.util.dom :as dom] - [app.util.i18n :as i18n :refer [tr]] - [rumext.alpha :as mf])) + [app.common.pages.helpers :as cph] + [app.main.data.modal :as modal] + [app.main.data.workspace :as dw] + [app.main.data.workspace.libraries :as dwl] + [app.main.store :as st] + [app.main.ui.components.context-menu :refer [context-menu]] + [app.main.ui.context :as ctx] + [app.main.ui.icons :as i] + [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [tr]] + [rumext.alpha :as mf])) (def component-attrs [:component-id :component-file :shape-ref]) 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 139d37e65..200405061 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 5984cd46a..23424981c 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 cb186933f..c9f288168 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 674de1cc2..ea9ad35d6 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 000000000..d9378b1b3 --- /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 5dbe23865..85a15fed3 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 63a598504..266aac703 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 e9543453d..43c4a58cb 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 000000000..ae3e79452 --- /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 ff63bf14b..036b8fe34 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 000000000..5cadf9a7d --- /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 bbb9c58dd..d6728d368 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 76764c69d..bb297f8bf 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 70000e1e0..1bb134525 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 880db210c..e33522b54 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 27a9fe468..0cdd5193c 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 0e6debc1a..4f4ed1607 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 6e0142b3e..26cc294a6 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 50891acc1..03b8e1ca4 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 ea9ad35d6..5f462796c 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 85a15fed3..279b8e247 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 43c4a58cb..c6015374a 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 036b8fe34..0c1a15ef7 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 bb297f8bf..5c5f8527f 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 2217af1dd..6be291020 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 1bb134525..4103e8137 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 8e408c060..fc84f8950 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 05474e551..52958aee5 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 26cc294a6..b2754977b 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 03b8e1ca4..3652bed1c 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 c9f288168..d7cb9a863 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 266aac703..a0f63f0f1 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 c6015374a..fb51d9fb7 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 d6728d368..d6b684d25 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 4103e8137..41a29f64e 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 200405061..f47ed59a2 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 8407df47f..0b6db38e1 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 6be291020..8694a905d 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 41a29f64e..14a57309f 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 fc84f8950..e80065095 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 ccdacf86a..e4768e78e 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 0fd9431e0..95ae1c0dc 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 681b8ef47..03d9c9342 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 aa188b215..c8ab0c25c 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 e8a01c255..86ad9189f 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 8849ac3cc..8f436ea02 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 f47ed59a2..6916500a1 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 3652bed1c..671e6a636 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 cd9e94ee7..1b73deac2 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 d7cb9a863..c1ee1dbd6 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 6fb2b0872..cb4bd70ae 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 a0f63f0f1..d58c05e3a 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 fb51d9fb7..4a84cf67e 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 7de27ffae..89264a93d 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 5cadf9a7d..843b74a4e 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 d6b684d25..85d4b980d 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 5c5f8527f..908c2b3e1 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 4f0161563..98c35746a 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 a7e38e185..a93eaf2a0 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 93efdb0ba..ba61e6524 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 896c49390..bd3a77457 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 a1f4da573..488c5ec2b 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 8694a905d..3b05d5f91 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 14a57309f..195caf140 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 445f5001c..1d1e1cc36 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 e33522b54..726997269 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 30f0ebb59..236314cb8 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 000000000..1dcf78640 --- /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 7f2f191ac..8ec9e69fa 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 f4057a60a..d852322f9 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 6068c1799..18cc78118 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 0cdd5193c..82d001351 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 4f4ed1607..34e044ae9 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 6a6957c25..cbd5d7115 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 872e0f247..8e4f98182 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 cc28169be..1dc58811c 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 353504a1e..f0e9af722 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 a061ecc43..e66974d01 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 172fe1f44..2bd88884b 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 e000b6145..a6c55af0d 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 95ae1c0dc..838f35d31 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 c1ee1dbd6..7807ea3ee 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 d9378b1b3..7c1b7ba0a 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 4a84cf67e..d81361a02 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 ae3e79452..1e7dace0d 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 98c35746a..2c69b0eae 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 a93eaf2a0..a72475f79 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 ba61e6524..24f629343 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 95429a6e6..98543f82b 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 3b05d5f91..4ea4eab75 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 726997269..d6022f704 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 7903f591a..3091904e1 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 99678a6c3..251be9a69 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 d852322f9..de1ac3b5c 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 80245771e..df8e838fd 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 d23ffc789..1622eb3f4 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 000000000..d1fbe026a --- /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 2bd88884b..c4f33a3b7 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 a6c55af0d..72ed97ec2 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 e4768e78e..ff45fb6c6 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 c75f47e52..a08fe8a21 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 6916500a1..152612769 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 5f462796c..06c5c497b 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 cb4bd70ae..d2bbff58f 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 d58c05e3a..b6ea7b917 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 d81361a02..7653d9954 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 908c2b3e1..70f14a36a 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 4ea4eab75..d460cbf89 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 58f8d957d..998e55803 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 bd5521110..1427ebd8a 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 1de776e7c..a9e99252b 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 236314cb8..5239bdd39 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))))) +