mirror of
https://github.com/penpot/penpot.git
synced 2025-01-08 07:50:43 -05:00
🎉 Add the ability to create components in bulk from images
This commit is contained in:
parent
527e4643da
commit
ce7852329a
5 changed files with 170 additions and 70 deletions
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}))
|
||||
|
|
|
@ -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)))))
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in a new issue