mirror of
https://github.com/penpot/penpot.git
synced 2025-06-02 06:21:37 +02:00
♻️ Refactor files upload effects
This commit is contained in:
parent
802f19453d
commit
27a85ce0da
10 changed files with 318 additions and 319 deletions
|
@ -553,32 +553,6 @@
|
||||||
(assoc :zoom zoom)
|
(assoc :zoom zoom)
|
||||||
(update :vbox merge srect)))))))))))
|
(update :vbox merge srect)))))))))))
|
||||||
|
|
||||||
;; --- Add shape to Workspace
|
|
||||||
|
|
||||||
(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
|
|
||||||
ptk/WatchEvent
|
|
||||||
(watch [_ state stream]
|
|
||||||
(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)))
|
|
||||||
page-id (:current-page-id state)
|
|
||||||
frame-id (-> (dwc/lookup-page-objects state page-id)
|
|
||||||
(cp/frame-id-by-position {:x frame-x :y frame-y}))
|
|
||||||
shape (-> (cp/make-minimal-shape type)
|
|
||||||
(merge data)
|
|
||||||
(merge {:x x :y y})
|
|
||||||
(assoc :frame-id frame-id)
|
|
||||||
(gsh/setup-selrect))]
|
|
||||||
(rx/of (dwc/add-shape shape))))))
|
|
||||||
|
|
||||||
;; --- Update Shape Attrs
|
;; --- Update Shape Attrs
|
||||||
|
|
||||||
|
@ -1414,119 +1388,16 @@
|
||||||
(dwc/add-shape shape)
|
(dwc/add-shape shape)
|
||||||
(dwc/commit-undo-transaction))))))
|
(dwc/commit-undo-transaction))))))
|
||||||
|
|
||||||
(defn image-upload [image x y]
|
|
||||||
(ptk/reify ::add-image
|
|
||||||
ptk/WatchEvent
|
|
||||||
(watch [_ state stream]
|
|
||||||
(let [{:keys [name width height id mtype]} image
|
|
||||||
shape {:name name
|
|
||||||
:width width
|
|
||||||
:height height
|
|
||||||
:x (- x (/ width 2))
|
|
||||||
:y (- y (/ height 2))
|
|
||||||
:metadata {:width width
|
|
||||||
:height height
|
|
||||||
:mtype mtype
|
|
||||||
:id id}}]
|
|
||||||
(rx/of (create-and-add-shape :image x y shape))))))
|
|
||||||
|
|
||||||
|
|
||||||
(defn- svg-dimensions [data]
|
|
||||||
(let [width (get-in data [:attrs :width] 100)
|
|
||||||
height (get-in data [:attrs :height] 100)
|
|
||||||
viewbox (get-in data [:attrs :viewBox] (str "0 0 " width " " height))
|
|
||||||
[_ _ width-str height-str] (str/split viewbox " ")
|
|
||||||
width (d/parse-integer width-str)
|
|
||||||
height (d/parse-integer height-str)]
|
|
||||||
[width height]))
|
|
||||||
|
|
||||||
(defn svg-upload [data x y]
|
|
||||||
(ptk/reify ::svg-upload
|
|
||||||
ptk/WatchEvent
|
|
||||||
(watch [_ state stream]
|
|
||||||
(let [page-id (:current-page-id state)
|
|
||||||
objects (dwc/lookup-page-objects state page-id)
|
|
||||||
frame-id (cp/frame-id-by-position objects {:x x :y y})
|
|
||||||
|
|
||||||
[width height] (svg-dimensions data)
|
|
||||||
x (- x (/ width 2))
|
|
||||||
y (- y (/ height 2))
|
|
||||||
|
|
||||||
create-svg-raw
|
|
||||||
(fn [{:keys [tag] :as data} unames root-id]
|
|
||||||
(let [base (cond (string? tag) tag
|
|
||||||
(keyword? tag) (name tag)
|
|
||||||
(nil? tag) "node"
|
|
||||||
:else (str tag))]
|
|
||||||
(-> {:id (uuid/next)
|
|
||||||
:type :svg-raw
|
|
||||||
:name (dwc/generate-unique-name unames (str "svg-" base))
|
|
||||||
:frame-id frame-id
|
|
||||||
;; For svg children we set its coordinates as the root of the svg
|
|
||||||
:width width
|
|
||||||
:height height
|
|
||||||
:x x
|
|
||||||
:y y
|
|
||||||
:content data
|
|
||||||
:root-id root-id}
|
|
||||||
(gsh/setup-selrect))))
|
|
||||||
|
|
||||||
add-svg-child
|
|
||||||
(fn add-svg-child [parent-id root-id [unames [rchs uchs]] [index {:keys [content] :as data}]]
|
|
||||||
(let [shape (create-svg-raw data unames root-id)
|
|
||||||
shape-id (:id shape)
|
|
||||||
[rch1 uch1] (dwc/add-shape-changes page-id shape)
|
|
||||||
|
|
||||||
;; Mov-objects won't have undo because we "delete" the object in the undo of the
|
|
||||||
;; previous operation
|
|
||||||
rch2 [{:type :mov-objects
|
|
||||||
:parent-id parent-id
|
|
||||||
:frame-id frame-id
|
|
||||||
:page-id page-id
|
|
||||||
:index index
|
|
||||||
:shapes [shape-id]}]
|
|
||||||
|
|
||||||
;; Careful! the undo changes are concatenated reversed (we undo in reverse order
|
|
||||||
changes [(d/concat rchs rch1 rch2) (d/concat uch1 uchs)]
|
|
||||||
unames (conj unames (:name shape))]
|
|
||||||
(reduce (partial add-svg-child shape-id root-id) [unames changes] (d/enumerate (:content data)))))
|
|
||||||
|
|
||||||
unames (dwc/retrieve-used-names objects)
|
|
||||||
|
|
||||||
svg-name (->> (str/replace (:name data) ".svg" "")
|
|
||||||
(dwc/generate-unique-name unames))
|
|
||||||
|
|
||||||
root-shape (create-svg-raw data unames nil)
|
|
||||||
root-shape (-> root-shape
|
|
||||||
(assoc :name svg-name))
|
|
||||||
root-id (:id root-shape)
|
|
||||||
|
|
||||||
changes (dwc/add-shape-changes page-id root-shape)
|
|
||||||
|
|
||||||
[_ [rchanges uchanges]] (reduce (partial add-svg-child root-id root-id) [unames changes] (d/enumerate (:content data)))]
|
|
||||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})
|
|
||||||
(dwc/select-shapes (d/ordered-set root-id)))
|
|
||||||
))))
|
|
||||||
|
|
||||||
(defn- paste-image
|
(defn- paste-image
|
||||||
[image]
|
[image]
|
||||||
(ptk/reify ::paste-bin-impl
|
(ptk/reify ::paste-bin-impl
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [response-sb (rx/subject)
|
(let [file-id (get-in state [:workspace-file :id])
|
||||||
file-id (get-in state [:workspace-file :id])
|
|
||||||
params {:file-id file-id
|
params {:file-id file-id
|
||||||
:local? true
|
|
||||||
:data [image]}]
|
:data [image]}]
|
||||||
(rx/concat (rx/of (dwp/upload-media-objects
|
(rx/of (dwp/upload-media-workspace params @ms/mouse-position))))))
|
||||||
(with-meta params
|
|
||||||
{:on-image
|
|
||||||
#(let [{:keys [x y]} @ms/mouse-position]
|
|
||||||
(rx/push! response-sb (image-upload % x y)))
|
|
||||||
:on-svg
|
|
||||||
#(let [{:keys [x y]} @ms/mouse-position]
|
|
||||||
(rx/push! response-sb (svg-upload % x y)))})))
|
|
||||||
(rx/take 1 response-sb))))))
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Interactions
|
;; Interactions
|
||||||
|
@ -1642,8 +1513,10 @@
|
||||||
(d/export dwp/fetch-shared-files)
|
(d/export dwp/fetch-shared-files)
|
||||||
(d/export dwp/link-file-to-library)
|
(d/export dwp/link-file-to-library)
|
||||||
(d/export dwp/unlink-file-from-library)
|
(d/export dwp/unlink-file-from-library)
|
||||||
(d/export dwp/upload-media-objects)
|
(d/export dwp/upload-media-asset)
|
||||||
|
(d/export dwp/upload-media-workspace)
|
||||||
(d/export dwp/clone-media-object)
|
(d/export dwp/clone-media-object)
|
||||||
|
(d/export dwc/image-uploaded)
|
||||||
|
|
||||||
;; Selection
|
;; Selection
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
[cljs.spec.alpha :as s]
|
[cljs.spec.alpha :as s]
|
||||||
[clojure.set :as set]
|
[clojure.set :as set]
|
||||||
|
[cuerdas.core :as str]
|
||||||
[potok.core :as ptk]))
|
[potok.core :as ptk]))
|
||||||
|
|
||||||
;; Change this to :info :debug or :trace to debug this module
|
;; Change this to :info :debug or :trace to debug this module
|
||||||
|
@ -601,3 +602,123 @@
|
||||||
:index index
|
:index index
|
||||||
:shapes [shape-id]})))]
|
:shapes [shape-id]})))]
|
||||||
(rx/of (commit-changes rchanges uchanges {:commit-local? true}))))))
|
(rx/of (commit-changes rchanges uchanges {:commit-local? true}))))))
|
||||||
|
|
||||||
|
;; --- Add shape to Workspace
|
||||||
|
|
||||||
|
(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
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state stream]
|
||||||
|
(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)))
|
||||||
|
page-id (:current-page-id state)
|
||||||
|
frame-id (-> (lookup-page-objects state page-id)
|
||||||
|
(cp/frame-id-by-position {:x frame-x :y frame-y}))
|
||||||
|
shape (-> (cp/make-minimal-shape type)
|
||||||
|
(merge data)
|
||||||
|
(merge {:x x :y y})
|
||||||
|
(assoc :frame-id frame-id)
|
||||||
|
(gsh/setup-selrect))]
|
||||||
|
(rx/of (add-shape shape))))))
|
||||||
|
|
||||||
|
(defn image-uploaded [image x y]
|
||||||
|
(ptk/reify ::image-uploaded
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state stream]
|
||||||
|
(let [{:keys [name width height id mtype]} image
|
||||||
|
shape {:name name
|
||||||
|
:width width
|
||||||
|
:height height
|
||||||
|
:x (- x (/ width 2))
|
||||||
|
:y (- y (/ height 2))
|
||||||
|
:metadata {:width width
|
||||||
|
:height height
|
||||||
|
:mtype mtype
|
||||||
|
:id id}}]
|
||||||
|
(rx/of (create-and-add-shape :image x y shape))))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn- svg-dimensions [data]
|
||||||
|
(let [width (get-in data [:attrs :width] 100)
|
||||||
|
height (get-in data [:attrs :height] 100)
|
||||||
|
viewbox (get-in data [:attrs :viewBox] (str "0 0 " width " " height))
|
||||||
|
[_ _ width-str height-str] (str/split viewbox " ")
|
||||||
|
width (d/parse-integer width-str)
|
||||||
|
height (d/parse-integer height-str)]
|
||||||
|
[width height]))
|
||||||
|
|
||||||
|
(defn svg-uploaded [data x y]
|
||||||
|
(ptk/reify ::svg-uploaded
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state stream]
|
||||||
|
(let [page-id (:current-page-id state)
|
||||||
|
objects (lookup-page-objects state page-id)
|
||||||
|
frame-id (cp/frame-id-by-position objects {:x x :y y})
|
||||||
|
|
||||||
|
[width height] (svg-dimensions data)
|
||||||
|
x (- x (/ width 2))
|
||||||
|
y (- y (/ height 2))
|
||||||
|
|
||||||
|
create-svg-raw
|
||||||
|
(fn [{:keys [tag] :as data} unames root-id]
|
||||||
|
(let [base (cond (string? tag) tag
|
||||||
|
(keyword? tag) (name tag)
|
||||||
|
(nil? tag) "node"
|
||||||
|
:else (str tag))]
|
||||||
|
(-> {:id (uuid/next)
|
||||||
|
:type :svg-raw
|
||||||
|
:name (generate-unique-name unames (str "svg-" base))
|
||||||
|
:frame-id frame-id
|
||||||
|
;; For svg children we set its coordinates as the root of the svg
|
||||||
|
:width width
|
||||||
|
:height height
|
||||||
|
:x x
|
||||||
|
:y y
|
||||||
|
:content data
|
||||||
|
:root-id root-id}
|
||||||
|
(gsh/setup-selrect))))
|
||||||
|
|
||||||
|
add-svg-child
|
||||||
|
(fn add-svg-child [parent-id root-id [unames [rchs uchs]] [index {:keys [content] :as data}]]
|
||||||
|
(let [shape (create-svg-raw data unames root-id)
|
||||||
|
shape-id (:id shape)
|
||||||
|
[rch1 uch1] (add-shape-changes page-id shape)
|
||||||
|
|
||||||
|
;; Mov-objects won't have undo because we "delete" the object in the undo of the
|
||||||
|
;; previous operation
|
||||||
|
rch2 [{:type :mov-objects
|
||||||
|
:parent-id parent-id
|
||||||
|
:frame-id frame-id
|
||||||
|
:page-id page-id
|
||||||
|
:index index
|
||||||
|
:shapes [shape-id]}]
|
||||||
|
|
||||||
|
;; Careful! the undo changes are concatenated reversed (we undo in reverse order
|
||||||
|
changes [(d/concat rchs rch1 rch2) (d/concat uch1 uchs)]
|
||||||
|
unames (conj unames (:name shape))]
|
||||||
|
(reduce (partial add-svg-child shape-id root-id) [unames changes] (d/enumerate (:content data)))))
|
||||||
|
|
||||||
|
unames (retrieve-used-names objects)
|
||||||
|
|
||||||
|
svg-name (->> (str/replace (:name data) ".svg" "")
|
||||||
|
(generate-unique-name unames))
|
||||||
|
|
||||||
|
root-shape (create-svg-raw data unames nil)
|
||||||
|
root-shape (-> root-shape
|
||||||
|
(assoc :name svg-name))
|
||||||
|
root-id (:id root-shape)
|
||||||
|
|
||||||
|
changes (add-shape-changes page-id root-shape)
|
||||||
|
|
||||||
|
[_ [rchanges uchanges]] (reduce (partial add-svg-child root-id root-id) [unames changes] (d/enumerate (:content data)))]
|
||||||
|
(rx/of (commit-changes rchanges uchanges {:commit-local? true})
|
||||||
|
(select-shapes (d/ordered-set root-id)))))))
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
[app.main.data.media :as di]
|
[app.main.data.media :as di]
|
||||||
[app.main.data.messages :as dm]
|
[app.main.data.messages :as dm]
|
||||||
[app.main.data.workspace.common :as dwc]
|
[app.main.data.workspace.common :as dwc]
|
||||||
|
[app.main.data.workspace.libraries :as dwl]
|
||||||
[app.main.repo :as rp]
|
[app.main.repo :as rp]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.util.i18n :as i18n :refer [tr]]
|
[app.util.i18n :as i18n :refer [tr]]
|
||||||
|
@ -31,7 +32,8 @@
|
||||||
[app.util.avatars :as avatars]
|
[app.util.avatars :as avatars]
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
[cljs.spec.alpha :as s]
|
[cljs.spec.alpha :as s]
|
||||||
[potok.core :as ptk]))
|
[potok.core :as ptk]
|
||||||
|
[app.main.store :as st]))
|
||||||
|
|
||||||
(declare persist-changes)
|
(declare persist-changes)
|
||||||
(declare shapes-changes-persisted)
|
(declare shapes-changes-persisted)
|
||||||
|
@ -372,6 +374,89 @@
|
||||||
(->> (http/send! {:method :get :uri uri})
|
(->> (http/send! {:method :get :uri uri})
|
||||||
(rx/map :body)))
|
(rx/map :body)))
|
||||||
|
|
||||||
|
(defn url-name [url]
|
||||||
|
(let [query-idx (str/last-index-of url "?")
|
||||||
|
url (if (> query-idx 0) (subs url 0 query-idx) url)
|
||||||
|
filename (->> (str/split url "/") (last))
|
||||||
|
ext-idx (str/last-index-of filename ".")]
|
||||||
|
(if (> ext-idx 0) (subs filename 0 ext-idx) filename)))
|
||||||
|
|
||||||
|
(defn- handle-upload-error [on-error stream]
|
||||||
|
(->> stream
|
||||||
|
(rx/catch
|
||||||
|
(fn [error]
|
||||||
|
(cond
|
||||||
|
(= (:code error) :media-type-not-allowed)
|
||||||
|
(rx/of (dm/error (tr "errors.media-type-not-allowed")))
|
||||||
|
|
||||||
|
(= (:code error) :media-type-mismatch)
|
||||||
|
(rx/of (dm/error (tr "errors.media-type-mismatch")))
|
||||||
|
|
||||||
|
(= (:code error) :unable-to-optimize)
|
||||||
|
(rx/of (dm/error (:hint error)))
|
||||||
|
|
||||||
|
(fn? on-error)
|
||||||
|
(do
|
||||||
|
(on-error error)
|
||||||
|
(rx/empty))
|
||||||
|
|
||||||
|
:else
|
||||||
|
(rx/throw error))))))
|
||||||
|
|
||||||
|
(defn- upload-uris [file-id local? name uris mtype on-image on-svg]
|
||||||
|
(letfn [(svg-url? [url]
|
||||||
|
(or (and mtype (= mtype "image/svg+xml"))
|
||||||
|
(str/ends-with? url ".svg")))
|
||||||
|
|
||||||
|
(prepare-uri [uri]
|
||||||
|
{:file-id file-id
|
||||||
|
:is-local local?
|
||||||
|
:name (or name (url-name uri))
|
||||||
|
:url uri})]
|
||||||
|
(rx/merge
|
||||||
|
(->> (rx/from uris)
|
||||||
|
(rx/filter (comp not svg-url?))
|
||||||
|
(rx/map prepare-uri)
|
||||||
|
(rx/mapcat #(rp/mutation! :create-file-media-object-from-url %))
|
||||||
|
(rx/do on-image))
|
||||||
|
|
||||||
|
(->> (rx/from uris)
|
||||||
|
(rx/filter svg-url?)
|
||||||
|
(rx/merge-map fetch-svg)
|
||||||
|
(rx/merge-map parse-svg)
|
||||||
|
(rx/with-latest vector uris)
|
||||||
|
(rx/map #(assoc (first %) :name (or name (url-name (second %)))))
|
||||||
|
(rx/do on-svg)))))
|
||||||
|
|
||||||
|
(defn- upload-data [file-id local? name data force-media on-image on-svg]
|
||||||
|
(let [svg-blob? (fn [blob]
|
||||||
|
(and (not force-media)
|
||||||
|
(= (.-type blob) "image/svg+xml")))
|
||||||
|
prepare-file
|
||||||
|
(fn [blob]
|
||||||
|
(let [name (or name (if (di/file? blob) (.-name blob) "blob"))]
|
||||||
|
{:file-id file-id
|
||||||
|
:name name
|
||||||
|
:is-local local?
|
||||||
|
:content blob}))
|
||||||
|
|
||||||
|
file-stream (->> (rx/from data)
|
||||||
|
(rx/map di/validate-file))]
|
||||||
|
(rx/merge
|
||||||
|
(->> file-stream
|
||||||
|
(rx/filter (comp not svg-blob?))
|
||||||
|
(rx/map prepare-file)
|
||||||
|
(rx/mapcat #(rp/mutation! :upload-file-media-object %))
|
||||||
|
(rx/do on-image))
|
||||||
|
|
||||||
|
(->> file-stream
|
||||||
|
(rx/filter svg-blob?)
|
||||||
|
(rx/merge-map #(.text %))
|
||||||
|
(rx/merge-map parse-svg)
|
||||||
|
(rx/with-latest vector file-stream)
|
||||||
|
(rx/map #(assoc (first %) :name (.-name (second %))))
|
||||||
|
(rx/do on-svg)))))
|
||||||
|
|
||||||
(defn upload-media-objects
|
(defn upload-media-objects
|
||||||
[{:keys [file-id local? data name uris mtype svg-as-images] :as params}]
|
[{:keys [file-id local? data name uris mtype svg-as-images] :as params}]
|
||||||
(us/assert ::upload-media-objects params)
|
(us/assert ::upload-media-objects params)
|
||||||
|
@ -380,113 +465,52 @@
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [{:keys [on-image on-svg on-error]
|
(let [{:keys [on-image on-svg on-error]
|
||||||
:or {on-image identity
|
:or {on-image identity
|
||||||
on-svg identity}} (meta params)
|
on-svg identity}} (meta params)]
|
||||||
|
|
||||||
svg? (fn [blob]
|
|
||||||
(= (.-type blob) "image/svg+xml"))
|
|
||||||
|
|
||||||
svg-url? (fn [url]
|
|
||||||
(or (and mtype (= mtype "image/svg+xml"))
|
|
||||||
(str/ends-with? url ".svg")))
|
|
||||||
|
|
||||||
url-name (fn [url]
|
|
||||||
(let [query-idx (str/last-index-of url "?")
|
|
||||||
url (if (> query-idx 0) (subs url 0 query-idx) url)
|
|
||||||
filename (->> (str/split url "/") (last))
|
|
||||||
ext-idx (str/last-index-of filename ".")]
|
|
||||||
(if (> ext-idx 0) (subs filename 0 ext-idx) filename)))
|
|
||||||
|
|
||||||
prepare-file
|
|
||||||
(fn [blob]
|
|
||||||
(let [name (or name (if (di/file? blob) (.-name blob) "blob"))]
|
|
||||||
{:name name
|
|
||||||
:file-id file-id
|
|
||||||
:content blob
|
|
||||||
:is-local local?}))
|
|
||||||
|
|
||||||
prepare-uri
|
|
||||||
(fn [uri]
|
|
||||||
{:file-id file-id
|
|
||||||
:is-local local?
|
|
||||||
:url uri
|
|
||||||
:name (or name (url-name uri))})
|
|
||||||
|
|
||||||
file-stream
|
|
||||||
(when data
|
|
||||||
(->> (rx/from data)
|
|
||||||
(rx/map di/validate-file)))
|
|
||||||
|
|
||||||
image-stream
|
|
||||||
(cond
|
|
||||||
(seq uris)
|
|
||||||
|
|
||||||
(rx/merge
|
|
||||||
(->> (rx/from uris)
|
|
||||||
(rx/filter (comp not svg-url?))
|
|
||||||
(rx/map prepare-uri)
|
|
||||||
(rx/mapcat #(rp/mutation! :create-file-media-object-from-url %))
|
|
||||||
(rx/do on-image))
|
|
||||||
|
|
||||||
(->> (rx/from uris)
|
|
||||||
(rx/filter svg-url?)
|
|
||||||
(rx/merge-map fetch-svg)
|
|
||||||
(rx/merge-map parse-svg)
|
|
||||||
(rx/with-latest vector uris)
|
|
||||||
(rx/map #(assoc (first %) :name (or name (url-name (second %)))))
|
|
||||||
(rx/do on-svg)))
|
|
||||||
|
|
||||||
:else
|
|
||||||
(rx/merge
|
|
||||||
(->> file-stream
|
|
||||||
(rx/filter #(or svg-as-images (not (svg? %))))
|
|
||||||
(rx/map prepare-file)
|
|
||||||
(rx/mapcat #(rp/mutation! :upload-file-media-object %))
|
|
||||||
(rx/do on-image))
|
|
||||||
|
|
||||||
(->> file-stream
|
|
||||||
(rx/filter #(and (not svg-as-images) (svg? %)))
|
|
||||||
(rx/merge-map #(.text %))
|
|
||||||
(rx/merge-map parse-svg)
|
|
||||||
(rx/with-latest vector file-stream)
|
|
||||||
(rx/map #(assoc (first %) :name (.-name (second %))))
|
|
||||||
(rx/do on-svg))))]
|
|
||||||
|
|
||||||
(rx/concat
|
(rx/concat
|
||||||
(rx/of (dm/show {:content (tr "media.loading")
|
(rx/of (dm/show {:content (tr "media.loading")
|
||||||
:type :info
|
:type :info
|
||||||
:timeout nil
|
:timeout nil
|
||||||
:tag :media-loading}))
|
:tag :media-loading}))
|
||||||
(->> image-stream
|
(->> (if (seq uris)
|
||||||
(rx/catch (fn [error]
|
;; Media objects is a list of URL's pointing to the path
|
||||||
(cond
|
(upload-uris file-id local? name uris mtype on-image on-svg)
|
||||||
(= (:code error) :media-type-not-allowed)
|
;; Media objects are blob of data to be upload
|
||||||
(rx/of (dm/error (tr "errors.media-type-not-allowed")))
|
(upload-data file-id local? name data svg-as-images on-image on-svg))
|
||||||
|
;; Every stream has its own sideffect. We need to ignore the result
|
||||||
|
(rx/ignore)
|
||||||
|
(handle-upload-error on-error)
|
||||||
|
(rx/finalize (st/emitf (dm/hide-tag :media-loading)))))))))
|
||||||
|
|
||||||
(= (:code error) :media-type-mismatch)
|
(defn upload-media-asset [params]
|
||||||
(rx/of (dm/error (tr "errors.media-type-mismatch")))
|
(let [params (-> params
|
||||||
|
(assoc :svg-as-images true)
|
||||||
|
(assoc :local? false)
|
||||||
|
(with-meta {:on-image #(st/emit! (dwl/add-media %))}))]
|
||||||
|
(upload-media-objects params)))
|
||||||
|
|
||||||
(= (:code error) :unable-to-optimize)
|
(defn upload-media-workspace
|
||||||
(rx/of (dm/error (:hint error)))
|
[params position]
|
||||||
|
(let [{:keys [x y]} position
|
||||||
|
params (-> params
|
||||||
|
(assoc :local? true)
|
||||||
|
(with-meta
|
||||||
|
{:on-image
|
||||||
|
#(st/emit! (dwc/image-uploaded % x y))
|
||||||
|
:on-svg
|
||||||
|
#(st/emit! (dwc/svg-uploaded % x y))}))]
|
||||||
|
(upload-media-objects params)))
|
||||||
|
|
||||||
(fn? on-error)
|
|
||||||
(do
|
|
||||||
(on-error error)
|
|
||||||
(rx/empty))
|
|
||||||
|
|
||||||
:else
|
|
||||||
(rx/throw error))))
|
|
||||||
(rx/finalize (fn []
|
|
||||||
(st/emit! (dm/hide-tag :media-loading))))))))))
|
|
||||||
|
|
||||||
;; --- Upload File Media objects
|
;; --- Upload File Media objects
|
||||||
|
|
||||||
(s/def ::object-id ::us/uuid)
|
(s/def ::object-id ::us/uuid)
|
||||||
|
|
||||||
(s/def ::clone-media-objects-params
|
(s/def ::clone-media-objects-params
|
||||||
(s/keys :req-un [::file-id ::local? ::object-id]))
|
(s/keys :req-un [::file-id ::object-id]))
|
||||||
|
|
||||||
(defn clone-media-object
|
(defn clone-media-object
|
||||||
[{:keys [file-id local? object-id] :as params}]
|
[{:keys [file-id object-id] :as params}]
|
||||||
(us/assert ::clone-media-objects-params params)
|
(us/assert ::clone-media-objects-params params)
|
||||||
(ptk/reify ::clone-media-objects
|
(ptk/reify ::clone-media-objects
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
|
@ -494,7 +518,7 @@
|
||||||
(let [{:keys [on-success on-error]
|
(let [{:keys [on-success on-error]
|
||||||
:or {on-success identity
|
:or {on-success identity
|
||||||
on-error identity}} (meta params)
|
on-error identity}} (meta params)
|
||||||
params {:is-local local?
|
params {:is-local true
|
||||||
:file-id file-id
|
:file-id file-id
|
||||||
:id object-id}]
|
:id object-id}]
|
||||||
|
|
||||||
|
|
|
@ -99,10 +99,10 @@
|
||||||
(when (and shape (not (:hidden shape)))
|
(when (and shape (not (:hidden shape)))
|
||||||
(let [shape (-> (gsh/transform-shape shape)
|
(let [shape (-> (gsh/transform-shape shape)
|
||||||
(gsh/translate-to-frame frame))
|
(gsh/translate-to-frame frame))
|
||||||
opts #js {:shape shape}]
|
opts #js {:shape shape}
|
||||||
(if (and (= :svg-raw (:type shape))
|
svg-element? (and (= :svg-raw (:type shape))
|
||||||
(not= :svg (get-in shape [:content :tag])))
|
(not= :svg (get-in shape [:content :tag])))]
|
||||||
[:> svg-raw-wrapper {:shape shape :frame frame}]
|
(if-not svg-element?
|
||||||
[:> shape-container {:shape shape}
|
[:> shape-container {:shape shape}
|
||||||
(case (:type shape)
|
(case (:type shape)
|
||||||
:text [:> text/text-shape opts]
|
:text [:> text/text-shape opts]
|
||||||
|
@ -113,7 +113,10 @@
|
||||||
:frame [:> frame-wrapper {:shape shape}]
|
:frame [:> frame-wrapper {:shape shape}]
|
||||||
:group [:> group-wrapper {:shape shape :frame frame}]
|
:group [:> group-wrapper {:shape shape :frame frame}]
|
||||||
:svg-raw [:> svg-raw-wrapper {:shape shape :frame frame}]
|
:svg-raw [:> svg-raw-wrapper {:shape shape :frame frame}]
|
||||||
nil)]))))))
|
nil)]
|
||||||
|
|
||||||
|
;; Don't wrap svg elements inside a <g> otherwise some can break
|
||||||
|
[:> svg-raw-wrapper {:shape shape :frame frame}]))))))
|
||||||
|
|
||||||
(defn get-viewbox [{:keys [x y width height] :or {x 0 y 0 width 100 height 100}}]
|
(defn get-viewbox [{:keys [x y width height] :or {x 0 y 0 width 100 height 100}}]
|
||||||
(str/fmt "%s %s %s %s" x y width height))
|
(str/fmt "%s %s %s %s" x y width height))
|
||||||
|
|
|
@ -62,13 +62,11 @@
|
||||||
[props]
|
[props]
|
||||||
(let [shape (unchecked-get props "shape")
|
(let [shape (unchecked-get props "shape")
|
||||||
childs (unchecked-get props "childs")
|
childs (unchecked-get props "childs")
|
||||||
frame (unchecked-get props "frame")]
|
frame (unchecked-get props "frame")
|
||||||
|
svg-element? (and (= :svg-raw (:type shape))
|
||||||
|
(not= :svg (get-in shape [:content :tag])))]
|
||||||
|
|
||||||
(if (and (= :svg-raw (:type shape))
|
(if-not svg-element?
|
||||||
(not= :svg (get-in shape [:content :tag])))
|
|
||||||
[:& component {:shape shape
|
|
||||||
:frame frame
|
|
||||||
:childs childs}]
|
|
||||||
[:> shape-container {:shape shape
|
[:> shape-container {:shape shape
|
||||||
:on-mouse-enter (handle-hover-shape shape true)
|
:on-mouse-enter (handle-hover-shape shape true)
|
||||||
:on-mouse-leave (handle-hover-shape shape false)
|
:on-mouse-leave (handle-hover-shape shape false)
|
||||||
|
@ -76,7 +74,12 @@
|
||||||
[:& component {:shape shape
|
[:& component {:shape shape
|
||||||
:frame frame
|
:frame frame
|
||||||
:childs childs
|
:childs childs
|
||||||
:is-child-selected? true}]]))))
|
:is-child-selected? true}]]
|
||||||
|
|
||||||
|
;; Don't wrap svg elements inside a <g> otherwise some can break
|
||||||
|
[:& component {:shape shape
|
||||||
|
:frame frame
|
||||||
|
:childs childs}]))))
|
||||||
|
|
||||||
(defn frame-container-factory
|
(defn frame-container-factory
|
||||||
[objects]
|
[objects]
|
||||||
|
|
|
@ -55,13 +55,12 @@
|
||||||
|
|
||||||
on-mouse-down (mf/use-callback
|
on-mouse-down (mf/use-callback
|
||||||
(mf/deps shape)
|
(mf/deps shape)
|
||||||
#(on-mouse-down % shape))]
|
#(on-mouse-down % shape))
|
||||||
|
|
||||||
(if (and (= :svg-raw (:type shape))
|
svg-element? (and (= :svg-raw (:type shape))
|
||||||
(not= :svg (get-in shape [:content :tag])))
|
(not= :svg (get-in shape [:content :tag])))]
|
||||||
[:& component {:shape shape
|
|
||||||
:frame frame
|
(if-not svg-element?
|
||||||
:childs childs}]
|
|
||||||
[:> shape-container {:shape shape
|
[:> shape-container {:shape shape
|
||||||
:on-mouse-down on-mouse-down
|
:on-mouse-down on-mouse-down
|
||||||
:cursor (when (seq (:interactions shape)) "pointer")}
|
:cursor (when (seq (:interactions shape)) "pointer")}
|
||||||
|
@ -77,7 +76,12 @@
|
||||||
:fill "#31EFB8"
|
:fill "#31EFB8"
|
||||||
:stroke "#31EFB8"
|
:stroke "#31EFB8"
|
||||||
:stroke-width 1
|
:stroke-width 1
|
||||||
:fill-opacity 0.2}])]))))
|
:fill-opacity 0.2}])]
|
||||||
|
|
||||||
|
;; Don't wrap svg elements inside a <g> otherwise some can break
|
||||||
|
[:& component {:shape shape
|
||||||
|
:frame frame
|
||||||
|
:childs childs}]))))
|
||||||
|
|
||||||
(defn frame-wrapper
|
(defn frame-wrapper
|
||||||
[shape-container show-interactions?]
|
[shape-container show-interactions?]
|
||||||
|
@ -187,8 +191,7 @@
|
||||||
:image [:> image-wrapper opts]
|
:image [:> image-wrapper opts]
|
||||||
:circle [:> circle-wrapper opts]
|
:circle [:> circle-wrapper opts]
|
||||||
:group [:> group-container {:shape shape :frame frame}]
|
:group [:> group-container {:shape shape :frame frame}]
|
||||||
:svg-raw [:> svg-raw-container {:shape shape :frame frame}]
|
:svg-raw [:> svg-raw-container {:shape shape :frame frame}])))))))
|
||||||
)))))))
|
|
||||||
|
|
||||||
(mf/defc frame-svg
|
(mf/defc frame-svg
|
||||||
{::mf/wrap [mf/memo]}
|
{::mf/wrap [mf/memo]}
|
||||||
|
|
|
@ -9,16 +9,17 @@
|
||||||
|
|
||||||
(ns app.main.ui.workspace.left-toolbar
|
(ns app.main.ui.workspace.left-toolbar
|
||||||
(:require
|
(:require
|
||||||
[rumext.alpha :as mf]
|
[app.common.geom.point :as gpt]
|
||||||
[app.common.media :as cm]
|
[app.common.media :as cm]
|
||||||
[app.main.refs :as refs]
|
|
||||||
[app.main.data.workspace :as dw]
|
[app.main.data.workspace :as dw]
|
||||||
|
[app.main.refs :as refs]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.components.file-uploader :refer [file-uploader]]
|
[app.main.ui.components.file-uploader :refer [file-uploader]]
|
||||||
[app.util.object :as obj]
|
[app.main.ui.icons :as i]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.i18n :as i18n :refer [tr]]
|
[app.util.i18n :as i18n :refer [tr]]
|
||||||
[app.main.ui.icons :as i]))
|
[app.util.object :as obj]
|
||||||
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
(mf/defc image-upload
|
(mf/defc image-upload
|
||||||
{::mf/wrap [mf/memo]}
|
{::mf/wrap [mf/memo]}
|
||||||
|
@ -29,26 +30,13 @@
|
||||||
on-click
|
on-click
|
||||||
(mf/use-callback #(dom/click (mf/ref-val ref)))
|
(mf/use-callback #(dom/click (mf/ref-val ref)))
|
||||||
|
|
||||||
handle-image-upload
|
|
||||||
(mf/use-callback
|
|
||||||
(fn [image]
|
|
||||||
(st/emit! (dw/image-upload image 0 0))))
|
|
||||||
|
|
||||||
handle-svg-upload
|
|
||||||
(mf/use-callback
|
|
||||||
(fn [svg]
|
|
||||||
(st/emit! (dw/svg-upload svg 0 0))))
|
|
||||||
|
|
||||||
on-files-selected
|
on-files-selected
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
(mf/deps file)
|
(mf/deps file)
|
||||||
(fn [blobs]
|
(fn [blobs]
|
||||||
(st/emit! (dw/upload-media-objects
|
(let [params {:file-id (:id file)
|
||||||
(with-meta {:file-id (:id file)
|
:data (seq blobs)}]
|
||||||
:local? true
|
(st/emit! (dw/upload-media-workspace params (gpt/point 0 0))))))]
|
||||||
:data (seq blobs)}
|
|
||||||
{:on-image handle-image-upload
|
|
||||||
:on-svg handle-svg-upload})))))]
|
|
||||||
|
|
||||||
[:li.tooltip.tooltip-right
|
[:li.tooltip.tooltip-right
|
||||||
{:alt (tr "workspace.toolbar.image")
|
{:alt (tr "workspace.toolbar.image")
|
||||||
|
|
|
@ -82,31 +82,35 @@
|
||||||
alt? (hooks/use-rxsub ms/keyboard-alt)
|
alt? (hooks/use-rxsub ms/keyboard-alt)
|
||||||
|
|
||||||
moving-iref (mf/use-memo (mf/deps (:id shape)) (make-is-moving-ref (:id shape)))
|
moving-iref (mf/use-memo (mf/deps (:id shape)) (make-is-moving-ref (:id shape)))
|
||||||
moving? (mf/deref moving-iref)]
|
moving? (mf/deref moving-iref)
|
||||||
|
svg-element? (and (= (:type shape) :svg-raw)
|
||||||
|
(not= :svg (get-in shape [:content :tag])))]
|
||||||
|
|
||||||
(when (and shape
|
(when (and shape
|
||||||
(or ghost? (not moving?))
|
(or ghost? (not moving?))
|
||||||
(not (:hidden shape)))
|
(not (:hidden shape)))
|
||||||
(if (and (= (:type shape) :svg-raw)
|
[:*
|
||||||
(not= :svg (get-in shape [:content :tag])))
|
(if-not svg-element?
|
||||||
;; When we don't want to add a wrapper to internal raw svg elements
|
[:g.shape-wrapper {:style {:cursor (if alt? cur/duplicate nil)}}
|
||||||
[:> svg-raw-wrapper opts]
|
(case (:type shape)
|
||||||
[:g.shape-wrapper {:style {:cursor (if alt? cur/duplicate nil)}}
|
:path [:> path/path-wrapper opts]
|
||||||
(case (:type shape)
|
:text [:> text/text-wrapper opts]
|
||||||
:path [:> path/path-wrapper opts]
|
:group [:> group-wrapper opts]
|
||||||
:text [:> text/text-wrapper opts]
|
:rect [:> rect-wrapper opts]
|
||||||
:group [:> group-wrapper opts]
|
:image [:> image-wrapper opts]
|
||||||
:rect [:> rect-wrapper opts]
|
:circle [:> circle-wrapper opts]
|
||||||
:image [:> image-wrapper opts]
|
:svg-raw [:> svg-raw-wrapper opts]
|
||||||
:circle [:> circle-wrapper opts]
|
|
||||||
:svg-raw [:> svg-raw-wrapper opts]
|
|
||||||
|
|
||||||
;; Only used when drawing a new frame.
|
;; Only used when drawing a new frame.
|
||||||
:frame [:> frame-wrapper {:shape shape}]
|
:frame [:> frame-wrapper {:shape shape}]
|
||||||
nil)
|
|
||||||
|
|
||||||
(when (debug? :bounding-boxes)
|
nil)]
|
||||||
[:& bounding-box {:shape shape :frame frame}])]))))
|
|
||||||
|
;; Don't wrap svg elements inside a <g> otherwise some can break
|
||||||
|
[:> svg-raw-wrapper opts])
|
||||||
|
|
||||||
|
(when (debug? :bounding-boxes)
|
||||||
|
[:& bounding-box {:shape shape :frame frame}])])))
|
||||||
|
|
||||||
(def group-wrapper (group/group-wrapper-factory shape-wrapper))
|
(def group-wrapper (group/group-wrapper-factory shape-wrapper))
|
||||||
(def svg-raw-wrapper (svg-raw/svg-raw-wrapper-factory shape-wrapper))
|
(def svg-raw-wrapper (svg-raw/svg-raw-wrapper-factory shape-wrapper))
|
||||||
|
|
|
@ -164,12 +164,9 @@
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
(mf/deps file-id)
|
(mf/deps file-id)
|
||||||
(fn [blobs]
|
(fn [blobs]
|
||||||
(let [params (with-meta {:file-id file-id
|
(let [params {:file-id file-id
|
||||||
:local? false
|
:data (seq blobs)}]
|
||||||
:data (seq blobs)
|
(st/emit! (dw/upload-media-asset params)))))
|
||||||
:svg-as-images true}
|
|
||||||
{:on-image on-media-uploaded})]
|
|
||||||
(st/emit! (dw/upload-media-objects params)))))
|
|
||||||
|
|
||||||
on-delete
|
on-delete
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
|
|
|
@ -456,12 +456,7 @@
|
||||||
on-image-uploaded
|
on-image-uploaded
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
(fn [image {:keys [x y]}]
|
(fn [image {:keys [x y]}]
|
||||||
(st/emit! (dw/image-upload image x y))))
|
(st/emit! (dw/image-uploaded image x y))))
|
||||||
|
|
||||||
on-svg-uploaded
|
|
||||||
(mf/use-callback
|
|
||||||
(fn [image {:keys [x y]}]
|
|
||||||
(st/emit! (dw/svg-upload image x y))))
|
|
||||||
|
|
||||||
on-drop
|
on-drop
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
|
@ -497,31 +492,23 @@
|
||||||
lines (str/lines data)
|
lines (str/lines data)
|
||||||
urls (filter #(and (not (str/blank? %))
|
urls (filter #(and (not (str/blank? %))
|
||||||
(not (str/starts-with? % "#")))
|
(not (str/starts-with? % "#")))
|
||||||
lines)]
|
lines)
|
||||||
(st/emit!
|
params {:file-id (:id file)
|
||||||
(dw/upload-media-objects
|
:uris urls}]
|
||||||
(with-meta {:file-id (:id file)
|
(st/emit! (dw/upload-media-workspace params viewport-coord)))
|
||||||
:local? true
|
|
||||||
:uris urls}
|
|
||||||
{:on-image #(on-image-uploaded % viewport-coord)
|
|
||||||
:on-svg #(on-svg-uploaded % viewport-coord)}))))
|
|
||||||
|
|
||||||
;; Will trigger when the user drags an SVG asset from the assets panel
|
;; Will trigger when the user drags an SVG asset from the assets panel
|
||||||
(and (dnd/has-type? event "text/asset-id") (= asset-type "image/svg+xml"))
|
(and (dnd/has-type? event "text/asset-id") (= asset-type "image/svg+xml"))
|
||||||
(let [path (cfg/resolve-file-media {:id asset-id})]
|
(let [path (cfg/resolve-file-media {:id asset-id})
|
||||||
(st/emit!
|
params {:file-id (:id file)
|
||||||
(dw/upload-media-objects
|
:uris [path]
|
||||||
(with-meta {:file-id (:id file)
|
:name asset-name
|
||||||
:local? true
|
:mtype asset-type}]
|
||||||
:uris [path]
|
(st/emit! (dw/upload-media-workspace params viewport-coord)))
|
||||||
:name asset-name
|
|
||||||
:mtype asset-type}
|
|
||||||
{:on-svg #(on-svg-uploaded % viewport-coord)}))))
|
|
||||||
|
|
||||||
;; Will trigger when the user drags an image from the assets SVG
|
;; Will trigger when the user drags an image from the assets SVG
|
||||||
(dnd/has-type? event "text/asset-id")
|
(dnd/has-type? event "text/asset-id")
|
||||||
(let [params {:file-id (:id file)
|
(let [params {:file-id (:id file)
|
||||||
:local? true
|
|
||||||
:object-id asset-id
|
:object-id asset-id
|
||||||
:name asset-name}]
|
:name asset-name}]
|
||||||
(st/emit! (dw/clone-media-object
|
(st/emit! (dw/clone-media-object
|
||||||
|
@ -534,12 +521,8 @@
|
||||||
:else
|
:else
|
||||||
(let [files (dnd/get-files event)
|
(let [files (dnd/get-files event)
|
||||||
params {:file-id (:id file)
|
params {:file-id (:id file)
|
||||||
:local? true
|
|
||||||
:data (seq files)}]
|
:data (seq files)}]
|
||||||
(st/emit! (dw/upload-media-objects
|
(st/emit! (dw/upload-media-workspace params viewport-coord)))))))
|
||||||
(with-meta params
|
|
||||||
{:on-image #(on-image-uploaded % viewport-coord)
|
|
||||||
:on-svg #(on-svg-uploaded % viewport-coord)}))))))))
|
|
||||||
|
|
||||||
on-paste
|
on-paste
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue