From 5e585201d330d41358e0ff0037d466dad5b939db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 1 Sep 2020 15:09:57 +0200 Subject: [PATCH 1/7] :tada: Create reusable components --- common/app/common/pages.cljc | 29 ++++ common/app/common/pages_helpers.cljc | 2 +- common/app/common/pages_migrations.cljc | 1 - frontend/resources/images/icons/component.svg | 3 + frontend/resources/locales.json | 63 +++++---- .../styles/common/dependencies/colors.scss | 2 + .../styles/main/partials/sidebar-assets.scss | 15 ++ .../styles/main/partials/sidebar-layers.scss | 80 ++++++++--- frontend/src/app/main/data/workspace.cljs | 94 ++----------- .../app/main/data/workspace/libraries.cljs | 120 +++++++++++++++- .../app/main/data/workspace/selection.cljs | 80 +++++++++++ frontend/src/app/main/exports.cljs | 32 +++++ frontend/src/app/main/store.cljs | 2 +- frontend/src/app/main/ui/icons.cljs | 1 + .../app/main/ui/workspace/context_menu.cljs | 12 +- .../app/main/ui/workspace/sidebar/assets.cljs | 131 ++++++++++++++---- .../app/main/ui/workspace/sidebar/layers.cljs | 16 ++- 17 files changed, 515 insertions(+), 168 deletions(-) create mode 100644 frontend/resources/images/icons/component.svg diff --git a/common/app/common/pages.cljc b/common/app/common/pages.cljc index 3e93ec8313..643720eab4 100644 --- a/common/app/common/pages.cljc +++ b/common/app/common/pages.cljc @@ -356,6 +356,12 @@ (defmethod change-spec :del-media [_] (s/keys :req-un [::id])) +(defmethod change-spec :add-component [_] + (s/keys :req-un [::id ::name ::new-shapes])) + +(defmethod change-spec :del-component [_] + (s/keys :req-un [::id])) + (s/def ::change (s/multi-spec change-spec :type)) (s/def ::changes (s/coll-of ::change)) @@ -473,6 +479,18 @@ :points [] :segments []))) +(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 ([] (make-file-data (uuid/next))) ([id] @@ -745,6 +763,17 @@ [data {:keys [id]}] (update data :media dissoc id)) +(defmethod process-change :add-component + [data {:keys [id name new-shapes]}] + (assoc-in data [:components id] + {:id id + :name name + :objects (d/index-by :id new-shapes)})) + +(defmethod process-change :del-component + [data {:keys [id]}] + (d/dissoc-in data [:components id])) + (defmethod process-operation :set [shape op] (let [attr (:attr op) diff --git a/common/app/common/pages_helpers.cljc b/common/app/common/pages_helpers.cljc index 27cc1836a8..fd33cbd43c 100644 --- a/common/app/common/pages_helpers.cljc +++ b/common/app/common/pages_helpers.cljc @@ -15,7 +15,7 @@ (defn get-children "Retrieve all children ids recursively for a given object" [id objects] - (let [shapes (get-in objects [id :shapes])] + (let [shapes (vec (get-in objects [id :shapes]))] (if shapes (d/concat shapes (mapcat #(get-children % objects) shapes)) []))) diff --git a/common/app/common/pages_migrations.cljc b/common/app/common/pages_migrations.cljc index a9dd9b21b2..33171c9fb2 100644 --- a/common/app/common/pages_migrations.cljc +++ b/common/app/common/pages_migrations.cljc @@ -52,4 +52,3 @@ ;; (assoc obj :parent-id parent-id))) ;; objects))))) - diff --git a/frontend/resources/images/icons/component.svg b/frontend/resources/images/icons/component.svg new file mode 100644 index 0000000000..2042881f9b --- /dev/null +++ b/frontend/resources/images/icons/component.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/locales.json b/frontend/resources/locales.json index 635cc19c91..0e96a75837 100644 --- a/frontend/resources/locales.json +++ b/frontend/resources/locales.json @@ -288,7 +288,7 @@ } }, "dashboard.grid.add-shared" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:146", "src/app/main/ui/dashboard/grid.cljs:166" ], + "used-in" : [ "src/app/main/ui/dashboard/grid.cljs:166", "src/app/main/ui/workspace/header.cljs:146" ], "translations" : { "en" : "Add as Shared Library", "fr" : "", @@ -297,7 +297,7 @@ } }, "dashboard.grid.add-shared-accept" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:69", "src/app/main/ui/dashboard/grid.cljs:95" ], + "used-in" : [ "src/app/main/ui/dashboard/grid.cljs:95", "src/app/main/ui/workspace/header.cljs:69" ], "translations" : { "en" : "Add as Shared Library", "fr" : "", @@ -306,7 +306,7 @@ } }, "dashboard.grid.add-shared-hint" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:68", "src/app/main/ui/dashboard/grid.cljs:94" ], + "used-in" : [ "src/app/main/ui/dashboard/grid.cljs:94", "src/app/main/ui/workspace/header.cljs:68" ], "translations" : { "en" : "Once added as Shared Library, the assets of this file library will be available to be used among the rest of your files.", "fr" : "", @@ -315,7 +315,7 @@ } }, "dashboard.grid.add-shared-message" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:67", "src/app/main/ui/dashboard/grid.cljs:93" ], + "used-in" : [ "src/app/main/ui/dashboard/grid.cljs:93", "src/app/main/ui/workspace/header.cljs:67" ], "translations" : { "en" : "Add ā€œ%sā€ as Shared Library", "fr" : "", @@ -342,7 +342,7 @@ } }, "dashboard.grid.remove-shared" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:144", "src/app/main/ui/dashboard/grid.cljs:165" ], + "used-in" : [ "src/app/main/ui/dashboard/grid.cljs:165", "src/app/main/ui/workspace/header.cljs:144" ], "translations" : { "en" : "Remove as Shared Library", "fr" : "", @@ -351,7 +351,7 @@ } }, "dashboard.grid.remove-shared-accept" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:78", "src/app/main/ui/dashboard/grid.cljs:114" ], + "used-in" : [ "src/app/main/ui/dashboard/grid.cljs:114", "src/app/main/ui/workspace/header.cljs:78" ], "translations" : { "en" : "Remove as Shared Library", "fr" : "", @@ -360,7 +360,7 @@ } }, "dashboard.grid.remove-shared-hint" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:77", "src/app/main/ui/dashboard/grid.cljs:113" ], + "used-in" : [ "src/app/main/ui/dashboard/grid.cljs:113", "src/app/main/ui/workspace/header.cljs:77" ], "translations" : { "en" : "Once removed as Shared Library, the File Library of this file will stop being available to be used among the rest of your files.", "fr" : "", @@ -369,7 +369,7 @@ } }, "dashboard.grid.remove-shared-message" : { - "used-in" : [ "src/app/main/ui/workspace/header.cljs:76", "src/app/main/ui/dashboard/grid.cljs:112" ], + "used-in" : [ "src/app/main/ui/dashboard/grid.cljs:112", "src/app/main/ui/workspace/header.cljs:76" ], "translations" : { "en" : "Remove ā€œ%sā€ as Shared Library", "fr" : "", @@ -621,6 +621,7 @@ "unused" : true }, "ds.button.save" : { + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:66" ], "translations" : { "en" : "Save", "fr" : "Sauvegarder", @@ -774,7 +775,7 @@ } }, "errors.media-type-mismatch" : { - "used-in" : [ "src/app/main/data/media.cljs:62", "src/app/main/data/workspace/persistence.cljs:352" ], + "used-in" : [ "src/app/main/data/workspace/persistence.cljs:352", "src/app/main/data/media.cljs:62" ], "translations" : { "en" : "Seems that the contents of the image does not match the file extension.", "fr" : "", @@ -783,7 +784,7 @@ } }, "errors.media-type-not-allowed" : { - "used-in" : [ "src/app/main/data/media.cljs:59", "src/app/main/data/workspace/persistence.cljs:349" ], + "used-in" : [ "src/app/main/data/workspace/persistence.cljs:349", "src/app/main/data/media.cljs:59" ], "translations" : { "en" : "Seems that this is not a valid image.", "fr" : "", @@ -828,7 +829,7 @@ } }, "errors.unexpected-error" : { - "used-in" : [ "src/app/main/data/media.cljs:65", "src/app/main/ui/settings/change_email.cljs:51", "src/app/main/ui/auth/register.cljs:54", "src/app/main/ui/workspace/sidebar/options/exports.cljs:66" ], + "used-in" : [ "src/app/main/data/media.cljs:65", "src/app/main/ui/settings/change_email.cljs:51", "src/app/main/ui/workspace/sidebar/options/exports.cljs:66", "src/app/main/ui/auth/register.cljs:54" ], "translations" : { "en" : "An unexpected error occurred.", "fr" : "Une erreur inattendue c'est produite", @@ -873,7 +874,7 @@ } }, "media.loading" : { - "used-in" : [ "src/app/main/data/media.cljs:44", "src/app/main/data/workspace/persistence.cljs:334" ], + "used-in" : [ "src/app/main/data/workspace/persistence.cljs:334", "src/app/main/data/media.cljs:44" ], "translations" : { "en" : "Loading image...", "fr" : "Chargement de l'image...", @@ -882,6 +883,7 @@ } }, "modal.create-color.new-color" : { + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:59" ], "translations" : { "en" : "New Color", "fr" : "Nouvelle couleur", @@ -1458,7 +1460,7 @@ } }, "workspace.assets.assets" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:374" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:476" ], "translations" : { "en" : "Assets", "fr" : "", @@ -1467,7 +1469,7 @@ } }, "workspace.assets.box-filter-all" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:394" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:496" ], "translations" : { "en" : "All assets", "fr" : "", @@ -1476,7 +1478,7 @@ } }, "workspace.assets.box-filter-colors" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:396" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:498" ], "translations" : { "en" : "Colors", "fr" : "", @@ -1485,7 +1487,7 @@ } }, "workspace.assets.box-filter-graphics" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:395" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:497" ], "translations" : { "en" : "Graphics", "fr" : "", @@ -1494,7 +1496,7 @@ } }, "workspace.assets.colors" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:247" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:324" ], "translations" : { "en" : "Colors", "fr" : "", @@ -1502,8 +1504,17 @@ "es" : "Colores" } }, + "workspace.assets.components" : { + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:106" ], + "translations" : { + "en" : "Components", + "fr" : "", + "ru" : "", + "es" : "Componentes" + } + }, "workspace.assets.delete" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:125", "src/app/main/ui/workspace/sidebar/assets.cljs:224" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:125", "src/app/main/ui/workspace/sidebar/assets.cljs:210", "src/app/main/ui/workspace/sidebar/assets.cljs:304" ], "translations" : { "en" : "Delete", "fr" : "", @@ -1512,7 +1523,7 @@ } }, "workspace.assets.edit" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:223" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:303" ], "translations" : { "en" : "Edit", "fr" : "", @@ -1521,7 +1532,7 @@ } }, "workspace.assets.file-library" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:309" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:401" ], "translations" : { "en" : "File library", "fr" : "", @@ -1530,7 +1541,7 @@ } }, "workspace.assets.graphics" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:99" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:184" ], "translations" : { "en" : "Graphics", "fr" : "", @@ -1539,7 +1550,7 @@ } }, "workspace.assets.libraries" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:377" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:479" ], "translations" : { "en" : "Libraries", "fr" : "", @@ -1548,7 +1559,7 @@ } }, "workspace.assets.not-found" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:339" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:440" ], "translations" : { "en" : "No assets found", "fr" : "", @@ -1557,7 +1568,7 @@ } }, "workspace.assets.rename" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:222" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:302" ], "translations" : { "en" : "Rename", "fr" : "", @@ -1566,7 +1577,7 @@ } }, "workspace.assets.search" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:381" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:483" ], "translations" : { "en" : "Search assets", "fr" : "", @@ -1575,7 +1586,7 @@ } }, "workspace.assets.shared" : { - "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:311" ], + "used-in" : [ "src/app/main/ui/workspace/sidebar/assets.cljs:403" ], "translations" : { "en" : "SHARED", "fr" : "", diff --git a/frontend/resources/styles/common/dependencies/colors.scss b/frontend/resources/styles/common/dependencies/colors.scss index 57cc27f167..0e8a21688e 100644 --- a/frontend/resources/styles/common/dependencies/colors.scss +++ b/frontend/resources/styles/common/dependencies/colors.scss @@ -20,6 +20,8 @@ $color-warning: #FC8802; $color-danger: #E65244; $color-info: #59b9e2; $color-ocean: #4285f4; +$color-component: #76B0B8; +$color-component-highlight: #00E0FF; // Gray scale $color-gray-10: #E3E3E3; diff --git a/frontend/resources/styles/main/partials/sidebar-assets.scss b/frontend/resources/styles/main/partials/sidebar-assets.scss index 621db56e64..2324c5ade4 100644 --- a/frontend/resources/styles/main/partials/sidebar-assets.scss +++ b/frontend/resources/styles/main/partials/sidebar-assets.scss @@ -170,6 +170,21 @@ grid-auto-rows: 7vh; column-gap: 0.5rem; row-gap: 0.5rem; + + &.big { + grid-template-columns: 1fr 1fr; + grid-auto-rows: 10vh; + + .grid-cell { + background-color: transparent; + border: 1px solid $color-gray-40; + border-radius: 4px; + + & svg { + height: 10vh; + } + } + } } .grid-cell { diff --git a/frontend/resources/styles/main/partials/sidebar-layers.scss b/frontend/resources/styles/main/partials/sidebar-layers.scss index 6658ff4f2a..9a53caeb66 100644 --- a/frontend/resources/styles/main/partials/sidebar-layers.scss +++ b/frontend/resources/styles/main/partials/sidebar-layers.scss @@ -20,42 +20,42 @@ margin-right: 8px; width: 13px; } - + &.group { &.open { .toggle-content { flex-shrink: 0; - + svg { transform: rotate(270deg); } } } } - + &:hover { background-color: $color-primary; - + svg { fill: $color-gray-60 !important; } - + .element-icon, .element-actions { - + svg { fill: $color-gray-60; } } - + .element-actions > * { display: flex; } - + span { color: $color-gray-60; } - + .toggle-content { svg { fill: $color-gray-60; @@ -64,13 +64,12 @@ } &.selected { - + svg { fill: $color-primary; } - + .element-icon { - svg { fill: $color-primary; } @@ -79,10 +78,10 @@ span { color: $color-primary; } - + &:hover { background-color: $color-primary; - + .element-icon, .element-actions { svg { @@ -95,20 +94,55 @@ } } } - + &.drag-top { border-top: 40px solid $color-gray-60 !important; } - + &.drag-bottom { border-bottom: 40px solid $color-gray-60 !important; } - + &.drag-inside { border: 2px solid $color-primary !important; } } +.element-list li.component { + + .element-list-body { + .element-name { + color: $color-component; + } + + svg { + fill: $color-component; + } + + &.selected { + .element-name { + color: $color-component-highlight; + } + + svg { + fill: $color-component-highlight; + } + } + + &:hover { + background-color: $color-component-highlight; + + .element-name { + color: $color-gray-60; + } + + svg { + fill: $color-gray-60; + } + } + } +} + .element-icon { svg { fill: $color-gray-30; @@ -132,7 +166,7 @@ span.element-name { white-space: nowrap; width: 100%; } - + .element-actions { display: flex; flex-shrink: 0; @@ -149,13 +183,13 @@ span.element-name { > * { display: none; } - + .toggle-element, .block-element { left: 0; position: absolute; top: 0; - + &.selected { display: flex; @@ -177,17 +211,17 @@ span.element-name { .toggle-content { margin-left: auto; width: 12px; - + svg { fill: $color-gray-20; transform: rotate(90deg); width: 10px; } - + &.inverse { svg { transform: rotate(270deg); } } - + &:hover { svg { fill: $color-gray-60; diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index b70766f0d5..d16ab658c7 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -22,6 +22,7 @@ [app.config :as cfg] [app.main.constants :as c] [app.main.data.workspace.common :as dwc] + [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.notifications :as dwn] [app.main.data.workspace.persistence :as dwp] [app.main.data.workspace.selection :as dws] @@ -1266,70 +1267,19 @@ ;; GROUPS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn group-shape - [id frame-id selected selection-rect] - {:id id - :type :group - :name (name (gensym "Group-")) - :shapes [] - :frame-id frame-id - :x (:x selection-rect) - :y (:y selection-rect) - :width (:width selection-rect) - :height (:height selection-rect)}) - (def group-selected (ptk/reify ::group-selected ptk/WatchEvent (watch [_ state stream] - (let [id (uuid/next) - page-id (:current-page-id state) + (let [page-id (:current-page-id state) objects (dwc/lookup-page-objects state page-id) selected (get-in state [:workspace-local :selected]) - items (->> selected - (map #(get objects %)) - (filter #(not= :frame (:type %))) - (map #(assoc % ::index (cph/position-on-parent (:id %) objects))) - (sort-by ::index))] - - (when (not-empty items) - (let [selrect (geom/selection-rect items) - frame-id (-> items first :frame-id) - parent-id (-> items first :parent-id) - group (-> (group-shape id frame-id selected selrect) - (geom/setup selrect)) - - index (::index (first items)) - - rchanges [{:type :add-obj - :id id - :page-id page-id - :frame-id frame-id - :parent-id parent-id - :obj group - :index index} - {:type :mov-objects - :page-id page-id - :parent-id id - :shapes (->> items - (map :id) - (into #{}) - (vec))}] - - uchanges - (reduce (fn [res obj] - (conj res {:type :mov-objects - :page-id page-id - :parent-id (:parent-id obj) - :index (::index obj) - :shapes [(:id obj)]})) - [] - items) - - uchanges (conj uchanges {:type :del-obj :id id :page-id page-id})] - + shapes (dws/shapes-for-grouping objects selected)] + (when-not (empty? shapes) + (let [[group rchanges uchanges] + (dws/prepare-create-group page-id shapes "Group-" false)] (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}) - (dws/select-shapes (d/ordered-set id))))))))) + (dws/select-shapes (d/ordered-set (:id group)))))))))) (def ungroup-selected (ptk/reify ::ungroup-selected @@ -1342,34 +1292,11 @@ group (get objects group-id)] (when (and (= 1 (count selected)) (= (:type group) :group)) - (let [shapes (:shapes group) - parent-id (cph/get-parent group-id objects) - parent (get objects parent-id) - index-in-parent (->> (:shapes parent) - (map-indexed vector) - (filter #(#{group-id} (second %))) - (ffirst)) - rchanges [{:type :mov-objects - :page-id page-id - :parent-id parent-id - :shapes shapes - :index index-in-parent}] - uchanges [{:type :add-obj - :page-id page-id - :id group-id - :frame-id (:frame-id group) - :obj (assoc group :shapes [])} - {:type :mov-objects - :page-id page-id - :parent-id group-id - :shapes shapes} - {:type :mov-objects - :page-id page-id - :parent-id parent-id - :shapes [group-id] - :index index-in-parent}]] + (let [[rchanges uchanges] + (dws/prepare-remove-group page-id group objects)] (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Interactions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1512,6 +1439,7 @@ "+" #(st/emit! (increase-zoom nil)) "-" #(st/emit! (decrease-zoom nil)) "ctrl+g" #(st/emit! group-selected) + "ctrl+k" #(st/emit! dwl/add-component) "shift+g" #(st/emit! ungroup-selected) "shift+0" #(st/emit! reset-zoom) "shift+1" #(st/emit! zoom-to-fit-all) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 94636042dd..1ef240fa58 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -13,6 +13,7 @@ [app.common.spec :as us] [app.common.uuid :as uuid] [app.main.data.workspace.common :as dwc] + [app.main.data.workspace.selection :as dws] [app.common.pages :as cp] [app.main.repo :as rp] [app.main.store :as st] @@ -68,7 +69,7 @@ (rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true})))))) (defn delete-color - [{:keys [id] :as color}] + [{:keys [id] :as params}] (us/assert ::us/uuid id) (ptk/reify ::delete-color ptk/WatchEvent @@ -94,7 +95,7 @@ (defn delete-media - [{:keys [id] :as media}] + [{:keys [id] :as params}] (us/assert ::us/uuid id) (ptk/reify ::delete-media ptk/WatchEvent @@ -106,3 +107,118 @@ :object prev}] (rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true})))))) +(declare clone-shape) + +(def add-component + (ptk/reify ::add-component + ptk/WatchEvent + (watch [_ state stream] + (let [page-id (:current-page-id state) + objects (dwc/lookup-page-objects state page-id) + selected (get-in state [:workspace-local :selected]) + shapes (dws/shapes-for-grouping objects selected)] + (when-not (empty? shapes) + (let [;; If the selected shape is a group, we can use it. If not, + ;; we need to create a group before creating the component. + [group rchanges uchanges] + (if (and (= (count shapes) 1) + (= (:type (first shapes)) :group)) + [(first shapes) [] []] + (dws/prepare-create-group page-id shapes "Component-" true)) + + [new-shape new-shapes updated-shapes] + (clone-shape group nil objects) + + rchanges (conj rchanges + {:type :add-component + :id (:id new-shape) + :name (:name new-shape) + :new-shapes new-shapes}) + + rchanges (into rchanges + (map (fn [updated-shape] + {:type :mod-obj + :page-id page-id + :id (:id updated-shape) + :operations [{:type :set + :attr :component-id + :val (:component-id updated-shape)}]}) + updated-shapes)) + + uchanges (conj uchanges + {:type :del-component + :id (:id new-shape)}) + + uchanges (into uchanges + (map (fn [updated-shape] + {:type :mod-obj + :page-id page-id + :id (:id updated-shape) + :operations [{:type :set + :attr :component-id + :val nil}]}) + updated-shapes))] + + (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}) + (dws/select-shapes (d/ordered-set (:id group)))))))))) + +(defn- clone-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 parent-id objects] + (let [new-id (uuid/next)] + (if (nil? (:shapes shape)) + + ; TODO: unify this case with the empty child-ids case. + (let [new-shape (assoc shape + :id new-id + :parent-id parent-id + :frame-id nil)] + [new-shape + [new-shape] + [(assoc shape :component-id (:id new-shape))]]) + + (loop [child-ids (seq (:shapes shape)) + new-children [] + updated-children []] + + (if (empty? child-ids) + (let [new-shape (assoc shape + :id new-id + :parent-id parent-id + :frame-id nil + :shapes (map :id new-children))] + [new-shape + (conj new-children new-shape) + (conj updated-children + (assoc shape :component-id (:id new-shape)))]) + + (let [child-id (first child-ids) + child (get objects child-id) + + [new-child new-child-shapes updated-child-shapes] + (clone-shape child new-id objects)] + + (recur + (next child-ids) + (into new-children new-child-shapes) + (into updated-children updated-child-shapes)))))))) + +(defn delete-component + [{:keys [id] :as params}] + (ptk/reify ::delete-component + ptk/WatchEvent + (watch [_ state stream] + (let [component (get-in state [:workspace-data :components id]) + + rchanges [{:type :del-component + :id id}] + + uchanges [{:type :add-component + :id id + :name (:name component) + :new-shapes (:objects component)}]] + + (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))) + diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index 77e0ad2dd8..c60fed1b70 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -183,6 +183,86 @@ (rx/of deselect-all (select-shape (:id selected)))))))) +;; --- Group shapes + +(defn shapes-for-grouping + [objects selected] + (->> selected + (map #(get objects %)) + (filter #(not= :frame (:type %))) + (map #(assoc % ::index (cph/position-on-parent (:id %) objects))) + (sort-by ::index))) + +(defn- make-group + [shapes prefix keep-name] + (let [selrect (geom/selection-rect shapes) + frame-id (-> shapes first :frame-id) + parent-id (-> shapes first :parent-id) + group-name (if (and keep-name (= (count shapes) 1)) + (:name (first shapes)) + (name (gensym prefix)))] + (-> (cp/make-minimal-group frame-id selrect group-name) + (geom/setup selrect) + (assoc :shapes (map :id shapes))))) + +(defn prepare-create-group + [page-id shapes prefix keep-name] + (let [group (make-group shapes prefix keep-name) + rchanges [{:type :add-obj + :id (:id group) + :page-id page-id + :frame-id (:frame-id (first shapes)) + :parent-id (:parent-id (first shapes)) + :obj group + :index (::index (first shapes))} + {:type :mov-objects + :page-id page-id + :parent-id (:id group) + :shapes (map :id shapes)}] + + uchanges (conj + (map (fn [obj] {:type :mov-objects + :page-id page-id + :parent-id (:parent-id obj) + :index (::index obj) + :shapes [(:id obj)]}) + shapes) + {:type :del-obj + :id (:id group) + :page-id page-id})] + [group rchanges uchanges])) + +(defn prepare-remove-group + [page-id group objects] + (let [shapes (:shapes group) + parent-id (cph/get-parent (:id group) objects) + parent (get objects parent-id) + index-in-parent (->> (:shapes parent) + (map-indexed vector) + (filter #(#{(:id group)} (second %))) + (ffirst)) + rchanges [{:type :mov-objects + :page-id page-id + :parent-id parent-id + :shapes shapes + :index index-in-parent}] + uchanges [{:type :add-obj + :page-id page-id + :id (:id group) + :frame-id (:frame-id group) + :obj (assoc group :shapes [])} + {:type :mov-objects + :page-id page-id + :parent-id (:id group) + :shapes shapes} + {:type :mov-objects + :page-id page-id + :parent-id parent-id + :shapes [(:id group)] + :index index-in-parent}]] + [rchanges uchanges])) + + ;; --- Duplicate Shapes (declare prepare-duplicate-change) (declare prepare-duplicate-frame-change) diff --git a/frontend/src/app/main/exports.cljs b/frontend/src/app/main/exports.cljs index a72b72b2d3..5cfbc1243f 100644 --- a/frontend/src/app/main/exports.cljs +++ b/frontend/src/app/main/exports.cljs @@ -156,3 +156,35 @@ :xmlnsXlink "http://www.w3.org/1999/xlink" :xmlns "http://www.w3.org/2000/svg"} [:& wrapper {:shape frame :view-box vbox}]])) + +;; TODO: unify with frame-svg? +(mf/defc component-svg + {::mf/wrap [mf/memo]} + [{:keys [objects group zoom] :or {zoom 1} :as props}] + (let [modifier (-> (gpt/point (:x group) (:y group)) + (gpt/negate) + (gmt/translate-matrix)) + + group-id (:id group) + + modifier-ids (concat [group-id] (cph/get-children group-id objects)) + update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier) + objects (reduce update-fn objects modifier-ids) + group (assoc-in group [:modifiers :displacement] modifier) + + width (* (:width group) zoom) + height (* (:height group) zoom) + vbox (str "0 0 " (:width group 0) + " " (:height group 0)) + wrapper (mf/use-memo + (mf/deps objects) + #(group-wrapper-factory objects))] + + [:svg {:view-box vbox + :width width + :height height + :version "1.1" + :xmlnsXlink "http://www.w3.org/1999/xlink" + :xmlns "http://www.w3.org/2000/svg"} + [:& wrapper {:shape group :view-box vbox}]])) + diff --git a/frontend/src/app/main/store.cljs b/frontend/src/app/main/store.cljs index b65165f3be..cf645ca837 100644 --- a/frontend/src/app/main/store.cljs +++ b/frontend/src/app/main/store.cljs @@ -67,4 +67,4 @@ (defn ^:export dump-objects [] (let [page-id (get @state :current-page-id)] - (logjs "state" (get-in @state [:workspace-data page-id :objects])))) + (logjs "state" (get-in @state [:workspace-data :pages-index page-id :objects])))) diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs index 5368f6cae5..157bcddb2f 100644 --- a/frontend/src/app/main/ui/icons.cljs +++ b/frontend/src/app/main/ui/icons.cljs @@ -31,6 +31,7 @@ (def chat (icon-xref :chat)) (def circle (icon-xref :circle)) (def close (icon-xref :close)) +(def component (icon-xref :component)) (def copy (icon-xref :copy)) (def curve (icon-xref :curve)) (def download (icon-xref :download)) diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index 4488023f80..5277e32b32 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -20,6 +20,7 @@ [app.main.ui.icons :as i] [app.util.dom :as dom] [app.main.data.workspace :as dw] + [app.main.data.workspace.libraries :as dwl] [app.main.ui.hooks :refer [use-rxsub]] [app.main.ui.components.dropdown :refer [dropdown]])) @@ -59,7 +60,8 @@ do-lock-shape #(st/emit! (dw/update-shape-flags id {:blocked true})) do-unlock-shape #(st/emit! (dw/update-shape-flags id {:blocked false})) do-create-group #(st/emit! dw/group-selected) - do-remove-group #(st/emit! dw/ungroup-selected)] + do-remove-group #(st/emit! dw/ungroup-selected) + do-add-component #(st/emit! dwl/add-component)] [:* [:& menu-entry {:title "Copy" :shortcut "Ctrl + c" @@ -101,13 +103,17 @@ [:& menu-entry {:title "Hide" :on-click do-hide-shape}]) - - (if (:blocked shape) [:& menu-entry {:title "Unlock" :on-click do-unlock-shape}] [:& menu-entry {:title "Lock" :on-click do-lock-shape}]) + + [:& menu-separator] + [:& menu-entry {:title "Create component" + :shortcut "Ctrl + K" + :on-click do-add-component}] + [:& menu-separator] [:& menu-entry {:title "Delete" :shortcut "Supr" diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index 7de10fcf53..b3d6484a04 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -14,6 +14,7 @@ [app.common.geom.shapes :as geom] [app.common.media :as cm] [app.common.pages :as cp] + [app.common.pages-helpers :as cph] [app.common.uuid :as uuid] [app.config :as cfg] [app.main.data.workspace :as dw] @@ -21,6 +22,7 @@ [app.main.data.colors :as dc] [app.main.refs :as refs] [app.main.store :as st] + [app.main.exports :as exports] [app.main.ui.components.context-menu :refer [context-menu]] [app.main.ui.components.file-uploader :refer [file-uploader]] [app.main.ui.components.tab-container :refer [tab-container tab-element]] @@ -38,6 +40,62 @@ [okulary.core :as l] [rumext.alpha :as mf])) +(mf/defc components-box + [{:keys [file-id local? components] :as props}] + (let [state (mf/use-state {:menu-open false + :top nil + :left nil + :component-id nil}) + on-delete + (mf/use-callback + (mf/deps state) + (fn [] + (let [params {:id (:component-id @state)}] + (st/emit! (dwl/delete-component params))))) + + on-context-menu + (mf/use-callback + (fn [component-id] + (fn [event] + (when local? + (let [pos (dom/get-client-position event) + top (:y pos) + left (- (:x pos) 20)] + (dom/prevent-default event) + (swap! state assoc :menu-open true + :top top + :left left + :component-id component-id)))))) + + on-drag-start + (mf/use-callback + (fn [path event] + (dnd/set-data! event "text/uri-list" (cfg/resolve-media-path path)) + (dnd/set-allowed-effect! event "move")))] + + [:div.asset-group + [:div.group-title + (tr "workspace.assets.components") + [:span (str "\u00A0(") (count components) ")"]] ;; Unicode 00A0 is non-breaking space + [:div.group-grid.big + (for [component components] + [:div.grid-cell {:key (:id component) + :draggable true + :on-context-menu (on-context-menu (:id component)) + :on-drag-start (partial on-drag-start (:path component))} + [:& exports/component-svg {:group (get-in component [:objects (:id component)]) + :objects (:objects component)}] + [:div.cell-name (:name component)]]) + + (when local? + [:& context-menu + {:selectable false + :show (:menu-open @state) + :on-close #(swap! state assoc :menu-open false) + :top (:top @state) + :left (:left @state) + :options [[(tr "workspace.assets.delete") on-delete]]}])]])) + (mf/defc graphics-box [{:keys [file-id local? objects open? on-open on-close] :as props}] (let [input-ref (mf/use-ref nil) @@ -126,7 +184,6 @@ :left (:left @state) :options [[(tr "workspace.assets.delete") on-delete]]}])])])) - (mf/defc color-item [{:keys [color local? locale file-id] :as props}] (let [rename? (= (:color-for-rename @refs/workspace-local) (:id color)) @@ -287,32 +344,45 @@ (vals (get-in state [:workspace-libraries id :data :media]))))) st/state =)) +(defn file-components-ref + [id] + (l/derived (fn [state] + (let [wfile (:workspace-file state)] + (if (= (:id wfile) id) + (vals (get-in wfile [:data :components])) + (vals (get-in state [:workspace-libraries id :data :components]))))) + st/state =)) + (defn apply-filters [coll filters] - (filter (fn [item] - (or (matches-search (:name item "!$!") (:term filters)) - (matches-search (:value item "!$!") (:term filters)))) - coll)) + (->> coll + (filter (fn [item] + (or (matches-search (:name item "!$!") (:term filters)) + (matches-search (:value item "!$!") (:term filters))))) + (sort-by #(str/lower (:name %))))) (mf/defc file-library [{:keys [file local? open? filters locale] :as props}] - (let [open? (mf/use-state open?) - shared? (:is-shared file) - router (mf/deref refs/router) - toggle-open #(swap! open? not) + (let [open? (mf/use-state open?) + shared? (:is-shared file) + router (mf/deref refs/router) + toggle-open #(swap! open? not) toggles (mf/use-state #{:graphics :colors}) - url (rt/resolve router :workspace - {:project-id (:project-id file) - :file-id (:id file)} - {:page-id (get-in file [:data :pages 0])}) + url (rt/resolve router :workspace + {:project-id (:project-id file) + :file-id (:id file)} + {:page-id (get-in file [:data :pages 0])}) - colors-ref (mf/use-memo (mf/deps (:id file)) #(file-colors-ref (:id file))) - colors (apply-filters (mf/deref colors-ref) filters) + colors-ref (mf/use-memo (mf/deps (:id file)) #(file-colors-ref (:id file))) + colors (apply-filters (mf/deref colors-ref) filters) - media-ref (mf/use-memo (mf/deps (:id file)) #(file-media-ref (:id file))) - media (apply-filters (mf/deref media-ref) filters)] + media-ref (mf/use-memo (mf/deps (:id file)) #(file-media-ref (:id file))) + media (apply-filters (mf/deref media-ref) filters) + + components-ref (mf/use-memo (mf/deps (:id file)) #(file-components-ref (:id file))) + components (apply-filters (mf/deref components-ref) filters)] [:div.tool-window [:div.tool-window-bar @@ -332,15 +402,23 @@ [:a {:href (str "#" url) :target "_blank"} i/chain]]])] (when @open? - (let [show-graphics? (and (or (= (:box filters) :all) - (= (:box filters) :graphics)) - (or (> (count media) 0) - (str/empty? (:term filters)))) - show-colors? (and (or (= (:box filters) :all) - (= (:box filters) :colors)) - (or (> (count colors) 0) - (str/empty? (:term filters))))] + (let [show-components? (and (or (= (:box filters) :all) + (= (:box filters) :components)) + (or (> (count components) 0) + (str/empty? (:term filters)))) + show-graphics? (and (or (= (:box filters) :all) + (= (:box filters) :graphics)) + (or (> (count media) 0) + (str/empty? (:term filters)))) + show-colors? (and (or (= (:box filters) :all) + (= (:box filters) :colors)) + (or (> (count colors) 0) + (str/empty? (:term filters))))] [:div.tool-window-content + (when show-components? + [:& components-box {:file-id (:id file) + :local? local? + :components components}]) (when show-graphics? [:& graphics-box {:file-id (:id file) :local? local? @@ -357,10 +435,11 @@ :on-open #(swap! toggles conj :colors) :on-close #(swap! toggles disj :colors)}]) - (when (and (not show-graphics?) (not show-colors?)) + (when (and (not show-components?) (not show-graphics?) (not show-colors?)) [:div.asset-group [:div.group-title (t locale "workspace.assets.not-found")]])]))])) + (mf/defc assets-toolbox [{:keys [team-id file] :as props}] (let [libraries (mf/deref refs/workspace-libraries) diff --git a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs index c5da4ad8d2..77fcd0e249 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs @@ -43,7 +43,9 @@ :rect i/box :curve i/curve :text i/text - :group i/folder + :group (if (nil? (:component-id shape)) + i/folder + i/component) nil)) ;; --- Layer Name @@ -186,6 +188,7 @@ [:li {:on-context-menu on-context-menu :ref dref :class (dom/classnames + :component (not (nil? (:component-id item))) :dnd-over (= (:over dprops) :center) :dnd-over-top (= (:over dprops) :top) :dnd-over-bot (= (:over dprops) :bot) @@ -285,7 +288,16 @@ (defn- strip-objects [objects] - (let [strip-data #(select-keys % [:id :name :blocked :hidden :shapes :type :content :parent-id :metadata])] + (let [strip-data #(select-keys % [:id + :name + :blocked + :hidden + :shapes + :type + :content + :parent-id + :component-id + :metadata])] (persistent! (reduce-kv (fn [res id obj] (assoc! res id (strip-data obj))) From 917643489fed89ea69cf74ef227814acee15abda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 8 Sep 2020 13:11:18 +0200 Subject: [PATCH 2/7] :tada: Synchronize page with a library --- common/app/common/pages.cljc | 78 +++++++++++++++++++ common/app/common/pages_helpers.cljc | 23 ++++++ .../app/main/data/workspace/libraries.cljs | 41 +++++++--- 3 files changed, 131 insertions(+), 11 deletions(-) diff --git a/common/app/common/pages.cljc b/common/app/common/pages.cljc index 643720eab4..42acab8997 100644 --- a/common/app/common/pages.cljc +++ b/common/app/common/pages.cljc @@ -362,6 +362,9 @@ (defmethod change-spec :del-component [_] (s/keys :req-un [::id])) +(defmethod change-spec :sync-library [_] + (s/keys :req-un [::id])) + (s/def ::change (s/multi-spec change-spec :type)) (s/def ::changes (s/coll-of ::change)) @@ -774,6 +777,81 @@ [data {:keys [id]}] (d/dissoc-in data [:components id])) +(declare sync-page) +(declare sync-shape-and-children) +(declare sync-shape) + +(defmethod process-change :sync-library + [data id] + (cph/walk-pages (sync-page (:components data)) data)) + +(defn- sync-page + [components] + (fn [page-id page] + (let [linked-shapes + (cph/select-objects #(some? (:component-id %)) page) + + updated-shapes + (reduce + (fn [updated-shapes linked-shape] + (let [component-id (:component-id linked-shape) + component (get components component-id)] + (into updated-shapes + (sync-shape-and-children linked-shape + component + (:objects page))))) + [] + linked-shapes)] + + (cph/update-object-list page updated-shapes)))) + +(defn- sync-shape-and-children + [linked-shape component objects] + (let [children (cph/get-children-objects (:id linked-shape) objects) + all-shapes (conj children linked-shape)] + (if (nil? component) + (map #(dissoc % :component-id :shape-ref) all-shapes) + (map #(sync-shape % (:objects component)) all-shapes)))) + +(defn- sync-shape + [shape component-objs] + (let [component-shape (get component-objs (:shape-ref shape))] + (if (nil? component-shape) + (assoc shape :shape-ref nil) + (-> shape + (d/update-when :content :content component-shape) + (d/update-when :fill-color :fill-color component-shape) + (d/update-when :fill-color-ref-file :fill-color-ref-file component-shape) + (d/update-when :fill-color-ref-id :fill-color-ref-id component-shape) + (d/update-when :fill-opacity :fill-opacity component-shape) + (d/update-when :font-family :font-family component-shape) + (d/update-when :font-size :font-size component-shape) + (d/update-when :font-style :font-style component-shape) + (d/update-when :font-weight :font-weight component-shape) + (d/update-when :letter-spacing :letter-spacing component-shape) + (d/update-when :line-height :line-height component-shape) + (d/update-when :proportion :proportion component-shape) + (d/update-when :rx :rx component-shape) + (d/update-when :ry :ry component-shape) + (d/update-when :cx :cx component-shape) + (d/update-when :cy :cy component-shape) + (d/update-when :x :x component-shape) + (d/update-when :y :y component-shape) + (d/update-when :exports :exports component-shape) + (d/update-when :stroke-color :stroke-color component-shape) + (d/update-when :stroke-color-ref-file :stroke-color-ref-file component-shape) + (d/update-when :stroke-color-ref-id :stroke-color-ref-id component-shape) + (d/update-when :stroke-opacity :stroke-opacity component-shape) + (d/update-when :stroke-style :stroke-style component-shape) + (d/update-when :stroke-width :stroke-width component-shape) + (d/update-when :stroke-alignment :stroke-alignment component-shape) + (d/update-when :text-align :text-align component-shape) + (d/update-when :width :width component-shape) + (d/update-when :height :height component-shape) + (d/update-when :interactions :interactions component-shape) + (d/update-when :selrect :selrect component-shape) + (d/update-when :points :points component-shape))))) + (defmethod process-operation :set [shape op] (let [attr (:attr op) diff --git a/common/app/common/pages_helpers.cljc b/common/app/common/pages_helpers.cljc index fd33cbd43c..57fbd955a2 100644 --- a/common/app/common/pages_helpers.cljc +++ b/common/app/common/pages_helpers.cljc @@ -12,6 +12,24 @@ [app.common.data :as d] [app.common.uuid :as uuid])) +(defn walk-pages + "Go through all pages of a file and apply a function to each one" + ;; The function receives two parameters (page-id and page), and + ;; returns the updated page. + [f data] + (update data :pages-index #(d/mapm f %))) + +(defn select-objects + "Get a list of all objects in a page that satisfy a condition" + [f page] + (filter f (vals (get page :objects)))) + +(defn update-object-list + "Update multiple objects in a page at once" + [page objects-list] + (update page :objects + #(into % (d/index-by :id objects-list)))) + (defn get-children "Retrieve all children ids recursively for a given object" [id objects] @@ -20,6 +38,11 @@ (d/concat shapes (mapcat #(get-children % objects) shapes)) []))) +(defn get-children-objects + "Retrieve all children objects recursively for a given object" + [id objects] + (map #(get objects %) (get-children id objects))) + (defn is-shape-grouped "Checks if a shape is inside a group" [shape-id objects] diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 1ef240fa58..16ecdf42a3 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -142,7 +142,10 @@ :id (:id updated-shape) :operations [{:type :set :attr :component-id - :val (:component-id updated-shape)}]}) + :val (:component-id updated-shape)} + {:type :set + :attr :shape-ref + :val (:shape-ref updated-shape)}]}) updated-shapes)) uchanges (conj uchanges @@ -156,6 +159,9 @@ :id (:id updated-shape) :operations [{:type :set :attr :component-id + :val nil} + {:type :set + :attr :shape-ref :val nil}]}) updated-shapes))] @@ -174,10 +180,15 @@ (let [new-shape (assoc shape :id new-id :parent-id parent-id - :frame-id nil)] - [new-shape - [new-shape] - [(assoc shape :component-id (:id new-shape))]]) + :frame-id nil) + + new-shapes [new-shape] + + updated-shapes [(cond-> shape + true (assoc :shape-ref (:id new-shape)) + (nil? parent-id) (assoc :component-id (:id new-shape)))]] + + [new-shape new-shapes updated-shapes]) (loop [child-ids (seq (:shapes shape)) new-children [] @@ -188,11 +199,17 @@ :id new-id :parent-id parent-id :frame-id nil - :shapes (map :id new-children))] - [new-shape - (conj new-children new-shape) - (conj updated-children - (assoc shape :component-id (:id new-shape)))]) + :shapes (map :id new-children)) + + new-shapes (conj new-children new-shape) + + updated-shapes + (conj updated-children + (cond-> shape + true (assoc :shape-ref (:id new-shape)) + (nil? parent-id) (assoc :component-id (:id new-shape))))] + + [new-shape new-shapes updated-shapes]) (let [child-id (first child-ids) child (get objects child-id) @@ -213,7 +230,9 @@ (let [component (get-in state [:workspace-data :components id]) rchanges [{:type :del-component - :id id}] + :id id} + {:type :sync-library + :id (get-in state [:workspace-file :id])}] uchanges [{:type :add-component :id id From f837bad894e8fa350191a7b3fee77db714bd1109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Wed, 9 Sep 2020 11:19:29 +0200 Subject: [PATCH 3/7] :tada: Update master component --- common/app/common/data.cljc | 14 ++ common/app/common/pages.cljc | 116 ++++------ common/app/common/pages_helpers.cljc | 81 +++++++ frontend/src/app/main/data/workspace.cljs | 8 +- .../src/app/main/data/workspace/common.cljs | 6 + .../app/main/data/workspace/libraries.cljs | 206 ++++++++++++++---- .../app/main/data/workspace/selection.cljs | 39 +--- .../app/main/ui/workspace/context_menu.cljs | 20 +- .../app/main/ui/workspace/sidebar/assets.cljs | 6 +- .../src/app/main/ui/workspace/viewport.cljs | 7 + 10 files changed, 337 insertions(+), 166 deletions(-) diff --git a/common/app/common/data.cljc b/common/app/common/data.cljc index cef92e02e3..fe84e3f832 100644 --- a/common/app/common/data.cljc +++ b/common/app/common/data.cljc @@ -182,6 +182,20 @@ (assoc m key (apply f found args)) m))) +(defn assoc-in-when + [m key-seq v] + (let [found (get-in m key-seq sentinel)] + (if-not (identical? sentinel found) + (assoc-in m key-seq v) + m))) + +(defn assoc-when + [m key v] + (let [found (get m key sentinel)] + (if-not (identical? sentinel found) + (assoc m key v) + m))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Data Parsing / Conversion ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/common/app/common/pages.cljc b/common/app/common/pages.cljc index 42acab8997..fba45543bb 100644 --- a/common/app/common/pages.cljc +++ b/common/app/common/pages.cljc @@ -362,8 +362,8 @@ (defmethod change-spec :del-component [_] (s/keys :req-un [::id])) -(defmethod change-spec :sync-library [_] - (s/keys :req-un [::id])) +(defmethod change-spec :update-component [_] + (s/keys :req-un [::id ::shapes])) (s/def ::change (s/multi-spec change-spec :type)) (s/def ::changes (s/coll-of ::change)) @@ -777,80 +777,50 @@ [data {:keys [id]}] (d/dissoc-in data [:components id])) -(declare sync-page) -(declare sync-shape-and-children) -(declare sync-shape) +(declare sync-component-shape) -(defmethod process-change :sync-library - [data id] - (cph/walk-pages (sync-page (:components data)) data)) +(defmethod process-change :update-component + [data {:keys [id shapes]}] + (let [sync-component + (fn [component] + (update component :objects + #(d/mapm (partial sync-component-shape shapes) %)))] -(defn- sync-page - [components] - (fn [page-id page] - (let [linked-shapes - (cph/select-objects #(some? (:component-id %)) page) + (update-in data [:components id] sync-component))) - updated-shapes - (reduce - (fn [updated-shapes linked-shape] - (let [component-id (:component-id linked-shape) - component (get components component-id)] - (into updated-shapes - (sync-shape-and-children linked-shape - component - (:objects page))))) - [] - linked-shapes)] - - (cph/update-object-list page updated-shapes)))) - -(defn- sync-shape-and-children - [linked-shape component objects] - (let [children (cph/get-children-objects (:id linked-shape) objects) - all-shapes (conj children linked-shape)] - (if (nil? component) - (map #(dissoc % :component-id :shape-ref) all-shapes) - (map #(sync-shape % (:objects component)) all-shapes)))) - -(defn- sync-shape - [shape component-objs] - (let [component-shape (get component-objs (:shape-ref shape))] - (if (nil? component-shape) - (assoc shape :shape-ref nil) - (-> shape - (d/update-when :content :content component-shape) - (d/update-when :fill-color :fill-color component-shape) - (d/update-when :fill-color-ref-file :fill-color-ref-file component-shape) - (d/update-when :fill-color-ref-id :fill-color-ref-id component-shape) - (d/update-when :fill-opacity :fill-opacity component-shape) - (d/update-when :font-family :font-family component-shape) - (d/update-when :font-size :font-size component-shape) - (d/update-when :font-style :font-style component-shape) - (d/update-when :font-weight :font-weight component-shape) - (d/update-when :letter-spacing :letter-spacing component-shape) - (d/update-when :line-height :line-height component-shape) - (d/update-when :proportion :proportion component-shape) - (d/update-when :rx :rx component-shape) - (d/update-when :ry :ry component-shape) - (d/update-when :cx :cx component-shape) - (d/update-when :cy :cy component-shape) - (d/update-when :x :x component-shape) - (d/update-when :y :y component-shape) - (d/update-when :exports :exports component-shape) - (d/update-when :stroke-color :stroke-color component-shape) - (d/update-when :stroke-color-ref-file :stroke-color-ref-file component-shape) - (d/update-when :stroke-color-ref-id :stroke-color-ref-id component-shape) - (d/update-when :stroke-opacity :stroke-opacity component-shape) - (d/update-when :stroke-style :stroke-style component-shape) - (d/update-when :stroke-width :stroke-width component-shape) - (d/update-when :stroke-alignment :stroke-alignment component-shape) - (d/update-when :text-align :text-align component-shape) - (d/update-when :width :width component-shape) - (d/update-when :height :height component-shape) - (d/update-when :interactions :interactions component-shape) - (d/update-when :selrect :selrect component-shape) - (d/update-when :points :points component-shape))))) +(defn- sync-component-shape + [new-shapes _ component-shape] + (let [shape (d/seek #(= (:shape-ref %) (:id component-shape)) new-shapes)] + (if (nil? shape) + component-shape + (-> component-shape + (d/assoc-when :content (:content shape)) + (d/assoc-when :fill-color (:fill-color shape)) + (d/assoc-when :fill-color-ref-file (:fill-color-ref-file shape)) + (d/assoc-when :fill-color-ref-id (:fill-color-ref-id shape)) + (d/assoc-when :fill-opacity (:fill-opacity shape)) + (d/assoc-when :font-family (:font-family shape)) + (d/assoc-when :font-size (:font-size shape)) + (d/assoc-when :font-style (:font-style shape)) + (d/assoc-when :font-weight (:font-weight shape)) + (d/assoc-when :letter-spacing (:letter-spacing shape)) + (d/assoc-when :line-height (:line-height shape)) + (d/assoc-when :proportion (:proportion shape)) + (d/assoc-when :rx (:rx shape)) + (d/assoc-when :ry (:ry shape)) + (d/assoc-when :stroke-color (:stroke-color shape)) + (d/assoc-when :stroke-color-ref-file (:stroke-color-ref-file shape)) + (d/assoc-when :stroke-color-ref-id (:stroke-color-ref-id shape)) + (d/assoc-when :stroke-opacity (:stroke-opacity shape)) + (d/assoc-when :stroke-style (:stroke-style shape)) + (d/assoc-when :stroke-width (:stroke-width shape)) + (d/assoc-when :stroke-alignment (:stroke-alignment shape)) + (d/assoc-when :text-align (:text-align shape)) + (d/assoc-when :width (:width shape)) + (d/assoc-when :height (:height shape)) + (d/assoc-when :interactions (:interactions shape)) + (d/assoc-when :selrect (:selrect shape)) + (d/assoc-when :points (:points shape)))))) (defmethod process-operation :set [shape op] diff --git a/common/app/common/pages_helpers.cljc b/common/app/common/pages_helpers.cljc index 57fbd955a2..1754665ffc 100644 --- a/common/app/common/pages_helpers.cljc +++ b/common/app/common/pages_helpers.cljc @@ -30,6 +30,16 @@ (update page :objects #(into % (d/index-by :id objects-list)))) +(defn get-root-component + "Get the root shape linked to the component for this shape, if any" + [id objects] + (let [obj (get objects id)] + (if-let [component-id (:component-id obj)] + id + (if-let [parent-id (:parent-id obj)] + (get-root-component parent-id obj) + nil)))) + (defn get-children "Retrieve all children ids recursively for a given object" [id objects] @@ -43,6 +53,26 @@ [id objects] (map #(get objects %) (get-children id objects))) +(defn get-object-with-children + "Retrieve a list with an object and all of its children" + [id objects] + (map #(get objects %) (concat [id] (get-children id objects)))) + +(defn walk-children + "Go through an object and all the children tree, and apply a + function to each one. Return the list of changed objects." + [id f objects] + (let [obj (get objects id)] + (if (nil? (:shapes obj)) + [(apply f obj)] + (loop [children (map #(get objects %) (:shapes obj)) + updated-children []] + (if (empty? children) + updated-children + (let [child (first children)] + (recur (rest children) + (concat [(apply f child)] updated-children)))))))) + (defn is-shape-grouped "Checks if a shape is inside a group" [shape-id objects] @@ -136,3 +166,54 @@ (lazy-seq (loopfn (rest ids))))))] (loopfn (:shapes root)))) +(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 xf-new-object] + (clone-object object parent-id objects xf-new-object identity)) + + ([object parent-id objects xf-new-object xf-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 (map :id new-direct-children))) + + new-object (xf-new-object new-object object) + + new-objects (concat [new-object] new-children) + + updated-object (xf-original-object object new-object) + + updated-objects (if (= object updated-object) + updated-children + (concat [updated-object] updated-children))] + + [new-object new-objects updated-objects]) + + (let [child-id (first child-ids) + child (get objects child-id) + + [new-child new-child-objects updated-child-objects] + (clone-object child new-id objects xf-new-object xf-original-object)] + + (recur + (next child-ids) + (concat new-direct-children [new-child]) + (concat new-children new-child-objects) + (concat updated-children updated-child-objects)))))))) + diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index d16ab658c7..4bf7debbdb 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -48,10 +48,6 @@ (s/def ::set-of-string (s/every string? :kind set?)) -;; --- Expose inner functions - -(defn interrupt? [e] (= e :interrupt)) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Workspace Initialization ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -956,7 +952,7 @@ ptk/WatchEvent (watch [_ state stream] (->> stream - (rx/filter interrupt?) + (rx/filter dwc/interrupt?) (rx/take 1) (rx/map (constantly clear-edition-mode)))))) @@ -985,7 +981,7 @@ ptk/WatchEvent (watch [_ state stream] (let [cancel-event? (fn [event] - (interrupt? event)) + (dwc/interrupt? event)) stoper (rx/filter (ptk/type? ::clear-drawing) stream)] (->> (rx/filter cancel-event? stream) (rx/take 1) diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs index 0bae8964c6..726f037862 100644 --- a/frontend/src/app/main/data/workspace/common.cljs +++ b/frontend/src/app/main/data/workspace/common.cljs @@ -44,6 +44,11 @@ ([state page-id] (get-in state [:workspace-data :pages-index page-id :options]))) +(defn interrupt? [e] (= e :interrupt)) + +(defn lookup-component-objects + ([state component-id] + (get-in state [:workspace-data :components component-id :objects]))) ;; --- Changes Handling @@ -454,3 +459,4 @@ objects (lookup-page-objects state page-id) [rchanges uchanges] (impl-gen-changes objects page-id (seq ids))] (rx/of (commit-changes rchanges uchanges {:commit-local? true}))))))) + diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 16ecdf42a3..0fb5d891bb 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -12,11 +12,15 @@ [app.common.data :as d] [app.common.spec :as us] [app.common.uuid :as uuid] + [app.common.pages-helpers :as cph] + [app.common.geom.point :as gpt] + [app.common.geom.shapes :as geom] [app.main.data.workspace.common :as dwc] [app.main.data.workspace.selection :as dws] [app.common.pages :as cp] [app.main.repo :as rp] [app.main.store :as st] + [app.main.streams :as ms] [app.util.color :as color] [app.util.i18n :refer [tr]] [beicon.core :as rx] @@ -107,7 +111,7 @@ :object prev}] (rx/of (dwc/commit-changes [rchg] [uchg] {:commit-local? true})))))) -(declare clone-shape) +(declare make-component-shape) (def add-component (ptk/reify ::add-component @@ -127,7 +131,7 @@ (dws/prepare-create-group page-id shapes "Component-" true)) [new-shape new-shapes updated-shapes] - (clone-shape group nil objects) + (make-component-shape group nil objects) rchanges (conj rchanges {:type :add-component @@ -168,59 +172,23 @@ (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}) (dws/select-shapes (d/ordered-set (:id group)))))))))) -(defn- clone-shape +(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 parent-id objects] - (let [new-id (uuid/next)] - (if (nil? (:shapes shape)) + (let [xf-new-shape (fn [new-shape original-shape] + (assoc new-shape :frame-id nil)) - ; TODO: unify this case with the empty child-ids case. - (let [new-shape (assoc shape - :id new-id - :parent-id parent-id - :frame-id nil) + xf-original-shape (fn [original-shape new-shape] + (cond-> original-shape + true + (assoc :shape-ref (:id new-shape)) - new-shapes [new-shape] + (nil? (:parent-id new-shape)) + (assoc :component-id (:id new-shape))))] - updated-shapes [(cond-> shape - true (assoc :shape-ref (:id new-shape)) - (nil? parent-id) (assoc :component-id (:id new-shape)))]] - - [new-shape new-shapes updated-shapes]) - - (loop [child-ids (seq (:shapes shape)) - new-children [] - updated-children []] - - (if (empty? child-ids) - (let [new-shape (assoc shape - :id new-id - :parent-id parent-id - :frame-id nil - :shapes (map :id new-children)) - - new-shapes (conj new-children new-shape) - - updated-shapes - (conj updated-children - (cond-> shape - true (assoc :shape-ref (:id new-shape)) - (nil? parent-id) (assoc :component-id (:id new-shape))))] - - [new-shape new-shapes updated-shapes]) - - (let [child-id (first child-ids) - child (get objects child-id) - - [new-child new-child-shapes updated-child-shapes] - (clone-shape child new-id objects)] - - (recur - (next child-ids) - (into new-children new-child-shapes) - (into updated-children updated-child-shapes)))))))) + (cph/clone-object shape parent-id objects xf-new-shape xf-original-shape))) (defn delete-component [{:keys [id] :as params}] @@ -241,3 +209,145 @@ (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))) +(defn instantiate-component + [id] + (us/assert ::us/uuid id) + (ptk/reify ::instantiate-component + ptk/WatchEvent + (watch [_ state stream] + (let [component (get-in state [:workspace-data :components id]) + component-shape (get-in component [:objects (:id component)]) + + orig-pos (gpt/point (:x component-shape) (:y component-shape)) + mouse-pos @ms/mouse-position + delta (gpt/subtract mouse-pos orig-pos) + + _ (js/console.log "orig-pos" (clj->js orig-pos)) + _ (js/console.log "mouse-pos" (clj->js mouse-pos)) + _ (js/console.log "delta" (clj->js delta)) + + page-id (:current-page-id state) + objects (dwc/lookup-page-objects state page-id) + unames (dwc/retrieve-used-names objects) + + all-frames (cph/select-frames objects) + + xf-new-shape + (fn [new-shape original-shape] + (let [new-name ;; TODO: ojoooooooooo + (dwc/generate-unique-name unames (:name new-shape))] + + (cond-> new-shape + true + (as-> $ + (assoc $ :name new-name) + (geom/move $ delta) + (assoc $ :frame-id + (dwc/calculate-frame-overlap all-frames $)) + (assoc $ :parent-id + (or (:parent-id $) (:frame-id $))) + (assoc $ :shape-ref (:id original-shape))) + + (nil? (:parent-id original-shape)) + (assoc :component-id (:id original-shape))))) + + [new-shape new-shapes _] + (cph/clone-object component-shape + nil + (get component :objects) + xf-new-shape) + + rchanges (map (fn [obj] + {:type :add-obj + :id (:id obj) + :page-id page-id + :frame-id (:frame-id obj) + :parent-id (:parent-id obj) + :obj obj}) + new-shapes) + + uchanges (map (fn [obj] + {:type :del-obj + :id (:id obj) + :page-id page-id}) + new-shapes)] + + (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}) + (dws/select-shapes (d/ordered-set (:id new-shape)))))))) + +(defn detach-component + [id] + (us/assert ::us/uuid id) + (ptk/reify ::detach-component + ptk/WatchEvent + (watch [_ state stream] + (let [page-id (:current-page-id state) + objects (dwc/lookup-page-objects state page-id) + root-id (cph/get-root-component id objects) + + shapes (cph/get-object-with-children root-id objects) + + rchanges (map (fn [obj] + {:type :mod-obj + :page-id page-id + :id (:id obj) + :operations [{:type :set + :attr :component-id + :val nil} + {:type :set + :attr :shape-ref + :val nil}]}) + shapes) + + uchanges (map (fn [obj] + {:type :mod-obj + :page-id page-id + :id (:id obj) + :operations [{:type :set + :attr :component-id + :val (:component-id obj)} + {:type :set + :attr :shape-ref + :val (:shape-ref obj)}]}) + shapes)] + + (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))) + +(defn reset-component + [id] + [id] + (us/assert ::us/uuid id) + (ptk/reify ::reset-component + ptk/WatchEvent + (watch [_ state stream] + ))) + +(defn update-component + [id] + [id] + (us/assert ::us/uuid id) + (ptk/reify ::update-component + ptk/WatchEvent + (watch [_ state stream] + (let [page-id (:current-page-id state) + objects (dwc/lookup-page-objects state page-id) + root-id (cph/get-root-component id objects) + root-shape (get objects id) + component-id (get root-shape :component-id) + component-objs (dwc/lookup-component-objects state component-id) + + shapes (cph/get-object-with-children root-id objects) + + rchanges [{:type :update-component + :id component-id + :shapes shapes} + {:type :sync-library + :id (get-in state [:workspace-file :id])}] + + + uchanges [{:type :update-component + :id component-id + :shapes (vals component-objs)}]] + + (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))) + diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index c60fed1b70..27b32ac5bf 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -33,33 +33,6 @@ (s/def ::set-of-string (s/every string? :kind set?)) -;; Duplicate from workspace. -;; FIXME: Move these functions to a common place - -(defn interrupt? [e] (= e :interrupt)) - -(defn- retrieve-used-names - [objects] - (into #{} (map :name) (vals objects))) - -(defn- extract-numeric-suffix - [basename] - (if-let [[match 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 ::set-of-string used) - (s/assert ::us/string basename) - (let [[prefix initial] (extract-numeric-suffix basename)] - (loop [counter initial] - (let [candidate (str prefix "-" counter)] - (if (contains? used candidate) - (recur (inc counter)) - candidate))))) - ;; --- Selection Rect (declare select-shapes-by-current-selrect) @@ -88,7 +61,7 @@ (ptk/reify ::handle-selection ptk/WatchEvent (watch [_ state stream] - (let [stoper (rx/filter #(or (interrupt? %) + (let [stoper (rx/filter #(or (dwc/interrupt? %) (ms/mouse-up? %)) stream)] (rx/concat @@ -198,7 +171,9 @@ (let [selrect (geom/selection-rect shapes) frame-id (-> shapes first :frame-id) parent-id (-> shapes first :parent-id) - group-name (if (and keep-name (= (count shapes) 1)) + group-name (if (and keep-name + (= (count shapes) 1) + (= (:type (first shapes)) :group)) (:name (first shapes)) (name (gensym prefix)))] (-> (cp/make-minimal-group frame-id selrect group-name) @@ -298,7 +273,7 @@ (defn- prepare-duplicate-shape-change [objects page-id names obj delta frame-id parent-id] (let [id (uuid/next) - name (generate-unique-name names (:name obj)) + name (dwc/generate-unique-name names (:name obj)) renamed-obj (assoc obj :id id :name name) moved-obj (geom/move renamed-obj delta) frames (cph/select-frames objects) @@ -338,7 +313,7 @@ (defn- prepare-duplicate-frame-change [objects page-id names obj delta] (let [frame-id (uuid/next) - frame-name (generate-unique-name names (:name obj)) + frame-name (dwc/generate-unique-name names (:name obj)) sch (->> (map #(get objects %) (:shapes obj)) (mapcat #(prepare-duplicate-shape-change objects page-id names % delta frame-id frame-id))) @@ -367,7 +342,7 @@ selected (get-in state [:workspace-local :selected]) delta (gpt/point 0 0) - unames (retrieve-used-names objects) + unames (dwc/retrieve-used-names objects) rchanges (prepare-duplicate-changes objects page-id unames selected delta) uchanges (mapv #(array-map :type :del-obj :page-id page-id :id (:id %)) diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index 5277e32b32..7669046bcd 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -61,7 +61,10 @@ do-unlock-shape #(st/emit! (dw/update-shape-flags id {:blocked false})) do-create-group #(st/emit! dw/group-selected) do-remove-group #(st/emit! dw/ungroup-selected) - do-add-component #(st/emit! dwl/add-component)] + do-add-component #(st/emit! dwl/add-component) + do-detach-component #(st/emit! (dwl/detach-component id)) + do-reset-component #(st/emit! (dwl/reset-component id)) + do-update-component #(st/emit! (dwl/update-component id))] [:* [:& menu-entry {:title "Copy" :shortcut "Ctrl + c" @@ -110,9 +113,18 @@ :on-click do-lock-shape}]) [:& menu-separator] - [:& menu-entry {:title "Create component" - :shortcut "Ctrl + K" - :on-click do-add-component}] + + (if (nil? (:shape-ref shape)) + [:& menu-entry {:title "Create component" + :shortcut "Ctrl + K" + :on-click do-add-component}] + [:* + [:& menu-entry {:title "Detach instance" + :on-click do-detach-component}] + [:& menu-entry {:title "Reset overrides" + :on-click do-reset-component}] + [:& menu-entry {:title "Update master component" + :on-click do-update-component}]]) [:& menu-separator] [:& menu-entry {:title "Delete" diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index b3d6484a04..8323948357 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -69,8 +69,8 @@ on-drag-start (mf/use-callback - (fn [path event] - (dnd/set-data! event "text/uri-list" (cfg/resolve-media-path path)) + (fn [component-id event] + (dnd/set-data! event "app/component" component-id) (dnd/set-allowed-effect! event "move")))] [:div.asset-group @@ -82,7 +82,7 @@ [:div.grid-cell {:key (:id component) :draggable true :on-context-menu (on-context-menu (:id component)) - :on-drag-start (partial on-drag-start (:path component))} + :on-drag-start (partial on-drag-start (:id component))} [:& exports/component-svg {:group (get-in component [:objects (:id component)]) :objects (:objects component)}] [:div.cell-name (:name component)]]) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 2fec8d0f21..224eecda42 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -22,6 +22,7 @@ [app.common.data :as d] [app.main.constants :as c] [app.main.data.workspace :as dw] + [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.drawing :as dd] [app.main.data.colors :as dwc] [app.main.data.fetch :as mdf] @@ -454,6 +455,7 @@ on-drag-enter (fn [e] (when (or (dnd/has-type? e "app/shape") + (dnd/has-type? e "app/component") (dnd/has-type? e "Files") (dnd/has-type? e "text/uri-list")) (dom/prevent-default e))) @@ -461,6 +463,7 @@ on-drag-over (fn [e] (when (or (dnd/has-type? e "app/shape") + (dnd/has-type? e "app/component") (dnd/has-type? e "Files") (dnd/has-type? e "text/uri-list")) (dom/prevent-default e))) @@ -491,6 +494,10 @@ (assoc :x final-x) (assoc :y final-y))))) + (dnd/has-type? event "app/component") + (let [component-id (dnd/get-data event "app/component")] + (st/emit! (dwl/instantiate-component component-id))) + (dnd/has-type? event "text/uri-list") (let [data (dnd/get-data event "text/uri-list") lines (str/lines data) From 1ad9a7f82fdfdbc51a78ba8e2040232729b3ad09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 10 Sep 2020 15:42:11 +0200 Subject: [PATCH 4/7] :tada: Link with components of other files --- common/app/common/pages.cljc | 58 +--- common/app/common/pages_helpers.cljc | 1 + .../styles/main/partials/sidebar-assets.scss | 4 +- .../styles/main/partials/sidebar-layers.scss | 6 +- frontend/src/app/main/data/workspace.cljs | 12 +- .../app/main/data/workspace/libraries.cljs | 273 ++++++++++++++++-- .../app/main/ui/workspace/context_menu.cljs | 14 +- .../app/main/ui/workspace/sidebar/assets.cljs | 37 +-- .../app/main/ui/workspace/sidebar/layers.cljs | 2 + .../src/app/main/ui/workspace/viewport.cljs | 4 +- frontend/src/app/util/router.cljs | 14 + 11 files changed, 329 insertions(+), 96 deletions(-) diff --git a/common/app/common/pages.cljc b/common/app/common/pages.cljc index fba45543bb..7f4b641146 100644 --- a/common/app/common/pages.cljc +++ b/common/app/common/pages.cljc @@ -44,6 +44,9 @@ (integer? %) (>= % min-safe-int) (<= % max-safe-int))) +(s/def ::component-id uuid?) +(s/def ::component-file uuid?) +(s/def ::shape-ref uuid?) (s/def ::safe-number #(and @@ -216,7 +219,10 @@ (s/def ::shape (s/and ::minimal-shape ::shape-attrs - (s/keys :opt-un [::id]))) + (s/keys :opt-un [::id + ::component-id + ::component-file + ::shape-ref]))) (s/def :internal.page/objects (s/map-of uuid? ::shape)) @@ -363,7 +369,7 @@ (s/keys :req-un [::id])) (defmethod change-spec :update-component [_] - (s/keys :req-un [::id ::shapes])) + (s/keys :req-un [::id ::name ::shapes])) (s/def ::change (s/multi-spec change-spec :type)) (s/def ::changes (s/coll-of ::change)) @@ -777,50 +783,12 @@ [data {:keys [id]}] (d/dissoc-in data [:components id])) -(declare sync-component-shape) - (defmethod process-change :update-component - [data {:keys [id shapes]}] - (let [sync-component - (fn [component] - (update component :objects - #(d/mapm (partial sync-component-shape shapes) %)))] - - (update-in data [:components id] sync-component))) - -(defn- sync-component-shape - [new-shapes _ component-shape] - (let [shape (d/seek #(= (:shape-ref %) (:id component-shape)) new-shapes)] - (if (nil? shape) - component-shape - (-> component-shape - (d/assoc-when :content (:content shape)) - (d/assoc-when :fill-color (:fill-color shape)) - (d/assoc-when :fill-color-ref-file (:fill-color-ref-file shape)) - (d/assoc-when :fill-color-ref-id (:fill-color-ref-id shape)) - (d/assoc-when :fill-opacity (:fill-opacity shape)) - (d/assoc-when :font-family (:font-family shape)) - (d/assoc-when :font-size (:font-size shape)) - (d/assoc-when :font-style (:font-style shape)) - (d/assoc-when :font-weight (:font-weight shape)) - (d/assoc-when :letter-spacing (:letter-spacing shape)) - (d/assoc-when :line-height (:line-height shape)) - (d/assoc-when :proportion (:proportion shape)) - (d/assoc-when :rx (:rx shape)) - (d/assoc-when :ry (:ry shape)) - (d/assoc-when :stroke-color (:stroke-color shape)) - (d/assoc-when :stroke-color-ref-file (:stroke-color-ref-file shape)) - (d/assoc-when :stroke-color-ref-id (:stroke-color-ref-id shape)) - (d/assoc-when :stroke-opacity (:stroke-opacity shape)) - (d/assoc-when :stroke-style (:stroke-style shape)) - (d/assoc-when :stroke-width (:stroke-width shape)) - (d/assoc-when :stroke-alignment (:stroke-alignment shape)) - (d/assoc-when :text-align (:text-align shape)) - (d/assoc-when :width (:width shape)) - (d/assoc-when :height (:height shape)) - (d/assoc-when :interactions (:interactions shape)) - (d/assoc-when :selrect (:selrect shape)) - (d/assoc-when :points (:points shape)))))) + [data {:keys [id name shapes]}] + (update-in data [:components id] + #(assoc % + :name name + :objects (d/index-by :id shapes)))) (defmethod process-operation :set [shape op] diff --git a/common/app/common/pages_helpers.cljc b/common/app/common/pages_helpers.cljc index 1754665ffc..b11a93cc26 100644 --- a/common/app/common/pages_helpers.cljc +++ b/common/app/common/pages_helpers.cljc @@ -174,6 +174,7 @@ 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 xf-new-object] (clone-object object parent-id objects xf-new-object identity)) diff --git a/frontend/resources/styles/main/partials/sidebar-assets.scss b/frontend/resources/styles/main/partials/sidebar-assets.scss index 2324c5ade4..d4d6ff4ed9 100644 --- a/frontend/resources/styles/main/partials/sidebar-assets.scss +++ b/frontend/resources/styles/main/partials/sidebar-assets.scss @@ -176,9 +176,7 @@ grid-auto-rows: 10vh; .grid-cell { - background-color: transparent; - border: 1px solid $color-gray-40; - border-radius: 4px; + padding: $x-small; & svg { height: 10vh; diff --git a/frontend/resources/styles/main/partials/sidebar-layers.scss b/frontend/resources/styles/main/partials/sidebar-layers.scss index 9a53caeb66..ea8e23fd40 100644 --- a/frontend/resources/styles/main/partials/sidebar-layers.scss +++ b/frontend/resources/styles/main/partials/sidebar-layers.scss @@ -111,7 +111,7 @@ .element-list li.component { .element-list-body { - .element-name { + span.element-name { color: $color-component; } @@ -120,7 +120,7 @@ } &.selected { - .element-name { + span.element-name { color: $color-component-highlight; } @@ -132,7 +132,7 @@ &:hover { background-color: $color-component-highlight; - .element-name { + span.element-name { color: $color-gray-60; } diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 4bf7debbdb..6db5753806 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -28,7 +28,7 @@ [app.main.data.workspace.selection :as dws] [app.main.data.workspace.texts :as dwtxt] [app.main.data.workspace.transforms :as dwt] - [app.main.data.colors :as dwl] + [app.main.data.colors :as mdc] [app.main.repo :as rp] [app.main.store :as st] [app.main.streams :as ms] @@ -1130,8 +1130,14 @@ (ptk/reify ::show-context-menu ptk/UpdateEvent (update [_ state] - (let [mdata {:position position + (let [page-id (:current-page-id state) + objects (dwc/lookup-page-objects state page-id) + root-id (cph/get-root-component (:id shape) objects) + root-shape (get objects root-id) + + mdata {:position position :shape shape + :root-shape root-shape :selected (get-in state [:workspace-local :selected])}] (-> state (assoc-in [:workspace-local :context-menu] mdata)))) @@ -1467,5 +1473,5 @@ "right" #(st/emit! (dwt/move-selected :right false)) "left" #(st/emit! (dwt/move-selected :left false)) - "i" #(st/emit! (dwl/picker-for-selected-shape ))}) + "i" #(st/emit! (mdc/picker-for-selected-shape ))}) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 0fb5d891bb..7fddac1879 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -23,6 +23,7 @@ [app.main.streams :as ms] [app.util.color :as color] [app.util.i18n :refer [tr]] + [app.util.router :as rt] [beicon.core :as rx] [cljs.spec.alpha :as s] [potok.core :as ptk])) @@ -147,6 +148,9 @@ :operations [{:type :set :attr :component-id :val (:component-id updated-shape)} + {:type :set + :attr :component-file + :val nil} {:type :set :attr :shape-ref :val (:shape-ref updated-shape)}]}) @@ -164,6 +168,9 @@ :operations [{:type :set :attr :component-id :val nil} + {:type :set + :attr :component-file + :val nil} {:type :set :attr :shape-ref :val nil}]}) @@ -192,15 +199,14 @@ (defn delete-component [{:keys [id] :as params}] + (us/assert ::us/uuid id) (ptk/reify ::delete-component ptk/WatchEvent (watch [_ state stream] (let [component (get-in state [:workspace-data :components id]) rchanges [{:type :del-component - :id id} - {:type :sync-library - :id (get-in state [:workspace-file :id])}] + :id id}] uchanges [{:type :add-component :id id @@ -210,12 +216,15 @@ (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))) (defn instantiate-component - [id] - (us/assert ::us/uuid id) + [file-id component-id] + (us/assert (s/nilable ::us/uuid) file-id) + (us/assert ::us/uuid component-id) (ptk/reify ::instantiate-component ptk/WatchEvent (watch [_ state stream] - (let [component (get-in state [:workspace-data :components id]) + (let [component (if (nil? file-id) + (get-in state [:workspace-data :components component-id]) + (get-in state [:workspace-libraries file-id :data :components component-id])) component-shape (get-in component [:objects (:id component)]) orig-pos (gpt/point (:x component-shape) (:y component-shape)) @@ -228,14 +237,16 @@ page-id (:current-page-id state) objects (dwc/lookup-page-objects state page-id) - unames (dwc/retrieve-used-names objects) + unames (atom (dwc/retrieve-used-names objects)) all-frames (cph/select-frames objects) xf-new-shape (fn [new-shape original-shape] - (let [new-name ;; TODO: ojoooooooooo - (dwc/generate-unique-name unames (:name new-shape))] + (let [new-name + (dwc/generate-unique-name @unames (:name new-shape))] + + (swap! unames conj new-name) (cond-> new-shape true @@ -249,7 +260,10 @@ (assoc $ :shape-ref (:id original-shape))) (nil? (:parent-id original-shape)) - (assoc :component-id (:id original-shape))))) + (assoc :component-id (:id original-shape)) + + (and (nil? (:parent-id original-shape)) (some? file-id)) + (assoc :component-file file-id)))) [new-shape new-shapes _] (cph/clone-object component-shape @@ -294,6 +308,9 @@ :operations [{:type :set :attr :component-id :val nil} + {:type :set + :attr :component-file + :val nil} {:type :set :attr :shape-ref :val nil}]}) @@ -306,6 +323,9 @@ :operations [{:type :set :attr :component-id :val (:component-id obj)} + {:type :set + :attr :component-file + :val (:component-file obj)} {:type :set :attr :shape-ref :val (:shape-ref obj)}]}) @@ -313,17 +333,51 @@ (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))) +(defn nav-to-component-file + [file-id] + (us/assert ::us/uuid file-id) + (ptk/reify ::nav-to-component-file + ptk/WatchEvent + (watch [_ state stream] + (let [file (get-in state [:workspace-libraries file-id]) + pparams {:project-id (:project-id file) + :file-id (:id file)} + qparams {:page-id (first (get-in file [:data :pages]))}] + (st/emit! (rt/nav-new-window :workspace pparams qparams)))))) + +(declare generate-sync-file) +(declare generate-sync-page) +(declare generate-sync-shape-and-children) +(declare generate-sync-shape) +(declare remove-component-and-ref) +(declare remove-ref) +(declare update-attrs) +(declare sync-attrs) + (defn reset-component - [id] [id] (us/assert ::us/uuid id) (ptk/reify ::reset-component ptk/WatchEvent (watch [_ state stream] - ))) + (let [page-id (:current-page-id state) + page (get-in state [:workspace-data :pages-index page-id]) + objects (dwc/lookup-page-objects state page-id) + root-id (cph/get-root-component id objects) + root-shape (get objects id) + file-id (get root-shape :component-file) + + components + (if (nil? file-id) + (get-in state [:workspace-data :components]) + (get-in state [:workspace-libraries file-id :data :components])) + + [rchanges uchanges] + (generate-sync-shape-and-children root-shape page components)] + + (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))) (defn update-component - [id] [id] (us/assert ::us/uuid id) (ptk/reify ::update-component @@ -333,21 +387,202 @@ objects (dwc/lookup-page-objects state page-id) root-id (cph/get-root-component id objects) root-shape (get objects id) + component-id (get root-shape :component-id) component-objs (dwc/lookup-component-objects state component-id) + component-obj (get component-objs component-id) - shapes (cph/get-object-with-children root-id objects) + ;; Clone again the original shape and its children, maintaing + ;; the ids of the cloned shapes. If the original shape has some + ;; new child shapes, the cloned ones will have new generated ids. + xf-new-shape (fn [new-shape original-shape] + (cond-> new-shape + true + (assoc :frame-id nil) + + (some? (:shape-ref original-shape)) + (assoc :id (:shape-ref original-shape)))) + + [new-shape new-shapes _] + (cph/clone-object root-shape nil objects xf-new-shape) rchanges [{:type :update-component :id component-id - :shapes shapes} - {:type :sync-library - :id (get-in state [:workspace-file :id])}] - + :name (:name new-shape) + :shapes new-shapes}] uchanges [{:type :update-component :id component-id + :name (:name component-obj) :shapes (vals component-objs)}]] (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))) - + +(defn sync-file + [{:keys [file-id] :as params}] + (us/assert (s/nilable ::us/uuid) file-id) + (ptk/reify ::sync-file + ptk/WatchEvent + (watch [_ state stream] + (let [[rchanges uchanges] (generate-sync-file state file-id)] + (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))) + +(defn- generate-sync-file + [state file-id] + (let [components + (if (nil? file-id) + (get-in state [:workspace-data :components]) + (get-in state [:workspace-libraries file-id :data :components]))] + (loop [pages (seq (vals (get-in state [:workspace-data :pages-index]))) + rchanges [] + uchanges []] + (let [page (first pages)] + (if (nil? page) + [rchanges uchanges] + (let [[page-rchanges page-uchanges] + (generate-sync-page page components)] + (recur (next pages) + (concat rchanges page-rchanges) + (concat uchanges page-uchanges)))))))) + +(defn- generate-sync-page + [page components] + (let [linked-shapes + (cph/select-objects #(some? (:component-id %)) page)] + (loop [shapes (seq linked-shapes) + rchanges [] + uchanges []] + (let [shape (first shapes)] + (if (nil? shape) + [rchanges uchanges] + (let [[shape-rchanges shape-uchanges] + (generate-sync-shape-and-children shape page components)] + (recur (next shapes) + (concat rchanges shape-rchanges) + (concat uchanges shape-uchanges)))))))) + +(defn- generate-sync-shape-and-children + [root-shape page components] + (let [objects (get page :objects) + all-shapes (cph/get-object-with-children (:id root-shape) objects) + component (get components (:component-id root-shape))] + (loop [shapes (seq all-shapes) + rchanges [] + uchanges []] + (let [shape (first shapes)] + (if (nil? shape) + [rchanges uchanges] + (let [[shape-rchanges shape-uchanges] + (generate-sync-shape shape page component)] + (recur (next shapes) + (concat rchanges shape-rchanges) + (concat uchanges shape-uchanges)))))))) + +(defn- generate-sync-shape + [shape page component] + (if (nil? component) + (remove-component-and-ref shape page) + (let [component-shape (get (:objects component) (:shape-ref shape))] + (if (nil? component-shape) + (remove-ref shape page) + (update-attrs shape component-shape page))))) + +(defn- remove-component-and-ref + [shape page] + [[{:type :mod-obj + :page-id (:id page) + :id (:id shape) + :operations [{:type :set + :attr :component-id + :val nil} + {:type :set + :attr :component-file + :val nil} + {:type :set + :attr :shape-ref + :val nil}]}] + [{:type :mod-obj + :page-id (:id page) + :id (:id shape) + :operations [{:type :set + :attr :component-id + :val (:component-id shape)} + {:type :set + :attr :component-file + :val (:component-file shape)} + {:type :set + :attr :shape-ref + :val (:shape-ref shape)}]}]]) + +(defn- remove-ref + [shape page] + [[{:type :mod-obj + :page-id (:id page) + :id (:id shape) + :operations [{:type :set + :attr :shape-ref + :val nil}]}] + [{:type :mod-obj + :page-id (:id page) + :id (:id shape) + :operations [{:type :set + :attr :shape-ref + :val (:shape-ref shape)}]}]]) + +(defn- update-attrs + [shape component-shape page] + (loop [attrs (seq sync-attrs) + roperations [] + uoperations []] + (let [attr (first attrs)] + (if (nil? attr) + (let [rchanges [{:type :mod-obj + :page-id (:id page) + :id (:id shape) + :operations roperations}] + uchanges [{:type :mod-obj + :page-id (:id page) + :id (:id shape) + :operations uoperations}]] + [rchanges uchanges]) + (if-not (contains? shape attr) + (recur (next attrs) + roperations + uoperations) + (let [roperation {:type :set + :attr attr + :val (get component-shape attr)} + uoperation {:type :set + :attr attr + :val (get shape attr)}] + (recur (next attrs) + (conj roperations roperation) + (conj uoperations uoperation)))))))) + +(def sync-attrs [:content + :fill-color + :fill-color-ref-file + :fill-color-ref-id + :fill-opacity + :font-family + :font-size + :font-style + :font-weight + :letter-spacing + :line-height + :proportion + :rx + :ry + :stroke-color + :stroke-color-ref-file + :stroke-color-ref-id + :stroke-opacity + :stroke-style + :stroke-width + :stroke-alignment + :text-align + :width + :height + :interactions + :points]) + diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index 7669046bcd..487708beee 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -46,6 +46,7 @@ [{:keys [mdata] :as props}] (let [{:keys [id] :as shape} (:shape mdata) selected (:selected mdata) + root-shape (:root-shape mdata) do-duplicate #(st/emit! dw/duplicate-selected) do-delete #(st/emit! dw/delete-selected) @@ -64,7 +65,11 @@ do-add-component #(st/emit! dwl/add-component) do-detach-component #(st/emit! (dwl/detach-component id)) do-reset-component #(st/emit! (dwl/reset-component id)) - do-update-component #(st/emit! (dwl/update-component id))] + do-update-component #(do + (st/emit! (dwl/update-component id)) + (st/emit! (dwl/sync-file {:file-id nil}))) + do-navigate-component-file #(st/emit! (dwl/nav-to-component-file + (:component-file root-shape)))] [:* [:& menu-entry {:title "Copy" :shortcut "Ctrl + c" @@ -123,8 +128,11 @@ :on-click do-detach-component}] [:& menu-entry {:title "Reset overrides" :on-click do-reset-component}] - [:& menu-entry {:title "Update master component" - :on-click do-update-component}]]) + (if (nil? (:component-file root-shape)) + [:& menu-entry {:title "Update master component" + :on-click do-update-component}] + [:& menu-entry {:title "Go to master component file" + :on-click do-navigate-component-file}])]) [:& menu-separator] [:& menu-entry {:title "Delete" diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index 8323948357..79a51e1dab 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -50,8 +50,8 @@ (mf/use-callback (mf/deps state) (fn [] - (let [params {:id (:component-id @state)}] - (st/emit! (dwl/delete-component params))))) + (st/emit! (dwl/delete-component {:id (:component-id @state)})) + (st/emit! (dwl/sync-file {:file-id nil})))) on-context-menu (mf/use-callback @@ -70,7 +70,8 @@ on-drag-start (mf/use-callback (fn [component-id event] - (dnd/set-data! event "app/component" component-id) + (dnd/set-data! event "app/component" {:file-id (if local? nil file-id) + :component-id component-id}) (dnd/set-allowed-effect! event "move")))] [:div.asset-group @@ -363,26 +364,26 @@ (mf/defc file-library [{:keys [file local? open? filters locale] :as props}] - (let [open? (mf/use-state open?) - shared? (:is-shared file) - router (mf/deref refs/router) - toggle-open #(swap! open? not) + (let [open? (mf/use-state open?) + shared? (:is-shared file) + router (mf/deref refs/router) + toggle-open #(swap! open? not) - toggles (mf/use-state #{:graphics :colors}) + toggles (mf/use-state #{:graphics :colors}) - url (rt/resolve router :workspace - {:project-id (:project-id file) - :file-id (:id file)} - {:page-id (get-in file [:data :pages 0])}) + url (rt/resolve router :workspace + {:project-id (:project-id file) + :file-id (:id file)} + {:page-id (get-in file [:data :pages 0])}) - colors-ref (mf/use-memo (mf/deps (:id file)) #(file-colors-ref (:id file))) - colors (apply-filters (mf/deref colors-ref) filters) + colors-ref (mf/use-memo (mf/deps (:id file)) #(file-colors-ref (:id file))) + colors (apply-filters (mf/deref colors-ref) filters) - media-ref (mf/use-memo (mf/deps (:id file)) #(file-media-ref (:id file))) - media (apply-filters (mf/deref media-ref) filters) + media-ref (mf/use-memo (mf/deps (:id file)) #(file-media-ref (:id file))) + media (apply-filters (mf/deref media-ref) filters) - components-ref (mf/use-memo (mf/deps (:id file)) #(file-components-ref (:id file))) - components (apply-filters (mf/deref components-ref) filters)] + components-ref (mf/use-memo (mf/deps (:id file)) #(file-components-ref (:id file))) + components (apply-filters (mf/deref components-ref) filters)] [:div.tool-window [:div.tool-window-bar diff --git a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs index 77fcd0e249..41c9288524 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs @@ -297,6 +297,8 @@ :content :parent-id :component-id + :component-file + :shape-ref :metadata])] (persistent! (reduce-kv (fn [res id obj] diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 224eecda42..72a64ed16d 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -495,8 +495,8 @@ (assoc :y final-y))))) (dnd/has-type? event "app/component") - (let [component-id (dnd/get-data event "app/component")] - (st/emit! (dwl/instantiate-component component-id))) + (let [{:keys [component-id file-id]} (dnd/get-data event "app/component")] + (st/emit! (dwl/instantiate-component file-id component-id))) (dnd/has-type? event "text/uri-list") (let [data (dnd/get-data event "text/uri-list") diff --git a/frontend/src/app/util/router.cljs b/frontend/src/app/util/router.cljs index 8d6d8707ee..067c44e5fc 100644 --- a/frontend/src/app/util/router.cljs +++ b/frontend/src/app/util/router.cljs @@ -16,6 +16,7 @@ [potok.core :as ptk] [reitit.core :as r] [app.common.data :as d] + [app.config :as cfg] [app.util.browser-history :as bhistory] [app.util.timers :as ts]) (:import @@ -112,6 +113,19 @@ (def navigate nav) +(deftype NavigateNewWindow [id params qparams] + ptk/EffectEvent + (effect [_ state stream] + (let [router (:router state) + path (resolve router id params qparams) + uri (str cfg/public-uri "/#" path)] + (js/window.open uri "_blank")))) + +(defn nav-new-window + ([id] (nav-new-window id nil nil)) + ([id params] (nav-new-window id params nil)) + ([id params qparams] (NavigateNewWindow. id params qparams))) + ;; --- History API (defn initialize-history From 47a8da43dcbfce918ea9ae7c08404a0575a1144b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Wed, 16 Sep 2020 09:00:14 +0200 Subject: [PATCH 5/7] :tada: Display selection and controls in color for components --- .../src/app/main/ui/workspace/selection.cljs | 53 ++++++++++++------- .../ui/workspace/shapes/interactions.cljs | 3 +- .../app/main/ui/workspace/shapes/outline.cljs | 7 +-- .../src/app/main/ui/workspace/viewport.cljs | 8 ++- 4 files changed, 46 insertions(+), 25 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/selection.cljs b/frontend/src/app/main/ui/workspace/selection.cljs index 87539d4bb0..eb73c081da 100644 --- a/frontend/src/app/main/ui/workspace/selection.cljs +++ b/frontend/src/app/main/ui/workspace/selection.cljs @@ -34,10 +34,11 @@ (def resize-point-circle-radius 10) (def resize-point-rect-size 8) (def resize-side-height 8) -(def selection-rect-color "#1FDEA7") +(def selection-rect-color-normal "#1FDEA7") +(def selection-rect-color-component "#00E0FF") (def selection-rect-width 1) -(mf/defc selection-rect [{:keys [transform rect zoom]}] +(mf/defc selection-rect [{:keys [transform rect zoom color]}] (let [{:keys [x y width height]} rect] [:rect.main {:x x @@ -45,7 +46,7 @@ :width width :height height :transform transform - :style {:stroke selection-rect-color + :style {:stroke color :stroke-width (/ selection-rect-width zoom) :fill "transparent"}}])) @@ -125,7 +126,7 @@ :on-mouse-down on-rotate}])) (mf/defc resize-point-handler - [{:keys [cx cy zoom position on-resize transform rotation]}] + [{:keys [cx cy zoom position on-resize transform rotation color]}] (let [{cx' :x cy' :y} (gpt/transform (gpt/point cx cy) transform) rot-square (case position :top-left 0 @@ -139,7 +140,7 @@ :vectorEffect "non-scaling-stroke" } :fill "#FFFFFF" - :stroke "#1FDEA7" + :stroke color :cx cx' :cy cy'}] @@ -173,6 +174,7 @@ [props] (let [shape (obj/get props "shape") zoom (obj/get props "zoom") + color (obj/get props "color") on-resize (obj/get props "on-resize") on-rotate (obj/get props "on-rotate") current-transform (mf/deref refs/current-transform) @@ -186,8 +188,10 @@ ;; Selection rect [:& selection-rect {:rect selrect :transform transform - :zoom zoom}] - [:& outline {:shape (geom/transform-shape shape)}] + :zoom zoom + :color color}] + [:& outline {:shape (geom/transform-shape shape) + :color color}] ;; Handlers (for [{:keys [type position props]} (handlers-for-selection selrect)] @@ -197,7 +201,8 @@ :on-rotate on-rotate :on-resize (partial on-resize position) :transform transform - :rotation (:rotation shape)} + :rotation (:rotation shape) + :color color} props (map->obj (merge common-props props))] (case type :rotation (when (not= :frame (:type shape)) [:> rotation-handler props]) @@ -206,7 +211,7 @@ ;; --- Selection Handlers (Component) (mf/defc path-edition-selection-handlers - [{:keys [shape modifiers zoom] :as props}] + [{:keys [shape modifiers zoom color] :as props}] (letfn [(on-mouse-down [event index] (dom/stop-propagation event) ;; TODO: this need code ux refactor @@ -240,26 +245,26 @@ :key index :on-mouse-down #(on-mouse-down % index) :fill "#ffffff" - :stroke "#1FDEA7" + :stroke color :style {:cursor cur/move-pointer}}]))]))) ;; TODO: add specs for clarity (mf/defc text-edition-selection-handlers - [{:keys [shape zoom] :as props}] + [{:keys [shape zoom color] :as props}] (let [{:keys [x y width height]} shape] [:g.controls [:rect.main {:x x :y y :transform (geom/transform-matrix shape) :width width :height height - :style {:stroke "#1FDEA7" + :style {:stroke color :stroke-width "0.5" :stroke-opacity "1" :fill "transparent"}}]])) (mf/defc multiple-selection-handlers - [{:keys [shapes selected zoom] :as props}] + [{:keys [shapes selected zoom color] :as props}] (let [shape (geom/selection-rect shapes) shape-center (geom/center shape) on-resize (fn [current-position initial-position event] @@ -272,13 +277,14 @@ [:* [:& controls {:shape shape :zoom zoom + :color color :on-resize on-resize :on-rotate on-rotate}] (when (debug? :selection-center) [:circle {:cx (:x shape-center) :cy (:y shape-center) :r 5 :fill "yellow"}])])) (mf/defc single-selection-handlers - [{:keys [shape zoom] :as props}] + [{:keys [shape zoom color] :as props}] (let [shape-id (:id shape) shape (geom/transform-shape shape) shape' (if (debug? :simple-selection) (geom/selection-rect [shape]) shape) @@ -293,6 +299,7 @@ [:* [:& controls {:shape shape' :zoom zoom + :color color :on-rotate on-rotate :on-resize on-resize}]])) @@ -304,7 +311,11 @@ shapes (->> (mf/deref (refs/objects-by-id selected)) (remove nil?)) num (count shapes) - {:keys [id type] :as shape} (first shapes)] + {:keys [id type] :as shape} (first shapes) + + color (if (or (> num 1) (nil? (:shape-ref shape))) + selection-rect-color-normal + selection-rect-color-component)] (cond (zero? num) nil @@ -312,18 +323,22 @@ (> num 1) [:& multiple-selection-handlers {:shapes shapes :selected selected - :zoom zoom}] + :zoom zoom + :color color}] (and (= type :text) (= edition (:id shape))) [:& text-edition-selection-handlers {:shape shape - :zoom zoom}] + :zoom zoom + :color color}] (and (or (= type :path) (= type :curve)) (= edition (:id shape))) [:& path-edition-selection-handlers {:shape shape - :zoom zoom}] + :zoom zoom + :color color}] :else [:& single-selection-handlers {:shape shape - :zoom zoom}]))) + :zoom zoom + :color color}]))) diff --git a/frontend/src/app/main/ui/workspace/shapes/interactions.cljs b/frontend/src/app/main/ui/workspace/shapes/interactions.cljs index ab6d79d4da..fb36edad84 100644 --- a/frontend/src/app/main/ui/workspace/shapes/interactions.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/interactions.cljs @@ -158,7 +158,8 @@ :zoom zoom}] (when dest-shape - [:& outline {:shape dest-shape}])]))) + [:& outline {:shape dest-shape + :color "#31EFB8"}])]))) (mf/defc interaction-handle diff --git a/frontend/src/app/main/ui/workspace/shapes/outline.cljs b/frontend/src/app/main/ui/workspace/shapes/outline.cljs index 1b7efbd3f4..361636e446 100644 --- a/frontend/src/app/main/ui/workspace/shapes/outline.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/outline.cljs @@ -22,6 +22,7 @@ [props] (let [zoom (mf/deref refs/selected-zoom) shape (unchecked-get props "shape") + color (unchecked-get props "color") transform (gsh/transform-matrix shape) {:keys [id x y width height]} shape @@ -31,7 +32,7 @@ "rect") common {:fill "transparent" - :stroke "#31EFB8" + :stroke color :strokeWidth (/ 1 zoom) :pointerEvents "none" :transform transform} @@ -42,10 +43,10 @@ :cy (+ y (/ height 2)) :rx (/ width 2) :ry (/ height 2)} - + (:curve :path) {:d (path/render-path shape)} - + {:x x :y y :width width diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 72a64ed16d..74957a7e56 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -133,12 +133,16 @@ hover (or (unchecked-get props "hover") #{}) outline? (set/union selected hover) shapes (->> (vals objects) (filter (comp outline? :id))) - transform (mf/deref refs/current-transform)] + transform (mf/deref refs/current-transform) + color (if (or (> (count shapes) 1) (nil? (:shape-ref (first shapes)))) + "#31EFB8" + "#00E0FF")] (when (nil? transform) [:g.outlines (for [shape shapes] [:& outline {:key (str "outline-" (:id shape)) - :shape (gsh/transform-shape shape)}])]))) + :shape (gsh/transform-shape shape) + :color color}])]))) (mf/defc frames {::mf/wrap [mf/memo] From 63c9e80ed4904462add0596ab403a844e157b2f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Wed, 16 Sep 2020 11:37:54 +0200 Subject: [PATCH 6/7] :tada: Update relative position of shapes inside component --- .../app/main/data/workspace/libraries.cljs | 89 ++++++++++++------- 1 file changed, 57 insertions(+), 32 deletions(-) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 7fddac1879..0f66b049de 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -353,6 +353,7 @@ (declare remove-ref) (declare update-attrs) (declare sync-attrs) +(declare calc-new-pos) (defn reset-component [id] @@ -465,7 +466,8 @@ [root-shape page components] (let [objects (get page :objects) all-shapes (cph/get-object-with-children (:id root-shape) objects) - component (get components (:component-id root-shape))] + component (get components (:component-id root-shape)) + root-component (get-in component [:objects (:shape-ref root-shape)])] (loop [shapes (seq all-shapes) rchanges [] uchanges []] @@ -473,19 +475,19 @@ (if (nil? shape) [rchanges uchanges] (let [[shape-rchanges shape-uchanges] - (generate-sync-shape shape page component)] + (generate-sync-shape shape root-shape root-component page component)] (recur (next shapes) (concat rchanges shape-rchanges) (concat uchanges shape-uchanges)))))))) (defn- generate-sync-shape - [shape page component] + [shape root-shape root-component page component] (if (nil? component) (remove-component-and-ref shape page) (let [component-shape (get (:objects component) (:shape-ref shape))] (if (nil? component-shape) (remove-ref shape page) - (update-attrs shape component-shape page))))) + (update-attrs shape component-shape root-shape root-component page))))) (defn- remove-component-and-ref [shape page] @@ -530,34 +532,46 @@ :val (:shape-ref shape)}]}]]) (defn- update-attrs - [shape component-shape page] - (loop [attrs (seq sync-attrs) - roperations [] - uoperations []] - (let [attr (first attrs)] - (if (nil? attr) - (let [rchanges [{:type :mod-obj - :page-id (:id page) - :id (:id shape) - :operations roperations}] - uchanges [{:type :mod-obj - :page-id (:id page) - :id (:id shape) - :operations uoperations}]] - [rchanges uchanges]) - (if-not (contains? shape attr) - (recur (next attrs) - roperations - uoperations) - (let [roperation {:type :set - :attr attr - :val (get component-shape attr)} - uoperation {:type :set - :attr attr - :val (get shape attr)}] + [shape component-shape root-shape root-component page] + (let [new-pos (calc-new-pos shape component-shape root-shape root-component)] + (loop [attrs (seq sync-attrs) + roperations [{:type :set + :attr :x + :val (:x new-pos)} + {:type :set + :attr :y + :val (:y new-pos)}] + uoperations [{:type :set + :attr :x + :val (:x shape)} + {:type :set + :attr :y + :val (:y shape)}]] + + (let [attr (first attrs)] + (if (nil? attr) + (let [rchanges [{:type :mod-obj + :page-id (:id page) + :id (:id shape) + :operations roperations}] + uchanges [{:type :mod-obj + :page-id (:id page) + :id (:id shape) + :operations uoperations}]] + [rchanges uchanges]) + (if-not (contains? shape attr) (recur (next attrs) - (conj roperations roperation) - (conj uoperations uoperation)))))))) + roperations + uoperations) + (let [roperation {:type :set + :attr attr + :val (get component-shape attr)} + uoperation {:type :set + :attr attr + :val (get shape attr)}] + (recur (next attrs) + (conj roperations roperation) + (conj uoperations uoperation))))))))) (def sync-attrs [:content :fill-color @@ -584,5 +598,16 @@ :width :height :interactions - :points]) + :points + :transform]) + +(defn- calc-new-pos + [shape component-shape root-shape root-component] + (let [root-pos (gpt/point (:x root-shape) (:y root-shape)) + root-component-pos (gpt/point (:x root-component) (:y root-component)) + component-pos (gpt/point (:x component-shape) (:y component-shape)) + delta (gpt/subtract component-pos root-component-pos) + shape-pos (gpt/point (:x shape) (:y shape)) + new-pos (gpt/add root-pos delta)] + new-pos)) From e2dec81699a13cf110d7220a3db289999fabda2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Wed, 16 Sep 2020 16:08:58 +0200 Subject: [PATCH 7/7] :recycle: Fix some comments --- common/app/common/data.cljc | 7 ---- common/app/common/pages.cljc | 11 +++-- common/app/common/pages_helpers.cljc | 28 ++++--------- .../app/main/data/workspace/libraries.cljs | 40 +++++++++---------- frontend/src/app/main/exports.cljs | 1 - 5 files changed, 34 insertions(+), 53 deletions(-) diff --git a/common/app/common/data.cljc b/common/app/common/data.cljc index fe84e3f832..ba0b92dc29 100644 --- a/common/app/common/data.cljc +++ b/common/app/common/data.cljc @@ -182,13 +182,6 @@ (assoc m key (apply f found args)) m))) -(defn assoc-in-when - [m key-seq v] - (let [found (get-in m key-seq sentinel)] - (if-not (identical? sentinel found) - (assoc-in m key-seq v) - m))) - (defn assoc-when [m key v] (let [found (get m key sentinel)] diff --git a/common/app/common/pages.cljc b/common/app/common/pages.cljc index 7f4b641146..6b6428c127 100644 --- a/common/app/common/pages.cljc +++ b/common/app/common/pages.cljc @@ -362,14 +362,17 @@ (defmethod change-spec :del-media [_] (s/keys :req-un [::id])) +(s/def :internal.changes.add-component/shapes + (s/coll-of ::shape)) + (defmethod change-spec :add-component [_] - (s/keys :req-un [::id ::name ::new-shapes])) + (s/keys :req-un [::id ::name :internal.changes.add-component/shapes])) (defmethod change-spec :del-component [_] (s/keys :req-un [::id])) (defmethod change-spec :update-component [_] - (s/keys :req-un [::id ::name ::shapes])) + (s/keys :req-un [::id ::name :internal.changes.add-component/shapes])) (s/def ::change (s/multi-spec change-spec :type)) (s/def ::changes (s/coll-of ::change)) @@ -773,11 +776,11 @@ (update data :media dissoc id)) (defmethod process-change :add-component - [data {:keys [id name new-shapes]}] + [data {:keys [id name shapes]}] (assoc-in data [:components id] {:id id :name name - :objects (d/index-by :id new-shapes)})) + :objects (d/index-by :id shapes)})) (defmethod process-change :del-component [data {:keys [id]}] diff --git a/common/app/common/pages_helpers.cljc b/common/app/common/pages_helpers.cljc index b11a93cc26..53be7402a0 100644 --- a/common/app/common/pages_helpers.cljc +++ b/common/app/common/pages_helpers.cljc @@ -43,6 +43,7 @@ (defn get-children "Retrieve all children ids recursively for a given object" [id objects] + ;; TODO: find why does this sometimes come as a list instead of vector (let [shapes (vec (get-in objects [id :shapes]))] (if shapes (d/concat shapes (mapcat #(get-children % objects) shapes)) @@ -58,21 +59,6 @@ [id objects] (map #(get objects %) (concat [id] (get-children id objects)))) -(defn walk-children - "Go through an object and all the children tree, and apply a - function to each one. Return the list of changed objects." - [id f objects] - (let [obj (get objects id)] - (if (nil? (:shapes obj)) - [(apply f obj)] - (loop [children (map #(get objects %) (:shapes obj)) - updated-children []] - (if (empty? children) - updated-children - (let [child (first children)] - (recur (rest children) - (concat [(apply f child)] updated-children)))))))) - (defn is-shape-grouped "Checks if a shape is inside a group" [shape-id objects] @@ -175,10 +161,10 @@ 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 xf-new-object] - (clone-object object parent-id objects xf-new-object identity)) + ([object parent-id objects update-new-object] + (clone-object object parent-id objects update-new-object identity)) - ([object parent-id objects xf-new-object xf-original-object] + ([object parent-id objects update-new-object update-original-object] (let [new-id (uuid/next)] (loop [child-ids (seq (:shapes object)) new-direct-children [] @@ -194,11 +180,11 @@ (some? (:shapes object)) (assoc :shapes (map :id new-direct-children))) - new-object (xf-new-object new-object object) + new-object (update-new-object new-object object) new-objects (concat [new-object] new-children) - updated-object (xf-original-object object new-object) + updated-object (update-original-object object new-object) updated-objects (if (= object updated-object) updated-children @@ -210,7 +196,7 @@ child (get objects child-id) [new-child new-child-objects updated-child-objects] - (clone-object child new-id objects xf-new-object xf-original-object)] + (clone-object child new-id objects update-new-object update-original-object)] (recur (next child-ids) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 0f66b049de..11d1df3c7a 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -138,7 +138,7 @@ {:type :add-component :id (:id new-shape) :name (:name new-shape) - :new-shapes new-shapes}) + :shapes new-shapes}) rchanges (into rchanges (map (fn [updated-shape] @@ -184,18 +184,18 @@ from parent and frame. Update the original shapes to have links to the new ones." [shape parent-id objects] - (let [xf-new-shape (fn [new-shape original-shape] - (assoc new-shape :frame-id nil)) + (let [update-new-shape (fn [new-shape original-shape] + (assoc new-shape :frame-id nil)) - xf-original-shape (fn [original-shape new-shape] - (cond-> original-shape - true - (assoc :shape-ref (:id new-shape)) + update-original-shape (fn [original-shape new-shape] + (cond-> original-shape + true + (assoc :shape-ref (:id new-shape)) - (nil? (:parent-id new-shape)) - (assoc :component-id (:id new-shape))))] + (nil? (:parent-id new-shape)) + (assoc :component-id (:id new-shape))))] - (cph/clone-object shape parent-id objects xf-new-shape xf-original-shape))) + (cph/clone-object shape parent-id objects update-new-shape update-original-shape))) (defn delete-component [{:keys [id] :as params}] @@ -211,7 +211,7 @@ uchanges [{:type :add-component :id id :name (:name component) - :new-shapes (:objects component)}]] + :shapes (vals (:objects component))}]] (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))) @@ -241,7 +241,7 @@ all-frames (cph/select-frames objects) - xf-new-shape + update-new-shape (fn [new-shape original-shape] (let [new-name (dwc/generate-unique-name @unames (:name new-shape))] @@ -269,7 +269,7 @@ (cph/clone-object component-shape nil (get component :objects) - xf-new-shape) + update-new-shape) rchanges (map (fn [obj] {:type :add-obj @@ -396,16 +396,16 @@ ;; Clone again the original shape and its children, maintaing ;; the ids of the cloned shapes. If the original shape has some ;; new child shapes, the cloned ones will have new generated ids. - xf-new-shape (fn [new-shape original-shape] - (cond-> new-shape - true - (assoc :frame-id nil) + update-new-shape (fn [new-shape original-shape] + (cond-> new-shape + true + (assoc :frame-id nil) - (some? (:shape-ref original-shape)) - (assoc :id (:shape-ref original-shape)))) + (some? (:shape-ref original-shape)) + (assoc :id (:shape-ref original-shape)))) [new-shape new-shapes _] - (cph/clone-object root-shape nil objects xf-new-shape) + (cph/clone-object root-shape nil objects update-new-shape) rchanges [{:type :update-component :id component-id diff --git a/frontend/src/app/main/exports.cljs b/frontend/src/app/main/exports.cljs index 5cfbc1243f..8a2105d67e 100644 --- a/frontend/src/app/main/exports.cljs +++ b/frontend/src/app/main/exports.cljs @@ -157,7 +157,6 @@ :xmlns "http://www.w3.org/2000/svg"} [:& wrapper {:shape frame :view-box vbox}]])) -;; TODO: unify with frame-svg? (mf/defc component-svg {::mf/wrap [mf/memo]} [{:keys [objects group zoom] :or {zoom 1} :as props}]