From ce7852329ab90cec857773ee882d335546358445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Fri, 11 Nov 2022 16:12:13 +0100 Subject: [PATCH] :tada: Add the ability to create components in bulk from images --- frontend/src/app/main/data/workspace.cljs | 59 +-------- .../src/app/main/data/workspace/media.cljs | 124 +++++++++++++++++- .../src/app/main/data/workspace/shapes.cljs | 11 +- .../main/data/workspace/state_helpers.cljs | 7 + .../app/main/ui/workspace/sidebar/assets.cljs | 39 +++++- 5 files changed, 170 insertions(+), 70 deletions(-) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index eb8916711..0a3d96d43 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -14,7 +14,6 @@ [app.common.geom.proportions :as gpr] [app.common.geom.shapes :as gsh] [app.common.geom.shapes.rect :as gpsr] - [app.common.logging :as log] [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] [app.common.spec :as us] @@ -56,7 +55,6 @@ [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.shapes-update-layout :as dwul] [app.main.data.workspace.state-helpers :as wsh] - [app.main.data.workspace.svg-upload :as svg] [app.main.data.workspace.thumbnails :as dwth] [app.main.data.workspace.transforms :as dwt] [app.main.data.workspace.undo :as dwu] @@ -1604,58 +1602,6 @@ (update [_ state] (dissoc state :remove-graphics)))) -(defn- create-shapes-svg - [file-id objects pos media-obj] - (let [path (cfg/resolve-file-media media-obj) - - upload-images - (fn [svg-data] - (->> (svg/upload-images svg-data file-id) - (rx/map #(assoc svg-data :image-data %)))) - - process-svg - (fn [svg-data] - (let [[shape children] - (svg/create-svg-shapes svg-data pos objects uuid/zero #{} false)] - [shape children]))] - - (->> (http/send! {:method :get :uri path :mode :no-cors}) - (rx/map :body) - (rx/map #(vector (:name media-obj) %)) - (rx/merge-map dwm/svg->clj) - (rx/merge-map upload-images) - (rx/map process-svg) - (rx/catch ; When error downloading media-obj, skip it and continue with next one - #(log/error :hint "error downloading file" - :path path - :name (:name media-obj) - :cause %))))) - -(defn- create-shapes-img - [pos {:keys [name width height id mtype] :as media-obj}] - (let [group-shape (cts/make-shape :group - {:x (:x pos) - :y (:y pos) - :width width - :height height} - {:name name - :frame-id uuid/zero - :parent-id uuid/zero}) - - img-shape (cts/make-shape :image - {:x (:x pos) - :y (:y pos) - :width width - :height height - :metadata {:id id - :width width - :height height - :mtype mtype}} - {:name name - :frame-id uuid/zero - :parent-id (:id group-shape)})] - (rx/of [group-shape [img-shape]]))) - (defn- remove-graphic [it file-data page [index [media-obj pos]]] (let [process-shapes @@ -1689,8 +1635,9 @@ (dch/commit-changes changes))) shapes (if (= (:mtype media-obj) "image/svg+xml") - (create-shapes-svg (:id file-data) (:objects page) pos media-obj) - (create-shapes-img pos media-obj))] + (->> (dwm/load-and-parse-svg media-obj) + (rx/mapcat (partial dwm/create-shapes-svg (:id file-data) (:objects page) pos))) + (dwm/create-shapes-img pos media-obj))] (rx/concat (rx/of (update-remove-graphics index)) diff --git a/frontend/src/app/main/data/workspace/media.cljs b/frontend/src/app/main/data/workspace/media.cljs index 3ff712de5..b94125f8f 100644 --- a/frontend/src/app/main/data/workspace/media.cljs +++ b/frontend/src/app/main/data/workspace/media.cljs @@ -7,11 +7,20 @@ (ns app.main.data.workspace.media (:require [app.common.exceptions :as ex] + [app.common.logging :as log] + [app.common.pages.changes-builder :as pcb] [app.common.spec :as us] + [app.common.types.container :as ctn] + [app.common.types.shape :as cts] + [app.common.types.shape-tree :as ctst] + [app.common.uuid :as uuid] + [app.config :as cfg] [app.main.data.media :as dmm] [app.main.data.messages :as dm] + [app.main.data.workspace.changes :as dch] [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.shapes :as dwsh] + [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.svg-upload :as svg] [app.main.repo :as rp] [app.main.store :as st] @@ -202,7 +211,6 @@ :on-image #(st/emit! (dwl/add-media %)))] (process-media-objects params))) - ;; TODO: it is really need handle SVG here, looks like it already ;; handled separately (defn upload-media-workspace @@ -216,6 +224,120 @@ ;; --- Upload File Media objects +(defn load-and-parse-svg + "Load the contents of a media-obj of type svg, and parse it + into a clojure structure." + [media-obj] + (let [path (cfg/resolve-file-media media-obj)] + (->> (http/send! {:method :get :uri path :mode :no-cors}) + (rx/map :body) + (rx/map #(vector (:name media-obj) %)) + (rx/merge-map svg->clj) + (rx/catch ; When error downloading media-obj, skip it and continue with next one + #(log/error :msg (str "Error downloading " (:name media-obj) " from " path) + :hint (ex-message %) + :error %))))) + +(defn create-shapes-svg + "Convert svg elements into penpot shapes." + [file-id objects pos svg-data] + (let [upload-images + (fn [svg-data] + (->> (svg/upload-images svg-data file-id) + (rx/map #(assoc svg-data :image-data %)))) + + process-svg + (fn [svg-data] + (let [[shape children] + (svg/create-svg-shapes svg-data pos objects uuid/zero #{} false)] + [shape children]))] + + (->> (upload-images svg-data) + (rx/map process-svg)))) + +(defn create-shapes-img + "Convert a media object that contains a bitmap image into shapes, + one shape of type :image and one group that contains it." + [pos {:keys [name width height id mtype] :as media-obj}] + (let [group-shape (cts/make-shape :group + {:x (:x pos) + :y (:y pos) + :width width + :height height} + {:name name + :frame-id uuid/zero + :parent-id uuid/zero}) + + img-shape (cts/make-shape :image + {:x (:x pos) + :y (:y pos) + :width width + :height height + :metadata {:id id + :width width + :height height + :mtype mtype}} + {:name name + :frame-id uuid/zero + :parent-id (:id group-shape)})] + (rx/of [group-shape [img-shape]]))) + +(defn- add-shapes-and-component + [it file-data page name [shape children]] + (let [page' (reduce #(ctst/add-shape (:id %2) %2 %1 uuid/zero (:parent-id %2) nil false) + page + (cons shape children)) + + shape' (ctn/get-shape page' (:id shape)) + + [component-shape component-shapes updated-shapes] + (ctn/make-component-shape shape' (:objects page') (:id file-data) true) + + changes (-> (pcb/empty-changes it) + (pcb/with-page page') + (pcb/with-objects (:objects page')) + (pcb/with-library-data file-data) + (pcb/add-objects (cons shape children)) + (pcb/add-component (:id component-shape) + "" + name + component-shapes + updated-shapes + (:id shape) + (:id page)))] + + (dch/commit-changes changes))) + +(defn- process-img-component + [media-obj] + (ptk/reify ::process-img-component + ptk/WatchEvent + (watch [it state _] + (let [file-data (wsh/get-local-file state) + page (wsh/lookup-page state) + pos (wsh/viewport-center state)] + (->> (create-shapes-img pos media-obj) + (rx/map (partial add-shapes-and-component it file-data page (:name media-obj)))))))) + +(defn- process-svg-component + [svg-data] + (ptk/reify ::process-svg-component + ptk/WatchEvent + (watch [it state _] + (let [file-data (wsh/get-local-file state) + page (wsh/lookup-page state) + pos (wsh/viewport-center state)] + (->> (create-shapes-svg (:id file-data) (:objects page) pos svg-data) + (rx/map (partial add-shapes-and-component it file-data page (:name svg-data)))))))) + +(defn upload-media-components + [params] + (let [params (assoc params + :local? false + :on-image #(st/emit! (process-img-component %)) + :on-svg #(st/emit! (process-svg-component %)))] + (process-media-objects params))) + (s/def ::object-id ::us/uuid) (s/def ::clone-media-objects-params diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index 72012c8d6..93f7b8264 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -276,11 +276,6 @@ (dch/commit-changes changes) (dwsul/update-layout-positions layout-ids))))))) -(defn- viewport-center - [state] - (let [{:keys [x y width height]} (get-in state [:workspace-local :vbox])] - [(+ x (/ width 2)) (+ y (/ height 2))])) - (defn create-and-add-shape [type frame-x frame-y data] (ptk/reify ::create-and-add-shape @@ -288,9 +283,9 @@ (watch [_ state _] (let [{:keys [width height]} data - [vbc-x vbc-y] (viewport-center state) - x (:x data (- vbc-x (/ width 2))) - y (:y data (- vbc-y (/ height 2))) + vbc (wsh/viewport-center state) + x (:x data (- (:x vbc) (/ width 2))) + y (:y data (- (:y vbc) (/ height 2))) page-id (:current-page-id state) frame-id (-> (wsh/lookup-page-objects state page-id) (ctst/top-nested-frame {:x frame-x :y frame-y})) diff --git a/frontend/src/app/main/data/workspace/state_helpers.cljs b/frontend/src/app/main/data/workspace/state_helpers.cljs index 5804e1434..f1a8acc35 100644 --- a/frontend/src/app/main/data/workspace/state_helpers.cljs +++ b/frontend/src/app/main/data/workspace/state_helpers.cljs @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.pages.helpers :as cph] [app.common.path.commands :as upc])) @@ -139,3 +140,9 @@ (as-> children $ (d/mapm (set-content-modifiers state) $)))) + +(defn viewport-center + [state] + (let [{:keys [x y width height]} (get-in state [:workspace-local :vbox])] + (gpt/point (+ x (/ width 2)) (+ y (/ height 2))))) + diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index fc12a7da3..d77fb28e1 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -552,8 +552,9 @@ (mf/defc components-box [{:keys [file-id local? components listing-thumbs? open? reverse-sort? open-groups selected-assets on-asset-click on-assets-delete on-clear-selection] :as props}] - (let [state (mf/use-state {:renaming nil - :component-id nil}) + (let [input-ref (mf/use-ref nil) + state (mf/use-state {:renaming nil + :component-id nil}) menu-state (mf/use-state auto-pos-menu-state) @@ -566,6 +567,24 @@ groups (group-assets components reverse-sort?) + components-v2 (mf/use-ctx ctx/components-v2) + + add-component + (mf/use-fn + (fn [] + #(st/emit! (dwl/set-assets-box-open file-id :components true)) + (dom/click (mf/ref-val input-ref)))) + + on-file-selected + (mf/use-fn + (mf/deps file-id) + (fn [blobs] + (let [params {:file-id file-id + :blobs (seq blobs)}] + (st/emit! (dwm/upload-media-components params) + (ptk/event ::ev/event {::ev/name "add-asset-to-library" + :asset-type "components"}))))) + on-duplicate (mf/use-fn (mf/deps @state) @@ -694,6 +713,16 @@ :box :components :assets-count (count components) :open? open?} + (when local? + [:& asset-section-block {:role :title-button} + (when components-v2 + [:div.assets-button {:on-click add-component} + i/plus + [:& file-uploader {:accept cm/str-image-types + :multi true + :ref input-ref + :on-selected on-file-selected}]])]) + [:& asset-section-block {:role :content} [:& components-group {:file-id file-id :prefix "" @@ -899,9 +928,9 @@ (mf/defc graphics-box [{:keys [file-id project-id local? objects listing-thumbs? open? open-groups selected-assets reverse-sort? on-asset-click on-assets-delete on-clear-selection] :as props}] - (let [input-ref (mf/use-ref nil) - state (mf/use-state {:renaming nil - :object-id nil}) + (let [input-ref (mf/use-ref nil) + state (mf/use-state {:renaming nil + :object-id nil}) menu-state (mf/use-state auto-pos-menu-state)