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 []