From 4a74862bf5cfb18564cfd0f3c11b48c5d774b9f3 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 23 Apr 2024 14:52:57 +0200 Subject: [PATCH 1/8] :sparkles: Add viewport information to the plugin --- .../src/app/main/data/workspace/zoom.cljs | 50 ++++++++++-- frontend/src/app/plugins/api.cljs | 40 +++++++--- frontend/src/app/plugins/shape.cljs | 3 +- frontend/src/app/plugins/viewport.cljs | 79 +++++++++++++++++++ 4 files changed, 150 insertions(+), 22 deletions(-) create mode 100644 frontend/src/app/plugins/viewport.cljs diff --git a/frontend/src/app/main/data/workspace/zoom.cljs b/frontend/src/app/main/data/workspace/zoom.cljs index 379776ede..8672544fd 100644 --- a/frontend/src/app/main/data/workspace/zoom.cljs +++ b/frontend/src/app/main/data/workspace/zoom.cljs @@ -6,6 +6,8 @@ (ns app.main.data.workspace.zoom (:require + [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.files.helpers :as cfh] [app.common.geom.align :as gal] [app.common.geom.matrix :as gmt] @@ -54,14 +56,20 @@ #(impl-update-zoom % center (fn [z] (max (/ z 1.3) 0.01))))))))) (defn set-zoom - [center scale] - (ptk/reify ::set-zoom - ptk/UpdateEvent - (update [_ state] - (update state :workspace-local - #(impl-update-zoom % center (fn [z] (-> (* z scale) - (max 0.01) - (min 200)))))))) + ([scale] + (set-zoom nil scale)) + ([center scale] + (ptk/reify ::set-zoom + ptk/UpdateEvent + (update [_ state] + (let [vp (dm/get-in state [:workspace-local :vbox]) + x (+ (:x vp) (/ (:width vp) 2)) + y (+ (:y vp) (/ (:height vp) 2)) + center (d/nilv center (gpt/point x y))] + (update state :workspace-local + #(impl-update-zoom % center (fn [z] (-> (* z scale) + (max 0.01) + (min 200)))))))))) (def reset-zoom (ptk/reify ::reset-zoom @@ -110,6 +118,32 @@ (assoc :zoom-inverse (/ 1 zoom)) (update :vbox merge srect))))))))))) +(defn fit-to-shapes + [ids] + (ptk/reify ::fit-to-shapes + ptk/UpdateEvent + (update [_ state] + (if (empty? ids) + state + (let [page-id (:current-page-id state) + objects (wsh/lookup-page-objects state page-id) + _ (prn "??" (->> ids (map #(get objects %)) (map :name))) + srect (->> ids + (map #(get objects %)) + (gsh/shapes->rect))] + + (update state :workspace-local + (fn [{:keys [vport] :as local}] + (let [srect (gal/adjust-to-viewport + vport srect + {:padding 40}) + zoom (/ (:width vport) + (:width srect))] + (-> local + (assoc :zoom zoom) + (assoc :zoom-inverse (/ 1 zoom)) + (update :vbox merge srect)))))))))) + (defn start-zooming [pt] (ptk/reify ::start-zooming ptk/WatchEvent diff --git a/frontend/src/app/plugins/api.cljs b/frontend/src/app/plugins/api.cljs index 51ed5155c..683d5677d 100644 --- a/frontend/src/app/plugins/api.cljs +++ b/frontend/src/app/plugins/api.cljs @@ -17,7 +17,8 @@ [app.plugins.events :as events] [app.plugins.file :as file] [app.plugins.page :as page] - [app.plugins.shape :as shape])) + [app.plugins.shape :as shape] + [app.plugins.viewport :as viewport])) ;; ;; PLUGINS PUBLIC API - The plugins will able to access this functions @@ -28,12 +29,30 @@ (map val) (map shape/data->shape-proxy))) +(defn create-shape + [type] + (let [page-id (:current-page-id @st/state) + page (dm/get-in @st/state [:workspace-data :pages-index page-id]) + shape (cts/setup-shape {:type :type + :x 0 :y 0 :width 100 :height 100}) + changes + (-> (cb/empty-changes) + (cb/with-page page) + (cb/with-objects (:objects page)) + (cb/add-object shape))] + (st/emit! (ch/commit-changes changes)) + (shape/data->shape-proxy shape))) + (deftype PenpotContext [] Object (addListener [_ type callback] (events/add-listener type callback)) + (getViewport + [_] + (viewport/create-proxy)) + (getFile [_] (file/data->file-proxy (:workspace-file @st/state) (:workspace-data @st/state))) @@ -70,19 +89,13 @@ "dark" (get-in @st/state [:profile :theme])))) + (createFrame + [_] + (create-shape :frame)) + (createRectangle [_] - (let [page-id (:current-page-id @st/state) - page (dm/get-in @st/state [:workspace-data :pages-index page-id]) - shape (cts/setup-shape {:type :rect - :x 0 :y 0 :width 100 :height 100}) - changes - (-> (cb/empty-changes) - (cb/with-page page) - (cb/with-objects (:objects page)) - (cb/add-object shape))] - (st/emit! (ch/commit-changes changes)) - (shape/data->shape-proxy shape)))) + (create-shape :rect))) (defn create-context [] @@ -90,4 +103,5 @@ (PenpotContext.) {:name "root" :get #(.getRoot ^js %)} {:name "currentPage" :get #(.getPage ^js %)} - {:name "selection" :get #(.getSelectedShapes ^js %)})) + {:name "selection" :get #(.getSelectedShapes ^js %)} + {:name "viewport" :get #(.getViewport ^js %)})) diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index 8a7aea836..1bcbea5dc 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -69,7 +69,8 @@ (clone [_] (.log js/console (clj->js _data))) (delete [_] (.log js/console (clj->js _data))) - (appendChild [_] (.log js/console (clj->js _data)))) + (appendChild [_ child] (.log js/console (clj->js _data))) + (insertChild [_ index child] (.log js/console (clj->js _data)))) (crc/define-properties! ShapeProxy diff --git a/frontend/src/app/plugins/viewport.cljs b/frontend/src/app/plugins/viewport.cljs new file mode 100644 index 000000000..c243d327a --- /dev/null +++ b/frontend/src/app/plugins/viewport.cljs @@ -0,0 +1,79 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.plugins.viewport + "RPC for plugins runtime." + (:require + [app.common.data.macros :as dm] + [app.common.geom.point :as gpt] + [app.common.record :as crc] + [app.common.record :as crc] + [app.common.uuid :as uuid] + [app.main.data.workspace.viewport :as dwv] + [app.main.data.workspace.zoom :as dwz] + [app.main.store :as st] + [app.plugins.page :as page] + [app.plugins.utils :refer [get-data-fn]] + [app.util.object :as obj])) + +(deftype ViewportProxy [] + Object + (zoomIntoView [_ shapes] + (let [ids + (->> shapes + (map (fn [v] + (if (string? v) + (uuid/uuid v) + (uuid/uuid (obj/get v "x"))))))] + (st/emit! (dwz/fit-to-shapes ids))))) + +(crc/define-properties! + ViewportProxy + {:name js/Symbol.toStringTag + :get (fn [] (str "ViewportProxy"))}) + +(defn create-proxy + [] + (crc/add-properties! + (ViewportProxy.) + {:name "center" + :get + (fn [_] + (let [vp (dm/get-in @st/state [:workspace-local :vbox]) + x (+ (:x vp) (/ (:width vp) 2)) + y (+ (:y vp) (/ (:height vp) 2))] + (.freeze js/Object #js {:x x :y y}))) + + :set + (fn [_ value] + (let [new-x (obj/get value "x") + new-y (obj/get value "y") + vb (dm/get-in @st/state [:workspace-local :vbox]) + old-x (+ (:x vb) (/ (:width vb) 2)) + old-y (+ (:y vb) (/ (:height vb) 2)) + delta-x (- new-x old-x) + delta-y (- new-y old-y) + to-position + {:x #(+ % delta-x) + :y #(+ % delta-y)}] + (st/emit! (dwv/update-viewport-position to-position))))} + + {:name "zoom" + :get + (fn [_] + (dm/get-in @st/state [:workspace-local :zoom])) + :set + (fn [_ value] + (let [z (dm/get-in @st/state [:workspace-local :zoom])] + (st/emit! (dwz/set-zoom (/ value z)))))} + + {:name "bounds" + :get + (fn [_] + (let [vport (dm/get-in @st/state [:workspace-local :vport])] + (.freeze js/Object (clj->js vport))))})) + + From 75d89653653868a046b0f1f9c5a5f88a8183a53a Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 24 Apr 2024 10:18:19 +0200 Subject: [PATCH 2/8] :sparkles: Add method to append children --- frontend/src/app/main/data/workspace.cljs | 1 + frontend/src/app/plugins/shape.cljs | 22 +++++++++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 52a09c8c9..078f6c1a3 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -790,6 +790,7 @@ (defn relocate-shapes [ids parent-id to-index & [ignore-parents?]] (dm/assert! (every? uuid? ids)) + (dm/assert! (set? ids)) (dm/assert! (uuid? parent-id)) (dm/assert! (number? to-index)) diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index 1bcbea5dc..33148f752 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -12,10 +12,12 @@ [app.common.files.helpers :as cfh] [app.common.record :as crc] [app.common.text :as txt] + [app.common.uuid :as uuid] [app.main.data.workspace :as udw] [app.main.data.workspace.changes :as dwc] [app.main.store :as st] [app.plugins.utils :refer [get-data get-data-fn]] + [app.util.object :as obj] [cuerdas.core :as str])) (declare data->shape-proxy) @@ -69,8 +71,16 @@ (clone [_] (.log js/console (clj->js _data))) (delete [_] (.log js/console (clj->js _data))) - (appendChild [_ child] (.log js/console (clj->js _data))) - (insertChild [_ index child] (.log js/console (clj->js _data)))) + + (appendChild [self child] + (let [parent-id (get-data self :id) + child-id (uuid/uuid (obj/get child "id"))] + (st/emit! (udw/relocate-shapes #{ child-id } parent-id 0)))) + + (insertChild [self index child] + (let [parent-id (get-data self :id) + child-id (uuid/uuid (obj/get child "id"))] + (st/emit! (udw/relocate-shapes #{ child-id } parent-id index))))) (crc/define-properties! ShapeProxy @@ -117,9 +127,6 @@ (let [id (get-data self :id)] (st/emit! (dwc/update-shapes [id] #(assoc % :name value)))))} - {:name "children" - :get #(.getChildren ^js %)} - {:name "fills" :get #(get-state % :fills make-fills) ;;:set (fn [self value] (.log js/console self value)) @@ -130,6 +137,11 @@ ;;:set (fn [self value] (.log js/console self value)) }) + (cond-> (or (cfh/frame-shape? data) (cfh/group-shape? data) (cfh/svg-raw-shape? data) (cfh/bool-shape? data)) + (crc/add-properties! + {:name "children" + :get #(.getChildren ^js %)})) + (cond-> (cfh/text-shape? data) (crc/add-properties! {:name "characters" From 21d38a058bb6f6e3f23b8a8af4cd32863d90aa7f Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 29 Apr 2024 12:20:15 +0200 Subject: [PATCH 3/8] :sparkles: Add to plugin api: upload media, group and ungroup --- .../src/app/main/data/workspace/groups.cljs | 76 ++++++++++++------- .../src/app/main/data/workspace/media.cljs | 12 ++- frontend/src/app/plugins/api.cljs | 38 +++++++++- frontend/src/app/plugins/shape.cljs | 28 ++++--- frontend/src/app/plugins/utils.cljs | 41 +++++++++- frontend/src/app/plugins/viewport.cljs | 4 - 6 files changed, 148 insertions(+), 51 deletions(-) diff --git a/frontend/src/app/main/data/workspace/groups.cljs b/frontend/src/app/main/data/workspace/groups.cljs index 30046ecef..be758443e 100644 --- a/frontend/src/app/main/data/workspace/groups.cljs +++ b/frontend/src/app/main/data/workspace/groups.cljs @@ -15,6 +15,7 @@ [app.common.types.container :as ctn] [app.common.types.shape :as cts] [app.common.types.shape.layout :as ctl] + [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.selection :as dws] [app.main.data.workspace.state-helpers :as wsh] @@ -68,7 +69,7 @@ result))))))) (defn prepare-create-group - [changes objects page-id shapes base-name keep-name?] + [changes id objects page-id shapes base-name keep-name?] (let [frame-id (:frame-id (first shapes)) parent-id (:parent-id (first shapes)) gname (if (and keep-name? @@ -84,7 +85,8 @@ (cfh/get-position-on-parent objects) inc) - group (cts/setup-shape {:type :group + group (cts/setup-shape {:id id + :type :group :name gname :shapes (mapv :id shapes) :selrect selrect @@ -173,30 +175,43 @@ ;; GROUPS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(def group-selected - (ptk/reify ::group-selected +(defn group-shapes + [id ids & {:keys [change-selection?] :or {change-selection? false}}] + (ptk/reify ::group-shapes ptk/WatchEvent (watch [it state _] - (let [page-id (:current-page-id state) + (let [id (d/nilv id (uuid/next)) + page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) - selected (->> (wsh/lookup-selected state) - (cfh/clean-loops objects) - (remove #(ctn/has-any-copy-parent? objects (get objects %)))) - shapes (shapes-for-grouping objects selected) + + shapes + (->> ids + (cfh/clean-loops objects) + (remove #(ctn/has-any-copy-parent? objects (get objects %))) + (shapes-for-grouping objects)) parents (into #{} (map :parent-id) shapes)] (when-not (empty? shapes) (let [[group changes] - (prepare-create-group (pcb/empty-changes it) objects page-id shapes "Group" false)] + (prepare-create-group (pcb/empty-changes it) id objects page-id shapes "Group" false)] (rx/of (dch/commit-changes changes) - (dws/select-shapes (d/ordered-set (:id group))) + (when change-selection? + (dws/select-shapes (d/ordered-set (:id group)))) (ptk/data-event :layout/update {:ids parents})))))))) -(def ungroup-selected - (ptk/reify ::ungroup-selected +(def group-selected + (ptk/reify ::group-selected + ptk/WatchEvent + (watch [_ state _] + (let [selected (wsh/lookup-selected state)] + (rx/of group-shapes nil selected))))) + +(defn ungroup-shapes + [ids & {:keys [change-selection?] :or {change-selection? false}}] + (ptk/reify ::ungroup-shapes ptk/WatchEvent (watch [it state _] - (let [page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) + (let [page-id (:current-page-id state) + objects (wsh/lookup-page-objects state page-id) prepare (fn [shape-id] @@ -213,35 +228,42 @@ (ctl/grid-layout? objects (:parent-id shape)) (pcb/update-shapes [(:parent-id shape)] ctl/assign-cells {:with-objects? true})))) - selected (->> (wsh/lookup-selected state) - (remove #(ctn/has-any-copy-parent? objects (get objects %))) - ;; components can't be ungrouped - (remove #(ctk/instance-head? (get objects %)))) - changes-list (sequence - (keep prepare) - selected) + ids (->> ids + (remove #(ctn/has-any-copy-parent? objects (get objects %))) + ;; components can't be ungrouped + (remove #(ctk/instance-head? (get objects %)))) + + changes-list (sequence (keep prepare) ids) parents (into #{} (comp (map #(cfh/get-parent objects %)) (keep :id)) - selected) + ids) child-ids (into (d/ordered-set) (mapcat #(dm/get-in objects [% :shapes])) - selected) + ids) changes {:redo-changes (vec (mapcat :redo-changes changes-list)) :undo-changes (vec (mapcat :undo-changes changes-list)) :origin it} undo-id (js/Symbol)] - (when-not (empty? selected) + (when-not (empty? ids) (rx/of (dwu/start-undo-transaction undo-id) (dch/commit-changes changes) (ptk/data-event :layout/update {:ids parents}) (dwu/commit-undo-transaction undo-id) - (dws/select-shapes child-ids))))))) + (when change-selection? + (dws/select-shapes child-ids)))))))) + +(def ungroup-selected + (ptk/reify ::ungroup-selected + ptk/WatchEvent + (watch [_ state _] + (let [selected (wsh/lookup-selected state)] + (rx/of (ungroup-shapes selected :change-selection? true)))))) (def mask-group (ptk/reify ::mask-group @@ -262,7 +284,7 @@ (= (:type (first shapes)) :group)) [first-shape (-> (pcb/empty-changes it page-id) (pcb/with-objects objects))] - (prepare-create-group (pcb/empty-changes it) objects page-id shapes "Mask" true)) + (prepare-create-group (pcb/empty-changes it) (uuid/next) objects page-id shapes "Mask" true)) changes (-> changes (pcb/update-shapes (:shapes group) diff --git a/frontend/src/app/main/data/workspace/media.cljs b/frontend/src/app/main/data/workspace/media.cljs index 693207d87..815619059 100644 --- a/frontend/src/app/main/data/workspace/media.cljs +++ b/frontend/src/app/main/data/workspace/media.cljs @@ -87,7 +87,17 @@ (->> (svg/upload-images svg-data file-id) (rx/map #(svg/add-svg-shapes (assoc svg-data :image-data %) position)))))) -(defn- process-uris + +(defn upload-media-url + [name file-id url] + (rp/cmd! + :create-file-media-object-from-url + {:name name + :file-id file-id + :url url + :is-local true})) + +(defn process-uris [{:keys [file-id local? name uris mtype on-image on-svg]}] (letfn [(svg-url? [url] (or (and mtype (= mtype "image/svg+xml")) diff --git a/frontend/src/app/plugins/api.cljs b/frontend/src/app/plugins/api.cljs index 683d5677d..5efaddfc9 100644 --- a/frontend/src/app/plugins/api.cljs +++ b/frontend/src/app/plugins/api.cljs @@ -13,12 +13,18 @@ [app.common.types.shape :as cts] [app.common.uuid :as uuid] [app.main.data.workspace.changes :as ch] + [app.main.data.workspace.groups :as dwg] + [app.main.data.workspace.media :as dwm] [app.main.store :as st] [app.plugins.events :as events] [app.plugins.file :as file] [app.plugins.page :as page] [app.plugins.shape :as shape] - [app.plugins.viewport :as viewport])) + [app.plugins.utils :as utils] + [app.plugins.viewport :as viewport] + [app.util.object :as obj] + [beicon.v2.core :as rx] + [promesa.core :as p])) ;; ;; PLUGINS PUBLIC API - The plugins will able to access this functions @@ -33,7 +39,7 @@ [type] (let [page-id (:current-page-id @st/state) page (dm/get-in @st/state [:workspace-data :pages-index page-id]) - shape (cts/setup-shape {:type :type + shape (cts/setup-shape {:type type :x 0 :y 0 :width 100 :height 100}) changes (-> (cb/empty-changes) @@ -89,13 +95,39 @@ "dark" (get-in @st/state [:profile :theme])))) + (uploadMediaUrl + [_ name url] + (let [file-id (get-in @st/state [:workspace-file :id])] + (p/create + (fn [resolve reject] + (->> (dwm/upload-media-url name file-id url) + (rx/map utils/to-js) + (rx/take 1) + (rx/subs! resolve reject)))))) + + (group + [_ shapes] + (let [page-id (:current-page-id @st/state) + id (uuid/next) + ids (into #{} (map #(get (obj/get % "_data") :id)) shapes)] + (st/emit! (dwg/group-shapes id ids)) + (shape/data->shape-proxy + (dm/get-in @st/state [:workspace-data :pages-index page-id :objects id])))) + + (ungroup + [_ group & rest] + (let [shapes (concat [group] rest) + ids (into #{} (map #(get (obj/get % "_data") :id)) shapes)] + (st/emit! (dwg/ungroup-shapes ids)))) + (createFrame [_] (create-shape :frame)) (createRectangle [_] - (create-shape :rect))) + (create-shape :rect)) + ) (defn create-context [] diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index 33148f752..56383e38a 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -16,9 +16,8 @@ [app.main.data.workspace :as udw] [app.main.data.workspace.changes :as dwc] [app.main.store :as st] - [app.plugins.utils :refer [get-data get-data-fn]] - [app.util.object :as obj] - [cuerdas.core :as str])) + [app.plugins.utils :as utils :refer [get-data get-data-fn]] + [app.util.object :as obj])) (declare data->shape-proxy) @@ -26,19 +25,13 @@ [fills] (.freeze js/Object - (apply array - (->> fills - ;; TODO: Transform explicitly instead of cljs->js? - (map #(clj->js % {:keyword-fn (fn [k] (str/camel (name k)))})))))) + (apply array (->> fills (map utils/to-js))))) (defn- make-strokes [strokes] (.freeze js/Object - (apply array - (->> strokes - ;; TODO: Transform explicitly instead of cljs->js? - (map #(clj->js % {:keyword-fn (fn [k] (str/camel (name k)))})))))) + (apply array (->> strokes (map utils/to-js))))) (defn- locate-shape [shape-id] @@ -64,7 +57,6 @@ (resize [self width height] - (let [id (get-data self :id)] (st/emit! (udw/update-dimensions [id] :width width) (udw/update-dimensions [id] :height height)))) @@ -99,7 +91,7 @@ :get (get-data-fn :id str)} {:name "type" - :get (get-data-fn :type)} + :get (get-data-fn :type name)} {:name "x" :get #(get-state % :x) @@ -129,12 +121,18 @@ {:name "fills" :get #(get-state % :fills make-fills) - ;;:set (fn [self value] (.log js/console self value)) + :set (fn [self value] + (let [id (get-data self :id) + value (mapv #(utils/from-js %) value)] + (st/emit! (dwc/update-shapes [id] #(assoc % :fills value))))) } {:name "strokes" :get #(get-state % :strokes make-strokes) - ;;:set (fn [self value] (.log js/console self value)) + :set (fn [self value] + (let [id (get-data self :id) + value (mapv #(utils/from-js %) value)] + (st/emit! (dwc/update-shapes [id] #(assoc % :strokes value))))) }) (cond-> (or (cfh/frame-shape? data) (cfh/group-shape? data) (cfh/svg-raw-shape? data) (cfh/bool-shape? data)) diff --git a/frontend/src/app/plugins/utils.cljs b/frontend/src/app/plugins/utils.cljs index 1b392d84c..32add7bfd 100644 --- a/frontend/src/app/plugins/utils.cljs +++ b/frontend/src/app/plugins/utils.cljs @@ -7,7 +7,13 @@ (ns app.plugins.utils "RPC for plugins runtime." (:require - [app.util.object :as obj])) + [app.common.data.macros :as dm] + [app.common.uuid :as uuid] + [app.util.object :as obj] + [cuerdas.core :as str])) + +(def uuid-regex + #"\w{8}-\w{4}-\w{4}-\w{4}-\w{12}") (defn get-data ([self attr] @@ -27,4 +33,37 @@ (fn [self] (get-data self attr transform-fn)))) +(defn from-js + "Converts the object back to js" + [obj] + (let [ret (js->clj obj {:keyword-fn (fn [k] (str/camel (name k)))})] + (reduce-kv + (fn [m k v] + (let [v (cond (map? v) + (from-js v) + + (and (string? v) (re-matches uuid-regex v)) + (uuid/uuid v) + + :else v)] + (assoc m (keyword (str/kebab k)) v))) + {} + ret))) + + +(defn to-js + "Converts to javascript an camelize the keys" + [obj] + (let [result + (reduce-kv + (fn [m k v] + (let [v (cond (object? v) (to-js v) + (uuid? v) (dm/str v) + :else v)] + (assoc m (str/camel (name k)) v))) + {} + obj)] + (clj->js result))) + + diff --git a/frontend/src/app/plugins/viewport.cljs b/frontend/src/app/plugins/viewport.cljs index c243d327a..8d647042a 100644 --- a/frontend/src/app/plugins/viewport.cljs +++ b/frontend/src/app/plugins/viewport.cljs @@ -8,15 +8,11 @@ "RPC for plugins runtime." (:require [app.common.data.macros :as dm] - [app.common.geom.point :as gpt] - [app.common.record :as crc] [app.common.record :as crc] [app.common.uuid :as uuid] [app.main.data.workspace.viewport :as dwv] [app.main.data.workspace.zoom :as dwz] [app.main.store :as st] - [app.plugins.page :as page] - [app.plugins.utils :refer [get-data-fn]] [app.util.object :as obj])) (deftype ViewportProxy [] From 67d48435e7f92fc87a2782fe618e54f85b8ef0d4 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 29 Apr 2024 15:43:48 +0200 Subject: [PATCH 4/8] :sparkles: Plugins create svg shapes --- common/src/app/common/svg/shapes_builder.cljc | 111 +++++++++--------- .../src/app/main/data/workspace/groups.cljs | 2 +- .../src/app/main/data/workspace/media.cljs | 9 ++ .../app/main/data/workspace/svg_upload.cljs | 89 +++++++------- frontend/src/app/plugins/api.cljs | 10 +- frontend/src/app/plugins/shape.cljs | 10 +- 6 files changed, 128 insertions(+), 103 deletions(-) diff --git a/common/src/app/common/svg/shapes_builder.cljc b/common/src/app/common/svg/shapes_builder.cljc index 619a81b94..41f25e1e2 100644 --- a/common/src/app/common/svg/shapes_builder.cljc +++ b/common/src/app/common/svg/shapes_builder.cljc @@ -22,6 +22,7 @@ [app.common.svg :as csvg] [app.common.svg.path :as path] [app.common.types.shape :as cts] + [app.common.uuid :as uuid] [cuerdas.core :as str])) (def default-rect @@ -78,67 +79,68 @@ (declare parse-svg-element) (defn create-svg-shapes - [svg-data {:keys [x y]} objects frame-id parent-id selected center?] - (let [[vb-x vb-y vb-width vb-height] (svg-dimensions svg-data) + ([svg-data pos objects frame-id parent-id selected center?] + (create-svg-shapes (uuid/next) svg-data pos objects frame-id parent-id selected center?)) + ([id svg-data {:keys [x y]} objects frame-id parent-id selected center?] + (let [[vb-x vb-y vb-width vb-height] (svg-dimensions svg-data) + unames (cfh/get-used-names objects) + svg-name (str/replace (:name svg-data) ".svg" "") - unames (cfh/get-used-names objects) - svg-name (str/replace (:name svg-data) ".svg" "") + svg-data (-> svg-data + (assoc :x (mth/round + (if center? + (- x vb-x (/ vb-width 2)) + x))) + (assoc :y (mth/round + (if center? + (- y vb-y (/ vb-height 2)) + y))) + (assoc :offset-x vb-x) + (assoc :offset-y vb-y) + (assoc :width vb-width) + (assoc :height vb-height) + (assoc :name svg-name)) - svg-data (-> svg-data - (assoc :x (mth/round - (if center? - (- x vb-x (/ vb-width 2)) - x))) - (assoc :y (mth/round - (if center? - (- y vb-y (/ vb-height 2)) - y))) - (assoc :offset-x vb-x) - (assoc :offset-y vb-y) - (assoc :width vb-width) - (assoc :height vb-height) - (assoc :name svg-name)) + [def-nodes svg-data] + (-> svg-data + (csvg/fix-default-values) + (csvg/fix-percents) + (csvg/extract-defs)) - [def-nodes svg-data] - (-> svg-data - (csvg/fix-default-values) - (csvg/fix-percents) - (csvg/extract-defs)) + ;; In penpot groups have the size of their children. To + ;; respect the imported svg size and empty space let's create + ;; a transparent shape as background to respect the imported + ;; size + background + {:tag :rect + :attrs {:x (dm/str vb-x) + :y (dm/str vb-y) + :width (dm/str vb-width) + :height (dm/str vb-height) + :fill "none" + :id "base-background"} + :hidden true + :content []} - ;; In penpot groups have the size of their children. To - ;; respect the imported svg size and empty space let's create - ;; a transparent shape as background to respect the imported - ;; size - background - {:tag :rect - :attrs {:x (dm/str vb-x) - :y (dm/str vb-y) - :width (dm/str vb-width) - :height (dm/str vb-height) - :fill "none" - :id "base-background"} - :hidden true - :content []} + svg-data (-> svg-data + (assoc :defs def-nodes) + (assoc :content (into [background] (:content svg-data)))) - svg-data (-> svg-data - (assoc :defs def-nodes) - (assoc :content (into [background] (:content svg-data)))) + root-shape (create-svg-root id frame-id parent-id svg-data) + root-id (:id root-shape) - root-shape (create-svg-root frame-id parent-id svg-data) - root-id (:id root-shape) + ;; Create the root shape + root-attrs (-> (:attrs svg-data) + (csvg/format-styles)) - ;; Create the root shape - root-attrs (-> (:attrs svg-data) - (csvg/format-styles)) + [_ children] + (reduce (partial create-svg-children objects selected frame-id root-id svg-data) + [unames []] + (d/enumerate (->> (:content svg-data) + (mapv #(csvg/inherit-attributes root-attrs %)))))] - [_ children] - (reduce (partial create-svg-children objects selected frame-id root-id svg-data) - [unames []] - (d/enumerate (->> (:content svg-data) - (mapv #(csvg/inherit-attributes root-attrs %)))))] - - [root-shape children])) + [root-shape children]))) (defn create-raw-svg [name frame-id {:keys [x y width height offset-x offset-y]} {:keys [attrs] :as data}] @@ -157,12 +159,13 @@ :svg-viewbox vbox}))) (defn create-svg-root - [frame-id parent-id {:keys [name x y width height offset-x offset-y attrs]}] + [id frame-id parent-id {:keys [name x y width height offset-x offset-y attrs]}] (let [props (-> (dissoc attrs :viewBox :view-box :xmlns) (d/without-keys csvg/inheritable-props) (csvg/attrs->props))] (cts/setup-shape - {:type :group + {:id id + :type :group :name name :frame-id frame-id :parent-id parent-id diff --git a/frontend/src/app/main/data/workspace/groups.cljs b/frontend/src/app/main/data/workspace/groups.cljs index be758443e..43bed0489 100644 --- a/frontend/src/app/main/data/workspace/groups.cljs +++ b/frontend/src/app/main/data/workspace/groups.cljs @@ -203,7 +203,7 @@ ptk/WatchEvent (watch [_ state _] (let [selected (wsh/lookup-selected state)] - (rx/of group-shapes nil selected))))) + (rx/of (group-shapes nil selected)))))) (defn ungroup-shapes [ids & {:keys [change-selection?] :or {change-selection? false}}] diff --git a/frontend/src/app/main/data/workspace/media.cljs b/frontend/src/app/main/data/workspace/media.cljs index 815619059..50105e9b0 100644 --- a/frontend/src/app/main/data/workspace/media.cljs +++ b/frontend/src/app/main/data/workspace/media.cljs @@ -459,3 +459,12 @@ (rx/tap on-success) (rx/catch on-error) (rx/finalize #(st/emit! (msg/hide-tag :media-loading))))))))) + +(defn create-svg-shape + [id name svg-string position] + (ptk/reify ::create-svg-shape + ptk/WatchEvent + (watch [_ _ _] + (->> (svg->clj [name svg-string]) + (rx/take 1) + (rx/map #(svg/add-svg-shapes id % position {:change-selection? false})))))) diff --git a/frontend/src/app/main/data/workspace/svg_upload.cljs b/frontend/src/app/main/data/workspace/svg_upload.cljs index fa159cf04..2517a73aa 100644 --- a/frontend/src/app/main/data/workspace/svg_upload.cljs +++ b/frontend/src/app/main/data/workspace/svg_upload.cljs @@ -13,6 +13,7 @@ [app.common.svg :as csvg] [app.common.svg.shapes-builder :as csvg.shapes-builder] [app.common.types.shape-tree :as ctst] + [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.selection :as dws] [app.main.data.workspace.state-helpers :as wsh] @@ -60,52 +61,58 @@ (rx/reduce conj {}))) (defn add-svg-shapes - [svg-data position] - (ptk/reify ::add-svg-shapes - ptk/WatchEvent - (watch [it state _] - (try - (let [page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) - frame-id (ctst/top-nested-frame objects position) - selected (wsh/lookup-selected state) - base (cfh/get-base-shape objects selected) + ([svg-data position] + (add-svg-shapes nil svg-data position nil)) - selected-id (first selected) - selected-frame? (and (= 1 (count selected)) - (= :frame (dm/get-in objects [selected-id :type]))) + ([id svg-data position {:keys [change-selection?] :or {change-selection? false}}] + (ptk/reify ::add-svg-shapes + ptk/WatchEvent + (watch [it state _] + (try + (let [id (d/nilv id (uuid/next)) + page-id (:current-page-id state) + objects (wsh/lookup-page-objects state page-id) + frame-id (ctst/top-nested-frame objects position) + selected (wsh/lookup-selected state) + base (cfh/get-base-shape objects selected) - parent-id (if (or selected-frame? (empty? selected)) - frame-id - (:parent-id base)) + selected-id (first selected) + selected-frame? (and (= 1 (count selected)) + (= :frame (dm/get-in objects [selected-id :type]))) - [new-shape new-children] - (csvg.shapes-builder/create-svg-shapes svg-data position objects frame-id parent-id selected true) + parent-id (if (or selected-frame? (empty? selected)) + frame-id + (:parent-id base)) - changes (-> (pcb/empty-changes it page-id) - (pcb/with-objects objects) - (pcb/add-object new-shape)) + [new-shape new-children] + (csvg.shapes-builder/create-svg-shapes id svg-data position objects frame-id parent-id selected true) - changes (reduce (fn [changes new-child] - (pcb/add-object changes new-child)) - changes - new-children) + changes (-> (pcb/empty-changes it page-id) + (pcb/with-objects objects) + (pcb/add-object new-shape)) - changes (pcb/resize-parents changes - (->> (:redo-changes changes) - (filter #(= :add-obj (:type %))) - (map :id) - (reverse) - (vec))) - undo-id (js/Symbol)] + changes (reduce (fn [changes new-child] + (pcb/add-object changes new-child)) + changes + new-children) - (rx/of (dwu/start-undo-transaction undo-id) - (dch/commit-changes changes) - (dws/select-shapes (d/ordered-set (:id new-shape))) - (ptk/data-event :layout/update {:ids [(:id new-shape)]}) - (dwu/commit-undo-transaction undo-id))) + changes (pcb/resize-parents changes + (->> (:redo-changes changes) + (filter #(= :add-obj (:type %))) + (map :id) + (reverse) + (vec))) + undo-id (js/Symbol)] + + (rx/of (dwu/start-undo-transaction undo-id) + (dch/commit-changes changes) + (when change-selection? + (dws/select-shapes (d/ordered-set (:id new-shape)))) + (ptk/data-event :layout/update {:ids [(:id new-shape)]}) + (dwu/commit-undo-transaction undo-id))) + + (catch :default cause + (js/console.log (.-stack cause)) + (rx/throw {:type :svg-parser + :data cause}))))))) - (catch :default cause - (js/console.log (.-stack cause)) - (rx/throw {:type :svg-parser - :data cause})))))) diff --git a/frontend/src/app/plugins/api.cljs b/frontend/src/app/plugins/api.cljs index 5efaddfc9..5c06da859 100644 --- a/frontend/src/app/plugins/api.cljs +++ b/frontend/src/app/plugins/api.cljs @@ -9,6 +9,7 @@ (:require [app.common.data.macros :as dm] [app.common.files.changes-builder :as cb] + [app.common.geom.point :as gpt] [app.common.record :as cr] [app.common.types.shape :as cts] [app.common.uuid :as uuid] @@ -127,7 +128,14 @@ (createRectangle [_] (create-shape :rect)) - ) + + (createShapeFromSvg + [_ svg-string] + (let [id (uuid/next) + page-id (:current-page-id @st/state)] + (st/emit! (dwm/create-svg-shape id "svg" svg-string (gpt/point 0 0))) + (shape/data->shape-proxy + (dm/get-in @st/state [:workspace-data :pages-index page-id :objects id]))))) (defn create-context [] diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index 56383e38a..27dc930c5 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -67,12 +67,12 @@ (appendChild [self child] (let [parent-id (get-data self :id) child-id (uuid/uuid (obj/get child "id"))] - (st/emit! (udw/relocate-shapes #{ child-id } parent-id 0)))) + (st/emit! (udw/relocate-shapes #{child-id} parent-id 0)))) (insertChild [self index child] (let [parent-id (get-data self :id) child-id (uuid/uuid (obj/get child "id"))] - (st/emit! (udw/relocate-shapes #{ child-id } parent-id index))))) + (st/emit! (udw/relocate-shapes #{child-id} parent-id index))))) (crc/define-properties! ShapeProxy @@ -124,16 +124,14 @@ :set (fn [self value] (let [id (get-data self :id) value (mapv #(utils/from-js %) value)] - (st/emit! (dwc/update-shapes [id] #(assoc % :fills value))))) - } + (st/emit! (dwc/update-shapes [id] #(assoc % :fills value)))))} {:name "strokes" :get #(get-state % :strokes make-strokes) :set (fn [self value] (let [id (get-data self :id) value (mapv #(utils/from-js %) value)] - (st/emit! (dwc/update-shapes [id] #(assoc % :strokes value))))) - }) + (st/emit! (dwc/update-shapes [id] #(assoc % :strokes value)))))}) (cond-> (or (cfh/frame-shape? data) (cfh/group-shape? data) (cfh/svg-raw-shape? data) (cfh/bool-shape? data)) (crc/add-properties! From e30c21a71f4cb58ba6865e3997aac7ca878cfec5 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 29 Apr 2024 17:07:01 +0200 Subject: [PATCH 5/8] :sparkles: Add relative positioning --- frontend/src/app/plugins/shape.cljs | 56 +++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index 27dc930c5..e285efc9a 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -107,6 +107,62 @@ (let [id (get-data self :id)] (st/emit! (udw/update-position id {:y value}))))} + {:name "parentX" + :get (fn [self] + (let [page-id (:current-page-id @st/state) + parent-id (get-state self :parent-id) + parent-x (dm/get-in @st/state [:workspace-data :pages-index page-id :objects parent-id :x])] + (- (get-state self :x) parent-x))) + :set + (fn [self value] + (let [page-id (:current-page-id @st/state) + id (get-data self :id) + parent-id (get-state self :parent-id) + parent-x (dm/get-in @st/state [:workspace-data :pages-index page-id :objects parent-id :x])] + (st/emit! (udw/update-position id {:x (+ parent-x value)}))))} + + {:name "parentY" + :get (fn [self] + (let [page-id (:current-page-id @st/state) + parent-id (get-state self :parent-id) + parent-y (dm/get-in @st/state [:workspace-data :pages-index page-id :objects parent-id :y])] + (- (get-state self :y) parent-y))) + :set + (fn [self value] + (let [page-id (:current-page-id @st/state) + id (get-data self :id) + parent-id (get-state self :parent-id) + parent-y (dm/get-in @st/state [:workspace-data :pages-index page-id :objects parent-id :y])] + (st/emit! (udw/update-position id {:y (+ parent-y value)}))))} + + {:name "frameX" + :get (fn [self] + (let [page-id (:current-page-id @st/state) + frame-id (get-state self :frame-id) + frame-x (dm/get-in @st/state [:workspace-data :pages-index page-id :objects frame-id :x])] + (- (get-state self :x) frame-x))) + :set + (fn [self value] + (let [page-id (:current-page-id @st/state) + id (get-data self :id) + frame-id (get-state self :frame-id) + frame-x (dm/get-in @st/state [:workspace-data :pages-index page-id :objects frame-id :x])] + (st/emit! (udw/update-position id {:x (+ frame-x value)}))))} + + {:name "frameY" + :get (fn [self] + (let [page-id (:current-page-id @st/state) + frame-id (get-state self :frame-id) + frame-y (dm/get-in @st/state [:workspace-data :pages-index page-id :objects frame-id :y])] + (- (get-state self :y) frame-y))) + :set + (fn [self value] + (let [page-id (:current-page-id @st/state) + id (get-data self :id) + frame-id (get-state self :frame-id) + frame-y (dm/get-in @st/state [:workspace-data :pages-index page-id :objects frame-id :y])] + (st/emit! (udw/update-position id {:y (+ frame-y value)}))))} + {:name "width" :get #(get-state % :width)} From 9243ba937d99be869a8a7a57d45c2d02a5657292 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 30 Apr 2024 11:34:50 +0200 Subject: [PATCH 6/8] :sparkles: Add to plugins clone and remove --- .../app/main/data/workspace/selection.cljs | 112 ++++++++++-------- frontend/src/app/plugins/shape.cljs | 16 ++- frontend/src/app/plugins/utils.cljs | 28 +++-- frontend/src/app/plugins/viewport.cljs | 27 +++-- 4 files changed, 112 insertions(+), 71 deletions(-) diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index d13921687..b271f1ae6 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -723,62 +723,76 @@ (gpt/subtract new-pos pt-obj))))) +(defn duplicate-shapes + [ids & {:keys [move-delta? alt-duplication? change-selection? return-ref] + :or {move-delta? false alt-duplication? false change-selection? true return-ref nil}}] + (ptk/reify ::duplicate-shapes + ptk/WatchEvent + (watch [it state _] + (let [page (wsh/lookup-page state) + objects (:objects page) + ids (into #{} + (comp (map (d/getf objects)) + (filter #(ctk/allow-duplicate? objects %)) + (map :id)) + ids)] + (when (seq ids) + (let [obj (get objects (first ids)) + delta (if move-delta? + (calc-duplicate-delta obj state objects) + (gpt/point 0 0)) + + file-id (:current-file-id state) + libraries (wsh/get-libraries state) + library-data (wsh/get-file state file-id) + + changes (->> (prepare-duplicate-changes objects page ids delta it libraries library-data file-id) + (duplicate-changes-update-indices objects ids)) + + tags (or (:tags changes) #{}) + + changes (cond-> changes alt-duplication? (assoc :tags (conj tags :alt-duplication))) + + id-original (first ids) + + new-ids (->> changes + :redo-changes + (filter #(= (:type %) :add-obj)) + (filter #(ids (:old-id %))) + (map #(get-in % [:obj :id])) + (into (d/ordered-set))) + + id-duplicated (first new-ids) + + frames (into #{} + (map #(get-in objects [% :frame-id])) + ids) + undo-id (js/Symbol)] + + ;; Warning: This order is important for the focus mode. + (->> (rx/of + (dwu/start-undo-transaction undo-id) + (dch/commit-changes changes) + (when change-selection? + (select-shapes new-ids)) + (ptk/data-event :layout/update {:ids frames}) + (memorize-duplicated id-original id-duplicated) + (dwu/commit-undo-transaction undo-id)) + (rx/tap #(when (some? return-ref) + (reset! return-ref id-duplicated)))))))))) + (defn duplicate-selected ([move-delta?] (duplicate-selected move-delta? false)) ([move-delta? alt-duplication?] (ptk/reify ::duplicate-selected ptk/WatchEvent - (watch [it state _] + (watch [_ state _] (when (or (not move-delta?) (nil? (get-in state [:workspace-local :transform]))) - (let [page (wsh/lookup-page state) - objects (:objects page) - selected (->> (wsh/lookup-selected state) - (map (d/getf objects)) - (filter #(ctk/allow-duplicate? objects %)) - (map :id) - set)] - (when (seq selected) - (let [obj (get objects (first selected)) - delta (if move-delta? - (calc-duplicate-delta obj state objects) - (gpt/point 0 0)) - - file-id (:current-file-id state) - libraries (wsh/get-libraries state) - library-data (wsh/get-file state file-id) - - changes (->> (prepare-duplicate-changes objects page selected delta it libraries library-data file-id) - (duplicate-changes-update-indices objects selected)) - - tags (or (:tags changes) #{}) - - changes (cond-> changes alt-duplication? (assoc :tags (conj tags :alt-duplication))) - - id-original (first selected) - - new-selected (->> changes - :redo-changes - (filter #(= (:type %) :add-obj)) - (filter #(selected (:old-id %))) - (map #(get-in % [:obj :id])) - (into (d/ordered-set))) - - id-duplicated (first new-selected) - - frames (into #{} - (map #(get-in objects [% :frame-id])) - selected) - undo-id (js/Symbol)] - - ;; Warning: This order is important for the focus mode. - (rx/of - (dwu/start-undo-transaction undo-id) - (dch/commit-changes changes) - (select-shapes new-selected) - (ptk/data-event :layout/update {:ids frames}) - (memorize-duplicated id-original id-duplicated) - (dwu/commit-undo-transaction undo-id)))))))))) + (let [selected (wsh/lookup-selected state)] + (rx/of (duplicate-shapes selected + :move-delta? move-delta? + :alt-duplication? alt-duplication?)))))))) (defn change-hover-state [id value] diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index e285efc9a..b537109d7 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -15,6 +15,8 @@ [app.common.uuid :as uuid] [app.main.data.workspace :as udw] [app.main.data.workspace.changes :as dwc] + [app.main.data.workspace.selection :as dws] + [app.main.data.workspace.shapes :as dwsh] [app.main.store :as st] [app.plugins.utils :as utils :refer [get-data get-data-fn]] [app.util.object :as obj])) @@ -61,8 +63,18 @@ (st/emit! (udw/update-dimensions [id] :width width) (udw/update-dimensions [id] :height height)))) - (clone [_] (.log js/console (clj->js _data))) - (delete [_] (.log js/console (clj->js _data))) + (clone [self] + (let [id (get-data self :id) + page-id (:current-page-id @st/state) + ret-v (atom nil)] + (st/emit! (dws/duplicate-shapes #{id} :change-selection? false :return-ref ret-v)) + (let [new-id (deref ret-v) + shape (dm/get-in @st/state [:workspace-data :pages-index page-id :objects new-id])] + (data->shape-proxy shape)))) + + (remove [self] + (let [id (get-data self :id)] + (st/emit! (dwsh/delete-shapes #{id})))) (appendChild [self child] (let [parent-id (get-data self :id) diff --git a/frontend/src/app/plugins/utils.cljs b/frontend/src/app/plugins/utils.cljs index 32add7bfd..23d8f276a 100644 --- a/frontend/src/app/plugins/utils.cljs +++ b/frontend/src/app/plugins/utils.cljs @@ -8,12 +8,11 @@ "RPC for plugins runtime." (:require [app.common.data.macros :as dm] + [app.common.spec :as us] [app.common.uuid :as uuid] [app.util.object :as obj] - [cuerdas.core :as str])) - -(def uuid-regex - #"\w{8}-\w{4}-\w{4}-\w{4}-\w{12}") + [cuerdas.core :as str] + [promesa.core :as p])) (defn get-data ([self attr] @@ -42,7 +41,7 @@ (let [v (cond (map? v) (from-js v) - (and (string? v) (re-matches uuid-regex v)) + (and (string? v) (re-matches us/uuid-rx v)) (uuid/uuid v) :else v)] @@ -50,7 +49,6 @@ {} ret))) - (defn to-js "Converts to javascript an camelize the keys" [obj] @@ -65,5 +63,19 @@ obj)] (clj->js result))) - - +(defn result-p + "Creates a pair of atom+promise. The promise will be resolved when the atom gets a value. + We use this to return the promise to the library clients and resolve its value when a value is passed + to the atom" + [] + (let [ret-v (atom nil) + ret-p + (p/create + (fn [resolve _] + (add-watch + ret-v + ::watcher + (fn [_ _ _ value] + (remove-watch ret-v ::watcher) + (resolve value)))))] + [ret-v ret-p])) diff --git a/frontend/src/app/plugins/viewport.cljs b/frontend/src/app/plugins/viewport.cljs index 8d647042a..656a65674 100644 --- a/frontend/src/app/plugins/viewport.cljs +++ b/frontend/src/app/plugins/viewport.cljs @@ -9,6 +9,7 @@ (:require [app.common.data.macros :as dm] [app.common.record :as crc] + [app.common.spec :as us] [app.common.uuid :as uuid] [app.main.data.workspace.viewport :as dwv] [app.main.data.workspace.zoom :as dwz] @@ -46,16 +47,17 @@ :set (fn [_ value] (let [new-x (obj/get value "x") - new-y (obj/get value "y") - vb (dm/get-in @st/state [:workspace-local :vbox]) - old-x (+ (:x vb) (/ (:width vb) 2)) - old-y (+ (:y vb) (/ (:height vb) 2)) - delta-x (- new-x old-x) - delta-y (- new-y old-y) - to-position - {:x #(+ % delta-x) - :y #(+ % delta-y)}] - (st/emit! (dwv/update-viewport-position to-position))))} + new-y (obj/get value "y")] + (when (and (us/safe-number? new-x) (us/safe-number? new-y)) + (let [vb (dm/get-in @st/state [:workspace-local :vbox]) + old-x (+ (:x vb) (/ (:width vb) 2)) + old-y (+ (:y vb) (/ (:height vb) 2)) + delta-x (- new-x old-x) + delta-y (- new-y old-y) + to-position + {:x #(+ % delta-x) + :y #(+ % delta-y)}] + (st/emit! (dwv/update-viewport-position to-position))))))} {:name "zoom" :get @@ -63,8 +65,9 @@ (dm/get-in @st/state [:workspace-local :zoom])) :set (fn [_ value] - (let [z (dm/get-in @st/state [:workspace-local :zoom])] - (st/emit! (dwz/set-zoom (/ value z)))))} + (when (us/safe-number? value) + (let [z (dm/get-in @st/state [:workspace-local :zoom])] + (st/emit! (dwz/set-zoom (/ value z))))))} {:name "bounds" :get From fde0bcfd3e9583f74208f7755149a3962cabf8df Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 3 May 2024 13:44:31 +0200 Subject: [PATCH 7/8] :sparkles: Add grid layout options to context --- .../app/main/data/workspace/shape_layout.cljs | 18 +- frontend/src/app/plugins/grid.cljs | 162 ++++++++++++++++++ frontend/src/app/plugins/shape.cljs | 67 +++++--- frontend/src/app/plugins/utils.cljs | 38 ++-- 4 files changed, 245 insertions(+), 40 deletions(-) create mode 100644 frontend/src/app/plugins/grid.cljs diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index 37e40cf91..b3c0d513f 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -72,7 +72,7 @@ :layout-grid-columns []}) (defn get-layout-initializer - [type from-frame?] + [type from-frame? calculate-params?] (let [[initial-layout-data calculate-params] (case type :flex [initial-flex-layout flex/calculate-params] @@ -87,9 +87,11 @@ (cond-> (not from-frame?) (assoc :show-content true :hide-in-viewer true))) - params (calculate-params objects (cfh/get-immediate-children objects (:id shape)) shape)] + params (when calculate-params? + (calculate-params objects (cfh/get-immediate-children objects (:id shape)) shape))] (cond-> (merge shape params) - (= type :grid) (-> (ctl/assign-cells objects) ctl/reorder-grid-children)))))) + (= type :grid) + (-> (ctl/assign-cells objects) ctl/reorder-grid-children)))))) ;; Never call this directly but through the data-event `:layout/update` ;; Otherwise a lot of cycle dependencies could be generated @@ -124,7 +126,7 @@ (ptk/reify ::finalize)) (defn create-layout-from-id - [id type from-frame?] + [id type & {:keys [from-frame? calculate-params?] :or {from-frame? false calculate-params? true}}] (dm/assert! "expected uuid for `id`" (uuid? id)) @@ -135,7 +137,7 @@ (let [objects (wsh/lookup-page-objects state) parent (get objects id) undo-id (js/Symbol) - layout-initializer (get-layout-initializer type from-frame?)] + layout-initializer (get-layout-initializer type from-frame? calculate-params?)] (rx/of (dwu/start-undo-transaction undo-id) (dch/update-shapes [id] layout-initializer {:with-objects? true}) @@ -177,7 +179,7 @@ (dwse/select-shapes ordered-ids) (dwsh/create-artboard-from-selection new-shape-id parent-id group-index (:name (first selected-shapes))) (cl/remove-all-fills [new-shape-id] {:color clr/black :opacity 1}) - (create-layout-from-id new-shape-id type false) + (create-layout-from-id new-shape-id type) (dch/update-shapes [new-shape-id] #(assoc % :layout-item-h-sizing :auto :layout-item-v-sizing :auto)) (dch/update-shapes selected #(assoc % :layout-item-h-sizing :fix :layout-item-v-sizing :fix)) (dwsh/delete-shapes page-id selected) @@ -188,7 +190,7 @@ (rx/of (dwsh/create-artboard-from-selection new-shape-id) (cl/remove-all-fills [new-shape-id] {:color clr/black :opacity 1}) - (create-layout-from-id new-shape-id type false) + (create-layout-from-id new-shape-id type) (dch/update-shapes [new-shape-id] #(assoc % :layout-item-h-sizing :auto :layout-item-v-sizing :auto)) (dch/update-shapes selected #(assoc % :layout-item-h-sizing :fix :layout-item-v-sizing :fix)))) @@ -227,7 +229,7 @@ (rx/of (dwu/start-undo-transaction undo-id) (if (and single? is-frame?) - (create-layout-from-id (first selected) type true) + (create-layout-from-id (first selected) type :from-frame? true) (create-layout-from-selection type)) (dwu/commit-undo-transaction undo-id)))))) diff --git a/frontend/src/app/plugins/grid.cljs b/frontend/src/app/plugins/grid.cljs new file mode 100644 index 000000000..7dc46bc9e --- /dev/null +++ b/frontend/src/app/plugins/grid.cljs @@ -0,0 +1,162 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.plugins.grid + (:require + [app.common.data :as d] + [app.common.record :as crc] + [app.common.spec :as us] + [app.common.types.shape.layout :as ctl] + [app.main.data.workspace.shape-layout :as dwsl] + [app.main.store :as st] + [app.plugins.utils :as utils :refer [get-data get-state]])) + +(defn- make-tracks + [tracks] + (.freeze + js/Object + (apply array (->> tracks (map utils/to-js))))) + +(deftype GridLayout [_data] + Object + + (addRow + [self type value] + (let [id (get-data self :id) + type (keyword type)] + (st/emit! (dwsl/add-layout-track #{id} :row {:type type :value value})))) + + (addRowAtIndex + [self type value index] + (let [id (get-data self :id) + type (keyword type)] + (st/emit! (dwsl/add-layout-track #{id} :row {:type type :value value} index)))) + + (addColumn + [self type value] + (let [id (get-data self :id) + type (keyword type)] + (st/emit! (dwsl/add-layout-track #{id} :column {:type type :value value})))) + + (addColumnAtIndex + [self type value index] + (let [id (get-data self :id) + type (keyword type)] + (st/emit! (dwsl/add-layout-track #{id} :column {:type type :value value} index)))) + + (removeRow + [self index] + (let [id (get-data self :id)] + (st/emit! (dwsl/remove-layout-track #{id} :row index)))) + + (removeColumn + [self index] + (let [id (get-data self :id)] + (st/emit! (dwsl/remove-layout-track #{id} :column index)))) + + (setColumn + [self index type value] + (let [id (get-data self :id) + type (keyword type)] + (st/emit! (dwsl/change-layout-track #{id} :column index (d/without-nils {:type type :value value}))))) + + (setRow + [self index type value] + (let [id (get-data self :id) + type (keyword type)] + (st/emit! (dwsl/change-layout-track #{id} :row index (d/without-nils {:type type :value value}))))) + + (remove + [self] + (let [id (get-data self :id)] + (st/emit! (dwsl/remove-layout #{id}))))) + +(defn grid-layout-proxy + [data] + (-> (GridLayout. data) + (crc/add-properties! + {:name "dir" + :get #(get-state % :layout-grid-dir d/name) + :set + (fn [self value] + (let [id (get-data self :id) + value (keyword value)] + (when (contains? ctl/grid-direction-types value) + (st/emit! (dwsl/update-layout #{id} {:layout-grid-dir value})))))} + + {:name "rows" + :get #(get-state % :layout-grid-rows make-tracks)} + + {:name "columns" + :get #(get-state % :layout-grid-columns make-tracks)} + + {:name "alignItems" + :get #(get-state % :layout-align-items d/name) + :set + (fn [self value] + (let [id (get-data self :id) + value (keyword value)] + (when (contains? ctl/align-items-types value) + (st/emit! (dwsl/update-layout #{id} {:layout-align-items value})))))} + + {:name "alignContent" + :get #(get-state % :layout-align-content d/name) + :set + (fn [self value] + (let [id (get-data self :id) + value (keyword value)] + (when (contains? ctl/align-content-types value) + (st/emit! (dwsl/update-layout #{id} {:layout-align-content value})))))} + + {:name "justifyItems" + :get #(get-state % :layout-justify-items d/name) + :set + (fn [self value] + (let [id (get-data self :id) + value (keyword value)] + (when (contains? ctl/justify-items-types value) + (st/emit! (dwsl/update-layout #{id} {:layout-justify-items value})))))} + + {:name "justifyContent" + :get #(get-state % :layout-justify-content d/name) + :set + (fn [self value] + (let [id (get-data self :id) + value (keyword value)] + (when (contains? ctl/justify-content-types value) + (st/emit! (dwsl/update-layout #{id} {:layout-justify-content value})))))} + + {:name "rowGap" + :get #(:row-gap (get-state % :layout-gap)) + :set + (fn [self value] + (let [id (get-data self :id)] + (when (us/safe-int? value) + (st/emit! (dwsl/update-layout #{id} {:layout-gap {:row-gap value}})))))} + + {:name "columnGap" + :get #(:column-gap (get-state % :layout-gap)) + :set + (fn [self value] + (let [id (get-data self :id)] + (when (us/safe-int? value) + (st/emit! (dwsl/update-layout #{id} {:layout-gap {:column-gap value}})))))} + + {:name "verticalPadding" + :get #(:p1 (get-state % :layout-padding)) + :set + (fn [self value] + (let [id (get-data self :id)] + (when (us/safe-int? value) + (st/emit! (dwsl/update-layout #{id} {:layout-padding {:p1 value :p3 value}})))))} + + {:name "horizontalPadding" + :get #(:p2 (get-state % :layout-padding)) + :set + (fn [self value] + (let [id (get-data self :id)] + (when (us/safe-int? value) + (st/emit! (dwsl/update-layout #{id} {:layout-padding {:p2 value :p4 value}})))))}))) diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index b537109d7..e4b466b2e 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -7,7 +7,6 @@ (ns app.plugins.shape "RPC for plugins runtime." (:require - [app.common.data :as d] [app.common.data.macros :as dm] [app.common.files.helpers :as cfh] [app.common.record :as crc] @@ -16,9 +15,11 @@ [app.main.data.workspace :as udw] [app.main.data.workspace.changes :as dwc] [app.main.data.workspace.selection :as dws] + [app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.shapes :as dwsh] [app.main.store :as st] - [app.plugins.utils :as utils :refer [get-data get-data-fn]] + [app.plugins.grid :as grid] + [app.plugins.utils :as utils :refer [get-data get-data-fn get-state]] [app.util.object :as obj])) (declare data->shape-proxy) @@ -40,23 +41,8 @@ (let [page-id (:current-page-id @st/state)] (dm/get-in @st/state [:workspace-data :pages-index page-id :objects shape-id]))) -(defn- get-state - ([self attr] - (let [id (get-data self :id) - page-id (d/nilv (get-data self :page-id) (:current-page-id @st/state))] - (dm/get-in @st/state [:workspace-data :pages-index page-id :objects id attr]))) - ([self attr mapfn] - (-> (get-state self attr) - (mapfn)))) - -(deftype ShapeProxy [^:mutable #_:clj-kondo/ignore _data] +(deftype ShapeProxy [#_:clj-kondo/ignore _data] Object - (getChildren - [self] - (apply array (->> (get-state self :shapes) - (map locate-shape) - (map data->shape-proxy)))) - (resize [self width height] (let [id (get-data self :id)] @@ -76,6 +62,13 @@ (let [id (get-data self :id)] (st/emit! (dwsh/delete-shapes #{id})))) + ;; Only for frames + groups + booleans + (getChildren + [self] + (apply array (->> (get-state self :shapes) + (map locate-shape) + (map data->shape-proxy)))) + (appendChild [self child] (let [parent-id (get-data self :id) child-id (uuid/uuid (obj/get child "id"))] @@ -84,7 +77,16 @@ (insertChild [self index child] (let [parent-id (get-data self :id) child-id (uuid/uuid (obj/get child "id"))] - (st/emit! (udw/relocate-shapes #{child-id} parent-id index))))) + (st/emit! (udw/relocate-shapes #{child-id} parent-id index)))) + + ;; Only for frames + (addFlexLayout [self] + (let [id (get-data self :id)] + (st/emit! (dwsl/create-layout-from-id id :flex :from-frame? true :calculate-params? false)))) + + (addGridLayout [self] + (let [id (get-data self :id)] + (st/emit! (dwsl/create-layout-from-id id :grid :from-frame? true :calculate-params? false))))) (crc/define-properties! ShapeProxy @@ -206,6 +208,32 @@ {:name "children" :get #(.getChildren ^js %)})) + (cond-> (not (or (cfh/frame-shape? data) (cfh/group-shape? data) (cfh/svg-raw-shape? data) (cfh/bool-shape? data))) + (-> (obj/unset! "appendChild") + (obj/unset! "insertChild") + (obj/unset! "getChildren"))) + + (cond-> (cfh/frame-shape? data) + (-> (crc/add-properties! + {:name "grid" + :get + (fn [self] + (let [layout (get-state self :layout)] + (when (= :grid layout) + (grid/grid-layout-proxy data))))}) + + #_(crc/add-properties! + {:name "flex" + :get + (fn [self] + (let [layout (get-state self :layout)] + (when (= :flex layout) + (flex-layout-proxy data))))}))) + + (cond-> (not (cfh/frame-shape? data)) + (-> (obj/unset! "addGridLayout") + (obj/unset! "addFlexLayout"))) + (cond-> (cfh/text-shape? data) (crc/add-properties! {:name "characters" @@ -213,4 +241,3 @@ :set (fn [self value] (let [id (get-data self :id)] (st/emit! (dwc/update-shapes [id] #(txt/change-text % value)))))})))) - diff --git a/frontend/src/app/plugins/utils.cljs b/frontend/src/app/plugins/utils.cljs index 23d8f276a..35022c36e 100644 --- a/frontend/src/app/plugins/utils.cljs +++ b/frontend/src/app/plugins/utils.cljs @@ -7,9 +7,11 @@ (ns app.plugins.utils "RPC for plugins runtime." (:require + [app.common.data :as d] [app.common.data.macros :as dm] [app.common.spec :as us] [app.common.uuid :as uuid] + [app.main.store :as st] [app.util.object :as obj] [cuerdas.core :as str] [promesa.core :as p])) @@ -32,22 +34,34 @@ (fn [self] (get-data self attr transform-fn)))) +(defn get-state + ([self attr] + (let [id (get-data self :id) + page-id (d/nilv (get-data self :page-id) (:current-page-id @st/state))] + (dm/get-in @st/state [:workspace-data :pages-index page-id :objects id attr]))) + ([self attr mapfn] + (-> (get-state self attr) + (mapfn)))) + (defn from-js "Converts the object back to js" - [obj] - (let [ret (js->clj obj {:keyword-fn (fn [k] (str/camel (name k)))})] - (reduce-kv - (fn [m k v] - (let [v (cond (map? v) - (from-js v) + ([obj] + (from-js obj identity)) + ([obj vfn] + (let [ret (js->clj obj {:keyword-fn (fn [k] (str/camel (name k)))})] + (reduce-kv + (fn [m k v] + (let [k (keyword (str/kebab k)) + v (cond (map? v) + (from-js v) - (and (string? v) (re-matches us/uuid-rx v)) - (uuid/uuid v) + (and (string? v) (re-matches us/uuid-rx v)) + (uuid/uuid v) - :else v)] - (assoc m (keyword (str/kebab k)) v))) - {} - ret))) + :else (vfn k v))] + (assoc m k v))) + {} + ret)))) (defn to-js "Converts to javascript an camelize the keys" From ca7f17efd1d2b9eaf98af68788875d6cf3532f99 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 6 May 2024 12:01:39 +0200 Subject: [PATCH 8/8] :sparkles: Add items to grid cells --- common/src/app/common/types/shape/layout.cljc | 36 +++++++++++++------ .../app/main/data/workspace/svg_upload.cljs | 1 - .../app/main/data/workspace/transforms.cljs | 23 ++++++++---- .../src/app/main/data/workspace/zoom.cljs | 1 - frontend/src/app/plugins/grid.cljs | 15 ++++++-- 5 files changed, 55 insertions(+), 21 deletions(-) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index d6c1178bd..7f5e6e83a 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -1281,6 +1281,21 @@ (let [cells+index (d/enumerate cells)] (d/seek #(in-cell? (second %) row column) cells+index))) +(defn free-cell-shapes + "Removes the shape-ids from the cells previously assigned." + [parent shape-ids] + (let [shape-ids (set shape-ids)] + (letfn [(free-cells + [cells] + (reduce-kv + (fn [m k v] + (if (some shape-ids (:shapes v)) + (assoc-in m [k :shapes] []) + m)) + cells + cells))] + (update parent :layout-grid-cells free-cells)))) + (defn push-into-cell "Push the shapes into the row/column cell and moves the rest" [parent shape-ids row column] @@ -1295,16 +1310,17 @@ ;; Move shift the `shapes` attribute between cells (->> (range start-index (inc to-index)) (map vector shape-ids) - (reduce (fn [[parent cells] [shape-id idx]] - ;; If the shape to put in a cell is the same that is already in the cell we do nothing - (if (= shape-id (get-in parent [:layout-grid-cells (get-in cells [idx :id]) :shapes 0])) - [parent cells] - (let [[parent cells] (free-cell-push parent cells idx)] - [(update-in parent [:layout-grid-cells (get-in cells [idx :id])] - assoc :position :manual - :shapes [shape-id]) - cells]))) - [parent cells]) + (reduce + (fn [[parent cells] [shape-id idx]] + ;; If the shape to put in a cell is the same that is already in the cell we do nothing + (if (= shape-id (get-in parent [:layout-grid-cells (get-in cells [idx :id]) :shapes 0])) + [parent cells] + (let [[parent cells] (free-cell-push parent cells idx)] + [(update-in parent [:layout-grid-cells (get-in cells [idx :id])] + assoc :position :manual + :shapes [shape-id]) + cells]))) + [parent cells]) (first))) parent))) diff --git a/frontend/src/app/main/data/workspace/svg_upload.cljs b/frontend/src/app/main/data/workspace/svg_upload.cljs index 2517a73aa..d81d8ecb3 100644 --- a/frontend/src/app/main/data/workspace/svg_upload.cljs +++ b/frontend/src/app/main/data/workspace/svg_upload.cljs @@ -112,7 +112,6 @@ (dwu/commit-undo-transaction undo-id))) (catch :default cause - (js/console.log (.-stack cause)) (rx/throw {:type :svg-parser :data cause}))))))) diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 05289294d..4fc56d2ce 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -831,7 +831,7 @@ :ignore-constraints false :ignore-snap-pixel true})))))) -(defn- move-shapes-to-frame +(defn move-shapes-to-frame [ids frame-id drop-index [row column :as cell]] (ptk/reify ::move-shapes-to-frame ptk/WatchEvent @@ -923,24 +923,32 @@ changes (-> (pcb/empty-changes it page-id) (pcb/with-objects objects) + ;; Remove layout-item properties when moving a shape outside a layout (cond-> (not (ctl/any-layout? objects frame-id)) (pcb/update-shapes moving-shapes-ids ctl/remove-layout-item-data)) + ;; Remove the swap slots if it is moving to a different component - (pcb/update-shapes child-heads - (fn [shape] - (cond-> shape - (not= component-main-frame (ctn/find-component-main objects shape false)) - (ctk/remove-swap-slot)))) + (pcb/update-shapes + child-heads + (fn [shape] + (cond-> shape + (not= component-main-frame (ctn/find-component-main objects shape false)) + (ctk/remove-swap-slot)))) + ;; Remove component-root property when moving a shape inside a component (cond-> (ctn/get-instance-root objects frame) (pcb/update-shapes moving-shapes-children-ids #(dissoc % :component-root))) + ;; Add component-root property when moving a component outside a component (cond-> (not (ctn/get-instance-root objects frame)) (pcb/update-shapes child-heads #(assoc % :component-root true))) + (pcb/update-shapes moving-shapes-ids #(cond-> % (cfh/frame-shape? %) (assoc :hide-in-viewer true))) (pcb/update-shapes shape-ids-to-detach ctk/detach-shape) (pcb/change-parent frame-id moving-shapes drop-index) + + ;; Change the grid cell in a grid layout (cond-> (ctl/grid-layout? objects frame-id) (-> (pcb/update-shapes [frame-id] @@ -948,7 +956,8 @@ (-> frame ;; Assign the cell when pushing into a specific grid cell (cond-> (some? cell) - (-> (ctl/push-into-cell moving-shapes-ids row column) + (-> (ctl/free-cell-shapes moving-shapes-ids) + (ctl/push-into-cell moving-shapes-ids row column) (ctl/assign-cells objects))) (ctl/assign-cell-positions objects))) {:with-objects? true}) diff --git a/frontend/src/app/main/data/workspace/zoom.cljs b/frontend/src/app/main/data/workspace/zoom.cljs index 8672544fd..6499f93a2 100644 --- a/frontend/src/app/main/data/workspace/zoom.cljs +++ b/frontend/src/app/main/data/workspace/zoom.cljs @@ -127,7 +127,6 @@ state (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) - _ (prn "??" (->> ids (map #(get objects %)) (map :name))) srect (->> ids (map #(get objects %)) (gsh/shapes->rect))] diff --git a/frontend/src/app/plugins/grid.cljs b/frontend/src/app/plugins/grid.cljs index 7dc46bc9e..07cb39c9a 100644 --- a/frontend/src/app/plugins/grid.cljs +++ b/frontend/src/app/plugins/grid.cljs @@ -10,9 +10,13 @@ [app.common.record :as crc] [app.common.spec :as us] [app.common.types.shape.layout :as ctl] + [app.common.uuid :as uuid] [app.main.data.workspace.shape-layout :as dwsl] + [app.main.data.workspace.transforms :as dwt] [app.main.store :as st] - [app.plugins.utils :as utils :refer [get-data get-state]])) + [app.plugins.utils :as utils :refer [get-data get-state]] + [app.util.object :as obj] + [potok.v2.core :as ptk])) (defn- make-tracks [tracks] @@ -72,7 +76,14 @@ (remove [self] (let [id (get-data self :id)] - (st/emit! (dwsl/remove-layout #{id}))))) + (st/emit! (dwsl/remove-layout #{id})))) + + (appendChild + [self child row column] + (let [parent-id (get-data self :id) + child-id (uuid/uuid (obj/get child "id"))] + (st/emit! (dwt/move-shapes-to-frame #{child-id} parent-id nil [row column]) + (ptk/data-event :layout/update {:ids [parent-id]}))))) (defn grid-layout-proxy [data]