mirror of
https://github.com/penpot/penpot.git
synced 2025-03-15 17:21:17 -05:00
Merge pull request #443 from penpot/feature/paste-svg
Upload SVG as shapes
This commit is contained in:
commit
2d07df2541
28 changed files with 922 additions and 328 deletions
|
@ -125,7 +125,7 @@
|
|||
(ex/raise :type :validation
|
||||
:code :media-type-mismatch
|
||||
:hint (str "Seems like you are uploading a file whose content does not match the extension."
|
||||
"Expected: " mtype "Got: " mtype')))
|
||||
"Expected: " mtype ". Got: " mtype')))
|
||||
{:width (.getImageWidth instance)
|
||||
:height (.getImageHeight instance)
|
||||
:mtype mtype'})))
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
:thumbnail-id (:id thumb)
|
||||
:width (:width source-info)
|
||||
:height (:height source-info)
|
||||
:mtype (:mtype source-info)})))
|
||||
:mtype source-mtype})))
|
||||
|
||||
|
||||
;; --- Create File Media Object (from URL)
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
(:require
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.spec :as us]
|
||||
[cuerdas.core :as str]
|
||||
[app.metrics :as mtx]
|
||||
[clojure.java.io :as io]
|
||||
[clojure.java.shell :as shell]
|
||||
|
@ -25,10 +26,25 @@
|
|||
[^String data]
|
||||
(IOUtils/toInputStream data "UTF-8"))
|
||||
|
||||
(defn- stream->string
|
||||
[input]
|
||||
(with-open [istream (io/input-stream input)]
|
||||
(-> (IOUtils/toString input "UTF-8"))))
|
||||
|
||||
(defn- clean-svg
|
||||
[^InputStream input]
|
||||
(let [result (shell/sh "svgcleaner" "-c" "-" :in input :out-enc :bytes)]
|
||||
(when (not= 0 (:exit result))
|
||||
(let [result (shell/sh
|
||||
;; "svgcleaner" "--allow-bigger-file" "-c" "-"
|
||||
"svgo"
|
||||
"--enable=prefixIds,removeDimensions,removeXMLNS,removeScriptElement"
|
||||
"--disable=removeViewBox,moveElemsAttrsToGroup"
|
||||
"-i" "-" "-o" "-"
|
||||
|
||||
:in input :out-enc :bytes)
|
||||
err-str (:err result)]
|
||||
(when (or (not= 0 (:exit result))
|
||||
;; svgcleaner returns 0 with some errors, we need to check
|
||||
(and (not= err-str "") (not (nil? err-str)) (str/starts-with? err-str "Error")))
|
||||
(ex/raise :type :validation
|
||||
:code :unable-to-optimize
|
||||
:hint (:err result)))
|
||||
|
|
|
@ -261,9 +261,13 @@
|
|||
(d/export gco/center-selrect)
|
||||
(d/export gco/center-rect)
|
||||
(d/export gco/center-points)
|
||||
|
||||
(d/export gpr/rect->selrect)
|
||||
(d/export gpr/rect->points)
|
||||
(d/export gpr/points->selrect)
|
||||
(d/export gpr/points->rect)
|
||||
(d/export gpr/center->rect)
|
||||
|
||||
(d/export gtr/transform-shape)
|
||||
(d/export gtr/transform-matrix)
|
||||
(d/export gtr/inverse-transform-matrix)
|
||||
|
|
|
@ -58,3 +58,12 @@
|
|||
:width (- maxx minx)
|
||||
:height (- maxy miny)}))
|
||||
|
||||
(defn center->rect [center width height]
|
||||
(assert (gpt/point center))
|
||||
(assert (and (number? width) (> width 0)))
|
||||
(assert (and (number? height) (> height 0)))
|
||||
|
||||
{:x (- (:x center) (/ width 2))
|
||||
:y (- (:y center) (/ height 2))
|
||||
:width width
|
||||
:height height})
|
||||
|
|
|
@ -26,17 +26,17 @@
|
|||
"Returns a transformation matrix without changing the shape properties.
|
||||
The result should be used in a `transform` attribute in svg"
|
||||
([shape] (transform-matrix shape nil))
|
||||
([{:keys [flip-x flip-y] :as shape} {:keys [no-flip]}]
|
||||
(let [shape-center (or (gco/center-shape shape)
|
||||
(gpt/point 0 0))]
|
||||
(-> (gmt/matrix)
|
||||
(gmt/translate shape-center)
|
||||
([shape params] (transform-matrix shape params (or (gco/center-shape shape)
|
||||
(gpt/point 0 0))))
|
||||
([{:keys [flip-x flip-y] :as shape} {:keys [no-flip]} shape-center]
|
||||
(-> (gmt/matrix)
|
||||
(gmt/translate shape-center)
|
||||
|
||||
(gmt/multiply (:transform shape (gmt/matrix)))
|
||||
(cond->
|
||||
(and (not no-flip) flip-x) (gmt/scale (gpt/point -1 1))
|
||||
(and (not no-flip) flip-y) (gmt/scale (gpt/point 1 -1)))
|
||||
(gmt/translate (gpt/negate shape-center))))))
|
||||
(gmt/multiply (:transform shape (gmt/matrix)))
|
||||
(cond->
|
||||
(and (not no-flip) flip-x) (gmt/scale (gpt/point -1 1))
|
||||
(and (not no-flip) flip-y) (gmt/scale (gpt/point 1 -1)))
|
||||
(gmt/translate (gpt/negate shape-center)))))
|
||||
|
||||
(defn inverse-transform-matrix
|
||||
([shape]
|
||||
|
|
|
@ -31,8 +31,7 @@
|
|||
:pages-index {}})
|
||||
|
||||
(def default-shape-attrs
|
||||
{:fill-color default-color
|
||||
:fill-opacity 1})
|
||||
{})
|
||||
|
||||
(def default-frame-attrs
|
||||
{:frame-id uuid/zero
|
||||
|
@ -55,8 +54,6 @@
|
|||
|
||||
{:type :image}
|
||||
|
||||
{:type :icon}
|
||||
|
||||
{:type :circle
|
||||
:name "Circle"
|
||||
:fill-color default-color
|
||||
|
@ -89,7 +86,9 @@
|
|||
|
||||
{:type :text
|
||||
:name "Text"
|
||||
:content nil}])
|
||||
:content nil}
|
||||
|
||||
{:type :svg-raw}])
|
||||
|
||||
(defn make-minimal-shape
|
||||
[type]
|
||||
|
|
|
@ -133,7 +133,8 @@ RUN set -ex; \
|
|||
mv /tmp/node/node-$NODE_VERSION-linux-x64 /usr/local/nodejs; \
|
||||
chown -R root /usr/local/nodejs; \
|
||||
/usr/local/nodejs/bin/npm install -g yarn; \
|
||||
rm -rf /tmp/node;
|
||||
/usr/local/nodejs/bin/npm install -g svgo; \
|
||||
rm -rf /tmp/node;
|
||||
|
||||
RUN set -ex; \
|
||||
cd /tmp; \
|
||||
|
|
|
@ -1282,6 +1282,12 @@
|
|||
},
|
||||
"unused" : true
|
||||
},
|
||||
"handoff.tabs.code.selected.svg-raw" : {
|
||||
"translations" : {
|
||||
"en" : "SVG"
|
||||
},
|
||||
"unused" : true
|
||||
},
|
||||
"handoff.tabs.info" : {
|
||||
"used-in" : [ "src/app/main/ui/handoff/right_sidebar.cljs:59" ],
|
||||
"translations" : {
|
||||
|
|
|
@ -553,35 +553,6 @@
|
|||
(assoc :zoom zoom)
|
||||
(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
|
||||
|
||||
|
@ -1417,20 +1388,6 @@
|
|||
(dwc/add-shape shape)
|
||||
(dwc/commit-undo-transaction))))))
|
||||
|
||||
(defn- image-uploaded
|
||||
[image]
|
||||
(let [{:keys [x y]} @ms/mouse-position
|
||||
{:keys [width height]} image
|
||||
shape {:name (:name image)
|
||||
:width width
|
||||
:height height
|
||||
:x (- x (/ width 2))
|
||||
:y (- y (/ height 2))
|
||||
:metadata {:width width
|
||||
:height height
|
||||
:id (:id image)
|
||||
:path (:path image)}}]
|
||||
(st/emit! (create-and-add-shape :image x y shape))))
|
||||
|
||||
(defn- paste-image
|
||||
[image]
|
||||
|
@ -1439,11 +1396,8 @@
|
|||
(watch [_ state stream]
|
||||
(let [file-id (get-in state [:workspace-file :id])
|
||||
params {:file-id file-id
|
||||
:local? true
|
||||
:data [image]}]
|
||||
(rx/of (dwp/upload-media-objects
|
||||
(with-meta params
|
||||
{:on-success image-uploaded})))))))
|
||||
(rx/of (dwp/upload-media-workspace params @ms/mouse-position))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Interactions
|
||||
|
@ -1536,6 +1490,7 @@
|
|||
:value previus-color}]
|
||||
{:commit-local? true}))))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Exports
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -1558,8 +1513,10 @@
|
|||
(d/export dwp/fetch-shared-files)
|
||||
(d/export dwp/link-file-to-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 dwc/image-uploaded)
|
||||
|
||||
;; Selection
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
[beicon.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[clojure.set :as set]
|
||||
[cuerdas.core :as str]
|
||||
[potok.core :as ptk]))
|
||||
|
||||
;; Change this to :info :debug or :trace to debug this module
|
||||
|
@ -522,6 +523,27 @@
|
|||
(update-in [:workspace-local :hover] disj id)
|
||||
(update :workspace-local dissoc :edition))))))
|
||||
|
||||
(defn add-shape-changes
|
||||
[page-id attrs]
|
||||
(let [id (:id attrs)
|
||||
frame-id (:frame-id attrs)
|
||||
shape (gpr/setup-proportions attrs)
|
||||
|
||||
default-attrs (if (= :frame (:type shape))
|
||||
cp/default-frame-attrs
|
||||
cp/default-shape-attrs)
|
||||
shape (merge default-attrs shape)
|
||||
|
||||
redo-changes [{:type :add-obj
|
||||
:id id
|
||||
:page-id page-id
|
||||
:frame-id frame-id
|
||||
:obj shape}]
|
||||
undo-changes [{:type :del-obj
|
||||
:page-id page-id
|
||||
:id id}]]
|
||||
|
||||
[redo-changes undo-changes]))
|
||||
|
||||
(defn add-shape
|
||||
[attrs]
|
||||
|
@ -532,36 +554,21 @@
|
|||
(let [page-id (:current-page-id state)
|
||||
objects (lookup-page-objects state page-id)
|
||||
|
||||
id (or (:id attrs) (uuid/next))
|
||||
shape (gpr/setup-proportions attrs)
|
||||
|
||||
unames (retrieve-used-names objects)
|
||||
name (generate-unique-name unames (:name shape))
|
||||
|
||||
id (or (:id attrs) (uuid/next))
|
||||
name (-> objects
|
||||
(retrieve-used-names)
|
||||
(generate-unique-name (:name attrs)))
|
||||
frame-id (if (= :frame (:type attrs))
|
||||
uuid/zero
|
||||
(or (:frame-id attrs)
|
||||
(cp/frame-id-by-position objects attrs)))
|
||||
|
||||
shape (merge
|
||||
(if (= :frame (:type shape))
|
||||
cp/default-frame-attrs
|
||||
cp/default-shape-attrs)
|
||||
(assoc shape
|
||||
:id id
|
||||
:name name))
|
||||
|
||||
rchange {:type :add-obj
|
||||
:id id
|
||||
:page-id page-id
|
||||
:frame-id frame-id
|
||||
:obj shape}
|
||||
uchange {:type :del-obj
|
||||
:page-id page-id
|
||||
:id id}]
|
||||
|
||||
[rchanges uchanges] (add-shape-changes page-id (assoc attrs
|
||||
:id id
|
||||
:frame-id frame-id
|
||||
:name name))]
|
||||
(rx/concat
|
||||
(rx/of (commit-changes [rchange] [uchange] {:commit-local? true})
|
||||
(rx/of (commit-changes rchanges uchanges {:commit-local? true})
|
||||
(select-shapes (d/ordered-set id)))
|
||||
(when (= :text (:type attrs))
|
||||
(->> (rx/of (start-edition-mode id))
|
||||
|
@ -595,3 +602,123 @@
|
|||
:index index
|
||||
:shapes [shape-id]})))]
|
||||
(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)))))))
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
(ns app.main.data.workspace.persistence
|
||||
(:require
|
||||
[cuerdas.core :as str]
|
||||
[app.util.http :as http]
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.media :as cm]
|
||||
|
@ -19,6 +21,7 @@
|
|||
[app.main.data.media :as di]
|
||||
[app.main.data.messages :as dm]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
|
@ -29,7 +32,8 @@
|
|||
[app.util.avatars :as avatars]
|
||||
[beicon.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[potok.core :as ptk]))
|
||||
[potok.core :as ptk]
|
||||
[app.main.store :as st]))
|
||||
|
||||
(declare persist-changes)
|
||||
(declare shapes-changes-persisted)
|
||||
|
@ -345,38 +349,123 @@
|
|||
(s/def ::name ::us/string)
|
||||
(s/def ::uri ::us/string)
|
||||
(s/def ::uris (s/coll-of ::uri))
|
||||
(s/def ::mtype ::us/string)
|
||||
|
||||
(s/def ::upload-media-objects
|
||||
(s/and
|
||||
(s/keys :req-un [::file-id ::local?]
|
||||
:opt-in [::name ::data ::uris])
|
||||
:opt-in [::name ::data ::uris ::mtype])
|
||||
(fn [props]
|
||||
(or (contains? props :data)
|
||||
(contains? props :uris)))))
|
||||
|
||||
(defn parse-svg [text]
|
||||
(->> (http/send! {:method :post
|
||||
:uri "/api/svg"
|
||||
:headers {"content-type" "image/svg+xml"}
|
||||
:body text})
|
||||
(rx/map (fn [{:keys [status body]}]
|
||||
(let [result (t/decode body)]
|
||||
(if (= status 200)
|
||||
result
|
||||
(throw result)))))))
|
||||
|
||||
(defn fetch-svg [uri]
|
||||
(->> (http/send! {:method :get :uri uri})
|
||||
(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
|
||||
[{:keys [file-id local? data name uris] :as params}]
|
||||
[{:keys [file-id local? data name uris mtype svg-as-images] :as params}]
|
||||
(us/assert ::upload-media-objects params)
|
||||
(ptk/reify ::upload-media-objects
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity}} (meta params)
|
||||
|
||||
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 name})]
|
||||
(let [{:keys [on-image on-svg on-error]
|
||||
:or {on-image identity
|
||||
on-svg identity}} (meta params)]
|
||||
|
||||
(rx/concat
|
||||
(rx/of (dm/show {:content (tr "media.loading")
|
||||
|
@ -384,31 +473,33 @@
|
|||
:timeout nil
|
||||
:tag :media-loading}))
|
||||
(->> (if (seq uris)
|
||||
(->> (rx/from uris)
|
||||
(rx/map prepare-uri)
|
||||
(rx/mapcat #(rp/mutation! :create-file-media-object-from-url %)))
|
||||
(->> (rx/from data)
|
||||
(rx/map di/validate-file)
|
||||
(rx/map prepare-file)
|
||||
(rx/mapcat #(rp/mutation! :upload-file-media-object %))))
|
||||
(rx/do on-success)
|
||||
(rx/catch (fn [error]
|
||||
(cond
|
||||
(= (:code error) :media-type-not-allowed)
|
||||
(rx/of (dm/error (tr "errors.media-type-not-allowed")))
|
||||
;; Media objects is a list of URL's pointing to the path
|
||||
(upload-uris file-id local? name uris mtype on-image on-svg)
|
||||
;; Media objects are blob of data to be upload
|
||||
(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)
|
||||
(rx/of (dm/error (tr "errors.media-type-mismatch")))
|
||||
(defn upload-media-asset [params]
|
||||
(let [params (-> params
|
||||
(assoc :svg-as-images true)
|
||||
(assoc :local? false)
|
||||
(with-meta {:on-image #(st/emit! (dwl/add-media %))}))]
|
||||
(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))))))))))
|
||||
(defn upload-media-workspace
|
||||
[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)))
|
||||
|
||||
|
||||
;; --- Upload File Media objects
|
||||
|
@ -416,10 +507,10 @@
|
|||
(s/def ::object-id ::us/uuid)
|
||||
|
||||
(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
|
||||
[{:keys [file-id local? object-id] :as params}]
|
||||
[{:keys [file-id object-id] :as params}]
|
||||
(us/assert ::clone-media-objects-params params)
|
||||
(ptk/reify ::clone-media-objects
|
||||
ptk/WatchEvent
|
||||
|
@ -427,7 +518,7 @@
|
|||
(let [{:keys [on-success on-error]
|
||||
:or {on-success identity
|
||||
on-error identity}} (meta params)
|
||||
params {:is-local local?
|
||||
params {:is-local true
|
||||
:file-id file-id
|
||||
:id object-id}]
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
[app.main.ui.shapes.rect :as rect]
|
||||
[app.main.ui.shapes.text :as text]
|
||||
[app.main.ui.shapes.group :as group]
|
||||
[app.main.ui.shapes.svg-raw :as svg-raw]
|
||||
[app.main.ui.shapes.shape :refer [shape-container]]))
|
||||
|
||||
(def ^:private default-color "#E8E9EA") ;; $color-canvas
|
||||
|
@ -77,26 +78,45 @@
|
|||
:is-child-selected? true
|
||||
:childs childs}]))))
|
||||
|
||||
(defn svg-raw-wrapper-factory
|
||||
[objects]
|
||||
(let [shape-wrapper (shape-wrapper-factory objects)
|
||||
svg-raw-shape (svg-raw/svg-raw-shape shape-wrapper)]
|
||||
(mf/fnc svg-raw-wrapper
|
||||
[{:keys [shape frame] :as props}]
|
||||
(let [childs (mapv #(get objects %) (:shapes shape))]
|
||||
[:& svg-raw-shape {:frame frame
|
||||
:shape shape
|
||||
:childs childs}]))))
|
||||
|
||||
(defn shape-wrapper-factory
|
||||
[objects]
|
||||
(mf/fnc shape-wrapper
|
||||
[{:keys [frame shape] :as props}]
|
||||
(let [group-wrapper (mf/use-memo (mf/deps objects) #(group-wrapper-factory objects))
|
||||
svg-raw-wrapper (mf/use-memo (mf/deps objects) #(svg-raw-wrapper-factory objects))
|
||||
frame-wrapper (mf/use-memo (mf/deps objects) #(frame-wrapper-factory objects))]
|
||||
(when (and shape (not (:hidden shape)))
|
||||
(let [shape (-> (gsh/transform-shape shape)
|
||||
(gsh/translate-to-frame frame))
|
||||
opts #js {:shape shape}]
|
||||
[:> shape-container {:shape shape}
|
||||
(case (:type shape)
|
||||
:text [:> text/text-shape opts]
|
||||
:rect [:> rect/rect-shape opts]
|
||||
:path [:> path/path-shape opts]
|
||||
:image [:> image/image-shape opts]
|
||||
:circle [:> circle/circle-shape opts]
|
||||
:frame [:> frame-wrapper {:shape shape}]
|
||||
:group [:> group-wrapper {:shape shape :frame frame}]
|
||||
nil)])))))
|
||||
opts #js {:shape shape}
|
||||
svg-element? (and (= :svg-raw (:type shape))
|
||||
(not= :svg (get-in shape [:content :tag])))]
|
||||
(if-not svg-element?
|
||||
[:> shape-container {:shape shape}
|
||||
(case (:type shape)
|
||||
:text [:> text/text-shape opts]
|
||||
:rect [:> rect/rect-shape opts]
|
||||
:path [:> path/path-shape opts]
|
||||
:image [:> image/image-shape opts]
|
||||
:circle [:> circle/circle-shape opts]
|
||||
:frame [:> frame-wrapper {:shape shape}]
|
||||
:group [:> group-wrapper {:shape shape :frame frame}]
|
||||
:svg-raw [:> svg-raw-wrapper {:shape shape :frame frame}]
|
||||
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}}]
|
||||
(str/fmt "%s %s %s %s" x y width height))
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
(def embed-ctx (mf/create-context false))
|
||||
(def render-ctx (mf/create-context nil))
|
||||
(def def-ctx (mf/create-context false))
|
||||
|
||||
(def current-route (mf/create-context nil))
|
||||
(def current-team-id (mf/create-context nil))
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
[app.main.ui.shapes.circle :as circle]
|
||||
[app.main.ui.shapes.frame :as frame]
|
||||
[app.main.ui.shapes.group :as group]
|
||||
[app.main.ui.shapes.svg-raw :as svg-raw]
|
||||
[app.main.ui.shapes.image :as image]
|
||||
[app.main.ui.shapes.path :as path]
|
||||
[app.main.ui.shapes.rect :as rect]
|
||||
|
@ -61,16 +62,24 @@
|
|||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
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])))]
|
||||
|
||||
[:> shape-container {:shape shape
|
||||
:on-mouse-enter (handle-hover-shape shape true)
|
||||
:on-mouse-leave (handle-hover-shape shape false)
|
||||
:on-click (select-shape shape)}
|
||||
[:& component {:shape shape
|
||||
:frame frame
|
||||
:childs childs
|
||||
:is-child-selected? true}]])))
|
||||
(if-not svg-element?
|
||||
[:> shape-container {:shape shape
|
||||
:on-mouse-enter (handle-hover-shape shape true)
|
||||
:on-mouse-leave (handle-hover-shape shape false)
|
||||
:on-click (select-shape shape)}
|
||||
[:& component {:shape shape
|
||||
:frame frame
|
||||
:childs childs
|
||||
: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
|
||||
[objects]
|
||||
|
@ -105,6 +114,21 @@
|
|||
(obj/merge! #js {:childs childs}))]
|
||||
[:> group-wrapper props]))))
|
||||
|
||||
(defn svg-raw-container-factory
|
||||
[objects]
|
||||
(let [shape-container (shape-container-factory objects)
|
||||
svg-raw-shape (svg-raw/svg-raw-shape shape-container)
|
||||
svg-raw-wrapper (shape-wrapper-factory svg-raw-shape)]
|
||||
(mf/fnc group-container
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
childs (mapv #(get objects %) (:shapes shape))
|
||||
props (-> (obj/new)
|
||||
(obj/merge! props)
|
||||
(obj/merge! #js {:childs childs}))]
|
||||
[:> svg-raw-wrapper props]))))
|
||||
|
||||
(defn shape-container-factory
|
||||
[objects show-interactions?]
|
||||
(let [path-wrapper (shape-wrapper-factory path/path-shape)
|
||||
|
@ -119,19 +143,23 @@
|
|||
frame (unchecked-get props "frame")
|
||||
group-container (mf/use-memo
|
||||
(mf/deps objects)
|
||||
#(group-container-factory objects))]
|
||||
#(group-container-factory objects))
|
||||
svg-raw-container (mf/use-memo
|
||||
(mf/deps objects)
|
||||
#(svg-raw-container-factory objects))]
|
||||
(when (and shape (not (:hidden shape)))
|
||||
(let [shape (-> (geom/transform-shape shape)
|
||||
(geom/translate-to-frame frame))
|
||||
opts #js {:shape shape
|
||||
:frame frame}]
|
||||
(case (:type shape)
|
||||
:text [:> text-wrapper opts]
|
||||
:rect [:> rect-wrapper opts]
|
||||
:path [:> path-wrapper opts]
|
||||
:image [:> image-wrapper opts]
|
||||
:circle [:> circle-wrapper opts]
|
||||
:group [:> group-container opts])))))))
|
||||
:text [:> text-wrapper opts]
|
||||
:rect [:> rect-wrapper opts]
|
||||
:path [:> path-wrapper opts]
|
||||
:image [:> image-wrapper opts]
|
||||
:circle [:> circle-wrapper opts]
|
||||
:group [:> group-container opts]
|
||||
:svg-raw [:> svg-raw-container opts])))))))
|
||||
|
||||
(defn adjust-frame-position [frame-id objects]
|
||||
(let [frame (get objects frame-id)
|
||||
|
|
|
@ -23,15 +23,22 @@
|
|||
nil))
|
||||
|
||||
(defn add-border-radius [attrs shape]
|
||||
(obj/merge! attrs #js {:rx (:rx shape)
|
||||
:ry (:ry shape)}))
|
||||
(if (or (:rx shape) (:ry shape))
|
||||
(obj/merge! attrs #js {:rx (:rx shape)
|
||||
:ry (:ry shape)})
|
||||
attrs))
|
||||
|
||||
(defn add-fill [attrs shape render-id]
|
||||
(let [fill-color-gradient-id (str "fill-color-gradient_" render-id)]
|
||||
(if (:fill-color-gradient shape)
|
||||
(cond
|
||||
(:fill-color-gradient shape)
|
||||
(obj/merge! attrs #js {:fill (str/format "url(#%s)" fill-color-gradient-id)})
|
||||
|
||||
(or (:fill-color shape) (:fill-opacity shape))
|
||||
(obj/merge! attrs #js {:fill (or (:fill-color shape) "transparent")
|
||||
:fillOpacity (:fill-opacity shape nil)}))))
|
||||
:fillOpacity (:fill-opacity shape nil)})
|
||||
|
||||
:else attrs)))
|
||||
|
||||
(defn add-stroke [attrs shape render-id]
|
||||
(let [stroke-style (:stroke-style shape :none)
|
||||
|
|
|
@ -9,12 +9,12 @@
|
|||
|
||||
(ns app.main.ui.shapes.filters
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[cuerdas.core :as str]
|
||||
[app.util.color :as color]
|
||||
[app.common.data :as d]
|
||||
[app.common.math :as mth]
|
||||
[app.common.uuid :as uuid]))
|
||||
[app.common.uuid :as uuid]
|
||||
[app.util.color :as color]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(defn get-filter-id []
|
||||
(str "filter_" (uuid/next)))
|
||||
|
@ -109,37 +109,6 @@
|
|||
:in2 filter-in
|
||||
:result filter-id}])
|
||||
|
||||
(defn filter-bounds [shape filter-entry]
|
||||
(let [{:keys [x y width height]} (:selrect shape)
|
||||
{:keys [offset-x offset-y blur spread] :or {offset-x 0 offset-y 0 blur 0 spread 0}} (:params filter-entry)
|
||||
filter-x (min x (+ x offset-x (- spread) (- blur) -5))
|
||||
filter-y (min y (+ y offset-y (- spread) (- blur) -5))
|
||||
filter-width (+ width (mth/abs offset-x) (* spread 2) (* blur 2) 10)
|
||||
filter-height (+ height (mth/abs offset-x) (* spread 2) (* blur 2) 10)]
|
||||
{:x1 filter-x
|
||||
:y1 filter-y
|
||||
:x2 (+ filter-x filter-width)
|
||||
:y2 (+ filter-y filter-height)}))
|
||||
|
||||
(defn get-filters-bounds
|
||||
[shape filters blur-value]
|
||||
|
||||
(let [filter-bounds (->> filters
|
||||
(filter #(= :drop-shadow (:type %)))
|
||||
(map (partial filter-bounds shape) ))
|
||||
;; We add the selrect so the minimum size will be the selrect
|
||||
filter-bounds (conj filter-bounds (:selrect shape))
|
||||
x1 (apply min (map :x1 filter-bounds))
|
||||
y1 (apply min (map :y1 filter-bounds))
|
||||
x2 (apply max (map :x2 filter-bounds))
|
||||
y2 (apply max (map :y2 filter-bounds))
|
||||
|
||||
x1 (- x1 (* blur-value 2))
|
||||
x2 (+ x2 (* blur-value 2))
|
||||
y1 (- y1 (* blur-value 2))
|
||||
y2 (+ y2 (* blur-value 2))]
|
||||
[x1 y1 (- x2 x1) (- y2 y1)]))
|
||||
|
||||
(defn blur-filters [type value]
|
||||
(->> [value]
|
||||
(remove :hidden)
|
||||
|
@ -185,18 +154,11 @@
|
|||
(->> shape :blur (blur-filters :layer-blur)))
|
||||
|
||||
;; Adds the previous filter as `filter-in` parameter
|
||||
filters (map #(assoc %1 :filter-in %2) filters (cons nil (map :id filters)))
|
||||
|
||||
[filter-x filter-y filter-width filter-height] (get-filters-bounds shape filters (or (-> shape :blur :value) 0))]
|
||||
filters (map #(assoc %1 :filter-in %2) filters (cons nil (map :id filters)))]
|
||||
|
||||
[:*
|
||||
(when (> (count filters) 2)
|
||||
[:filter {:id filter-id
|
||||
:x filter-x
|
||||
:y filter-y
|
||||
:width filter-width
|
||||
:height filter-height
|
||||
:filterUnits "userSpaceOnUse"
|
||||
:color-interpolation-filters "sRGB"}
|
||||
|
||||
(for [entry filters]
|
||||
|
|
123
frontend/src/app/main/ui/shapes/svg_raw.cljs
Normal file
123
frontend/src/app/main/ui/shapes/svg_raw.cljs
Normal file
|
@ -0,0 +1,123 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns app.main.ui.shapes.svg-raw
|
||||
(:require
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.main.ui.shapes.attrs :as usa]
|
||||
[app.util.data :as d]
|
||||
[app.util.object :as obj]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(defn clean-attrs
|
||||
"Transforms attributes to their react equivalent"
|
||||
[attrs]
|
||||
(letfn [(transform-key [key]
|
||||
(-> (name key)
|
||||
(str/replace ":" "-")
|
||||
(str/camel)
|
||||
(keyword)))
|
||||
|
||||
(format-styles [style-str]
|
||||
(->> (str/split style-str ";")
|
||||
(map str/trim)
|
||||
(map #(str/split % ":"))
|
||||
(group-by first)
|
||||
(map (fn [[key val]]
|
||||
(vector
|
||||
(transform-key key)
|
||||
(second (first val)))))
|
||||
(into {})))
|
||||
|
||||
(map-fn [[key val]]
|
||||
(cond
|
||||
(= key :style) [key (format-styles val)]
|
||||
:else (vector (transform-key key) val)))]
|
||||
|
||||
(->> attrs
|
||||
(map map-fn)
|
||||
(into {}))))
|
||||
|
||||
(defn vbox->rect
|
||||
"Converts the viewBox into a rectangle"
|
||||
[vbox]
|
||||
(when vbox
|
||||
(let [[x y width height] (map d/parse-float (str/split vbox " "))]
|
||||
{:x x :y y :width width :height height})))
|
||||
|
||||
(defn vbox-center [shape]
|
||||
(let [vbox-rect (-> (get-in shape [:content :attrs :viewBox] "0 0 100 100")
|
||||
(vbox->rect))]
|
||||
(gsh/center-rect vbox-rect)))
|
||||
|
||||
(defn vbox-bounds [shape]
|
||||
(let [vbox-rect (-> (get-in shape [:content :attrs :viewBox] "0 0 100 100")
|
||||
(vbox->rect))
|
||||
vbox-center (gsh/center-rect vbox-rect)
|
||||
transform (gsh/transform-matrix shape nil vbox-center)]
|
||||
(-> (gsh/rect->points vbox-rect)
|
||||
(gsh/transform-points vbox-center transform)
|
||||
(gsh/points->rect))) )
|
||||
|
||||
(defn transform-viewbox [shape]
|
||||
(let [center (vbox-center shape)
|
||||
bounds (vbox-bounds shape)
|
||||
{:keys [x y width height]} (gsh/center->rect center (:width bounds) (:height bounds))]
|
||||
(str x " " y " " width " " height)))
|
||||
|
||||
(defn svg-raw-shape [shape-wrapper]
|
||||
(mf/fnc svg-raw-shape
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [frame (unchecked-get props "frame")
|
||||
shape (unchecked-get props "shape")
|
||||
childs (unchecked-get props "childs")
|
||||
|
||||
{:keys [tag attrs] :as content} (:content shape)
|
||||
|
||||
attrs (obj/merge! (clj->js (clean-attrs attrs))
|
||||
(usa/extract-style-attrs shape))]
|
||||
|
||||
(cond
|
||||
;; Root SVG TAG
|
||||
(and (map? content) (= tag :svg))
|
||||
(let [;; {:keys [x y width height]} (-> (:points shape) gsh/points->selrect)
|
||||
{:keys [x y width height]} shape
|
||||
attrs (-> attrs
|
||||
(obj/set! "x" x)
|
||||
(obj/set! "y" y)
|
||||
(obj/set! "width" width)
|
||||
(obj/set! "height" height)
|
||||
(obj/set! "preserveAspectRatio" "none")
|
||||
#_(obj/set! "viewBox" (transform-viewbox shape)))]
|
||||
|
||||
[:g.svg-raw {:transform (gsh/transform-matrix shape)}
|
||||
[:> "svg" attrs
|
||||
(for [item childs]
|
||||
[:& shape-wrapper {:frame frame
|
||||
:shape item
|
||||
:key (:id item)}])]])
|
||||
|
||||
;; Other tags different than root
|
||||
(map? content)
|
||||
[:> (name tag) attrs
|
||||
(for [item childs]
|
||||
[:& shape-wrapper {:frame frame
|
||||
:shape item
|
||||
:key (:id item)}])]
|
||||
|
||||
;; String content
|
||||
(string? content) content
|
||||
|
||||
:else nil))))
|
||||
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
[app.main.ui.shapes.circle :as circle]
|
||||
[app.main.ui.shapes.frame :as frame]
|
||||
[app.main.ui.shapes.group :as group]
|
||||
[app.main.ui.shapes.svg-raw :as svg-raw]
|
||||
[app.main.ui.shapes.image :as image]
|
||||
[app.main.ui.shapes.path :as path]
|
||||
[app.main.ui.shapes.rect :as rect]
|
||||
|
@ -54,24 +55,33 @@
|
|||
|
||||
on-mouse-down (mf/use-callback
|
||||
(mf/deps shape)
|
||||
#(on-mouse-down % shape))]
|
||||
#(on-mouse-down % shape))
|
||||
|
||||
[:> shape-container {:shape shape
|
||||
:on-mouse-down on-mouse-down
|
||||
:cursor (when (seq (:interactions shape)) "pointer")}
|
||||
[:& component {:shape shape
|
||||
:frame frame
|
||||
:childs childs
|
||||
:is-child-selected? true}]
|
||||
(when (and (:interactions shape) show-interactions?)
|
||||
[:rect {:x (- x 1)
|
||||
:y (- y 1)
|
||||
:width (+ width 2)
|
||||
:height (+ height 2)
|
||||
:fill "#31EFB8"
|
||||
:stroke "#31EFB8"
|
||||
:stroke-width 1
|
||||
:fill-opacity 0.2}])])))
|
||||
svg-element? (and (= :svg-raw (:type shape))
|
||||
(not= :svg (get-in shape [:content :tag])))]
|
||||
|
||||
(if-not svg-element?
|
||||
[:> shape-container {:shape shape
|
||||
:on-mouse-down on-mouse-down
|
||||
:cursor (when (seq (:interactions shape)) "pointer")}
|
||||
[:& component {:shape shape
|
||||
:frame frame
|
||||
:childs childs
|
||||
:is-child-selected? true}]
|
||||
(when (and (:interactions shape) show-interactions?)
|
||||
[:rect {:x (- x 1)
|
||||
:y (- y 1)
|
||||
:width (+ width 2)
|
||||
:height (+ height 2)
|
||||
:fill "#31EFB8"
|
||||
:stroke "#31EFB8"
|
||||
:stroke-width 1
|
||||
: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
|
||||
[shape-container show-interactions?]
|
||||
|
@ -81,6 +91,10 @@
|
|||
[shape-container show-interactions?]
|
||||
(generic-wrapper-factory (group/group-shape shape-container) show-interactions?))
|
||||
|
||||
(defn svg-raw-wrapper
|
||||
[shape-container show-interactions?]
|
||||
(generic-wrapper-factory (svg-raw/svg-raw-shape shape-container) show-interactions?))
|
||||
|
||||
(defn rect-wrapper
|
||||
[show-interactions?]
|
||||
(generic-wrapper-factory rect/rect-shape show-interactions?))
|
||||
|
@ -133,6 +147,20 @@
|
|||
:show-interactions? show-interactions?})]
|
||||
[:> group-wrapper props]))))
|
||||
|
||||
(defn svg-raw-container-factory
|
||||
[objects show-interactions?]
|
||||
(let [shape-container (shape-container-factory objects show-interactions?)
|
||||
svg-raw-wrapper (svg-raw-wrapper shape-container show-interactions?)]
|
||||
(mf/fnc svg-raw-container
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
childs (mapv #(get objects %) (:shapes shape))
|
||||
props (obj/merge! #js {} props
|
||||
#js {:childs childs
|
||||
:show-interactions? show-interactions?})]
|
||||
[:> svg-raw-wrapper props]))))
|
||||
|
||||
(defn shape-container-factory
|
||||
[objects show-interactions?]
|
||||
(let [path-wrapper (path-wrapper show-interactions?)
|
||||
|
@ -144,8 +172,11 @@
|
|||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [group-container (mf/use-memo
|
||||
(mf/deps objects)
|
||||
#(group-container-factory objects show-interactions?))
|
||||
(mf/deps objects)
|
||||
#(group-container-factory objects show-interactions?))
|
||||
svg-raw-container (mf/use-memo
|
||||
(mf/deps objects)
|
||||
#(svg-raw-container-factory objects show-interactions?))
|
||||
shape (unchecked-get props "shape")
|
||||
frame (unchecked-get props "frame")]
|
||||
(when (and shape (not (:hidden shape)))
|
||||
|
@ -153,15 +184,14 @@
|
|||
(geom/translate-to-frame frame))
|
||||
opts #js {:shape shape}]
|
||||
(case (:type shape)
|
||||
:frame [:g.empty]
|
||||
:text [:> text-wrapper opts]
|
||||
:rect [:> rect-wrapper opts]
|
||||
:path [:> path-wrapper opts]
|
||||
:image [:> image-wrapper opts]
|
||||
:circle [:> circle-wrapper opts]
|
||||
:group [:> group-container
|
||||
{:shape shape
|
||||
:frame frame}])))))))
|
||||
:frame [:g.empty]
|
||||
:text [:> text-wrapper opts]
|
||||
:rect [:> rect-wrapper opts]
|
||||
:path [:> path-wrapper opts]
|
||||
:image [:> image-wrapper opts]
|
||||
:circle [:> circle-wrapper opts]
|
||||
:group [:> group-container {:shape shape :frame frame}]
|
||||
:svg-raw [:> svg-raw-container {:shape shape :frame frame}])))))))
|
||||
|
||||
(mf/defc frame-svg
|
||||
{::mf/wrap [mf/memo]}
|
||||
|
|
|
@ -9,16 +9,17 @@
|
|||
|
||||
(ns app.main.ui.workspace.left-toolbar
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.media :as cm]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[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.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/wrap [mf/memo]}
|
||||
|
@ -29,28 +30,13 @@
|
|||
on-click
|
||||
(mf/use-callback #(dom/click (mf/ref-val ref)))
|
||||
|
||||
on-uploaded
|
||||
(mf/use-callback
|
||||
(fn [image]
|
||||
(->> {:name (:name image)
|
||||
:width (:width image)
|
||||
:height (:height image)
|
||||
:metadata {:width (:width image)
|
||||
:height (:height image)
|
||||
:mtype (:mtype image)
|
||||
:id (:id image)}}
|
||||
(dw/create-and-add-shape :image 0 0)
|
||||
(st/emit!))))
|
||||
|
||||
on-files-selected
|
||||
(mf/use-callback
|
||||
(mf/deps file)
|
||||
(fn [blobs]
|
||||
(st/emit! (dw/upload-media-objects
|
||||
(with-meta {:file-id (:id file)
|
||||
:local? true
|
||||
:data (seq blobs)}
|
||||
{:on-success on-uploaded})))))]
|
||||
(let [params {:file-id (:id file)
|
||||
:data (seq blobs)}]
|
||||
(st/emit! (dw/upload-media-workspace params (gpt/point 0 0))))))]
|
||||
|
||||
[:li.tooltip.tooltip-right
|
||||
{:alt (tr "workspace.toolbar.image")
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
[app.main.ui.workspace.shapes.common :as common]
|
||||
[app.main.ui.workspace.shapes.frame :as frame]
|
||||
[app.main.ui.workspace.shapes.group :as group]
|
||||
[app.main.ui.workspace.shapes.svg-raw :as svg-raw]
|
||||
[app.main.ui.workspace.shapes.path :as path]
|
||||
[app.main.ui.workspace.shapes.text :as text]
|
||||
[app.util.object :as obj]
|
||||
|
@ -37,6 +38,7 @@
|
|||
[rumext.alpha :as mf]))
|
||||
|
||||
(declare group-wrapper)
|
||||
(declare svg-raw-wrapper)
|
||||
(declare frame-wrapper)
|
||||
|
||||
(def circle-wrapper (common/generic-wrapper-factory circle/circle-shape))
|
||||
|
@ -80,27 +82,37 @@
|
|||
alt? (hooks/use-rxsub ms/keyboard-alt)
|
||||
|
||||
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
|
||||
(or ghost? (not moving?))
|
||||
(not (:hidden shape)))
|
||||
[:g.shape-wrapper {:style {:cursor (if alt? cur/duplicate nil)}}
|
||||
(case (:type shape)
|
||||
:path [:> path/path-wrapper opts]
|
||||
:text [:> text/text-wrapper opts]
|
||||
:group [:> group-wrapper opts]
|
||||
:rect [:> rect-wrapper opts]
|
||||
:image [:> image-wrapper opts]
|
||||
:circle [:> circle-wrapper opts]
|
||||
[:*
|
||||
(if-not svg-element?
|
||||
[:g.shape-wrapper {:style {:cursor (if alt? cur/duplicate nil)}}
|
||||
(case (:type shape)
|
||||
:path [:> path/path-wrapper opts]
|
||||
:text [:> text/text-wrapper opts]
|
||||
:group [:> group-wrapper opts]
|
||||
:rect [:> rect-wrapper opts]
|
||||
:image [:> image-wrapper opts]
|
||||
:circle [:> circle-wrapper opts]
|
||||
:svg-raw [:> svg-raw-wrapper opts]
|
||||
|
||||
;; Only used when drawing a new frame.
|
||||
:frame [:> frame-wrapper {:shape shape}]
|
||||
nil)
|
||||
;; Only used when drawing a new frame.
|
||||
:frame [:> frame-wrapper {:shape shape}]
|
||||
|
||||
nil)]
|
||||
|
||||
;; 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 svg-raw-wrapper (svg-raw/svg-raw-wrapper-factory shape-wrapper))
|
||||
(def frame-wrapper (frame/frame-wrapper-factory shape-wrapper))
|
||||
|
||||
|
|
93
frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs
Normal file
93
frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs
Normal file
|
@ -0,0 +1,93 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns app.main.ui.workspace.shapes.svg-raw
|
||||
(:require
|
||||
[app.main.refs :as refs]
|
||||
[app.main.ui.shapes.svg-raw :as svg-raw]
|
||||
[app.main.ui.shapes.shape :refer [shape-container]]
|
||||
[app.main.ui.workspace.effects :as we]
|
||||
[rumext.alpha :as mf]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.main.ui.context :as muc]))
|
||||
|
||||
;; This is a list of svg tags that can be grouped in shape-container
|
||||
;; this allows them to have gradients, shadows and masks
|
||||
(def svg-elements #{:svg :circle :ellipse :image :line :path :polygon :polyline :rect :symbol :text :textPath})
|
||||
|
||||
(defn- svg-raw-wrapper-factory-equals?
|
||||
[np op]
|
||||
(let [n-shape (unchecked-get np "shape")
|
||||
o-shape (unchecked-get op "shape")
|
||||
n-frame (unchecked-get np "frame")
|
||||
o-frame (unchecked-get op "frame")]
|
||||
(and (= n-frame o-frame)
|
||||
(= n-shape o-shape))))
|
||||
|
||||
(defn svg-raw-wrapper-factory
|
||||
[shape-wrapper]
|
||||
(let [svg-raw-shape (svg-raw/svg-raw-shape shape-wrapper)]
|
||||
(mf/fnc svg-raw-wrapper
|
||||
{::mf/wrap [#(mf/memo' % svg-raw-wrapper-factory-equals?)]
|
||||
::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
frame (unchecked-get props "frame")
|
||||
|
||||
{:keys [id x y width height]} shape
|
||||
|
||||
childs-ref (mf/use-memo (mf/deps shape) #(refs/objects-by-id (:shapes shape)))
|
||||
childs (mf/deref childs-ref)
|
||||
|
||||
{:keys [id x y width height]} shape
|
||||
transform (gsh/transform-matrix shape)
|
||||
|
||||
tag (get-in shape [:content :tag])
|
||||
|
||||
handle-mouse-down (we/use-mouse-down shape)
|
||||
handle-context-menu (we/use-context-menu shape)
|
||||
handle-pointer-enter (we/use-pointer-enter shape)
|
||||
handle-pointer-leave (we/use-pointer-leave shape)
|
||||
|
||||
def-ctx? (mf/use-ctx muc/def-ctx)]
|
||||
|
||||
(cond
|
||||
(and (contains? svg-elements tag) (not def-ctx?))
|
||||
[:> shape-container { :shape shape }
|
||||
[:& svg-raw-shape
|
||||
{:frame frame
|
||||
:shape shape
|
||||
:childs childs}]
|
||||
|
||||
(when (= tag :svg)
|
||||
[:rect.group-actions
|
||||
{:x x
|
||||
:y y
|
||||
:transform transform
|
||||
:width width
|
||||
:height height
|
||||
:fill "transparent"
|
||||
:on-mouse-down handle-mouse-down
|
||||
:on-context-menu handle-context-menu
|
||||
:on-pointer-over handle-pointer-enter
|
||||
:on-pointer-out handle-pointer-leave}])]
|
||||
|
||||
;; We cannot wrap inside groups the shapes that go inside the defs tag
|
||||
;; we use the context so we know when we should not render the container
|
||||
(= tag :defs)
|
||||
[:& (mf/provider muc/def-ctx) {:value true}
|
||||
[:& svg-raw-shape {:frame frame
|
||||
:shape shape
|
||||
:childs childs}]]
|
||||
|
||||
:else
|
||||
[:& svg-raw-shape {:frame frame
|
||||
:shape shape
|
||||
:childs childs}])))))
|
||||
|
|
@ -164,11 +164,9 @@
|
|||
(mf/use-callback
|
||||
(mf/deps file-id)
|
||||
(fn [blobs]
|
||||
(let [params (with-meta {:file-id file-id
|
||||
:local? false
|
||||
:data (seq blobs)}
|
||||
{:on-success on-media-uploaded})]
|
||||
(st/emit! (dw/upload-media-objects params)))))
|
||||
(let [params {:file-id file-id
|
||||
:data (seq blobs)}]
|
||||
(st/emit! (dw/upload-media-asset params)))))
|
||||
|
||||
on-delete
|
||||
(mf/use-callback
|
||||
|
@ -212,9 +210,10 @@
|
|||
|
||||
on-drag-start
|
||||
(mf/use-callback
|
||||
(fn [{:keys [name id]} event]
|
||||
(fn [{:keys [name id mtype]} event]
|
||||
(dnd/set-data! event "text/asset-id" (str id))
|
||||
(dnd/set-data! event "text/asset-name" name)
|
||||
(dnd/set-data! event "text/asset-type" mtype)
|
||||
(dnd/set-allowed-effect! event "move")))]
|
||||
|
||||
[:div.asset-group
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
(if (:masked-group? shape)
|
||||
i/mask
|
||||
i/folder))
|
||||
:svg-raw i/file-svg
|
||||
nil))
|
||||
|
||||
;; --- Layer Name
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
[app.main.ui.workspace.sidebar.options.path :as path]
|
||||
[app.main.ui.workspace.sidebar.options.rect :as rect]
|
||||
[app.main.ui.workspace.sidebar.options.text :as text]
|
||||
[app.main.ui.workspace.sidebar.options.text :as text]
|
||||
[app.main.ui.workspace.sidebar.options.svg-raw :as svg-raw]
|
||||
[app.util.i18n :as i18n :refer [tr t]]
|
||||
[app.util.object :as obj]
|
||||
[beicon.core :as rx]
|
||||
|
@ -42,14 +42,15 @@
|
|||
[{:keys [shape shapes-with-children page-id file-id]}]
|
||||
[:*
|
||||
(case (:type shape)
|
||||
:frame [:& frame/options {:shape shape}]
|
||||
:group [:& group/options {:shape shape :shape-with-children shapes-with-children}]
|
||||
:text [:& text/options {:shape shape}]
|
||||
:rect [:& rect/options {:shape shape}]
|
||||
:icon [:& icon/options {:shape shape}]
|
||||
:circle [:& circle/options {:shape shape}]
|
||||
:path [:& path/options {:shape shape}]
|
||||
:image [:& image/options {:shape shape}]
|
||||
:frame [:& frame/options {:shape shape}]
|
||||
:group [:& group/options {:shape shape :shape-with-children shapes-with-children}]
|
||||
:text [:& text/options {:shape shape}]
|
||||
:rect [:& rect/options {:shape shape}]
|
||||
:icon [:& icon/options {:shape shape}]
|
||||
:circle [:& circle/options {:shape shape}]
|
||||
:path [:& path/options {:shape shape}]
|
||||
:image [:& image/options {:shape shape}]
|
||||
:svg-raw [:& svg-raw/options {:shape shape}]
|
||||
nil)
|
||||
[:& exports-menu
|
||||
{:shape shape
|
||||
|
@ -105,3 +106,4 @@
|
|||
:page-id page-id
|
||||
:section section}]))
|
||||
|
||||
|
||||
|
|
|
@ -72,6 +72,14 @@
|
|||
:text :ignore}
|
||||
|
||||
:circle
|
||||
{:measure :shape
|
||||
:fill :shape
|
||||
:shadow :shape
|
||||
:blur :shape
|
||||
:stroke :shape
|
||||
:text :ignore}
|
||||
|
||||
:svg-raw
|
||||
{:measure :shape
|
||||
:fill :shape
|
||||
:shadow :shape
|
||||
|
|
114
frontend/src/app/main/ui/workspace/sidebar/options/svg_raw.cljs
Normal file
114
frontend/src/app/main/ui/workspace/sidebar/options/svg_raw.cljs
Normal file
|
@ -0,0 +1,114 @@
|
|||
;; 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/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns app.main.ui.workspace.sidebar.options.svg-raw
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[cuerdas.core :as str]
|
||||
[app.util.data :as d]
|
||||
[app.main.ui.workspace.sidebar.options.measures :refer [measure-attrs measures-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.fill :refer [fill-attrs fill-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.stroke :refer [stroke-attrs stroke-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.shadow :refer [shadow-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.blur :refer [blur-menu]]))
|
||||
|
||||
;; This is a list of svg tags that can be grouped in shape-container
|
||||
;; this allows them to have gradients, shadows and masks
|
||||
(def svg-elements #{:svg :circle :ellipse :image :line :path :polygon :polyline :rect :symbol :text :textPath})
|
||||
|
||||
(defn hex->number [hex] 1)
|
||||
(defn shorthex->longhex [hex]
|
||||
(let [[_ r g b] hex]
|
||||
(str "#" r r g g b b)))
|
||||
|
||||
(defn parse-color [color]
|
||||
(cond
|
||||
(or (not color) (= color "none")) nil
|
||||
|
||||
(and (str/starts-with? color "#") (= (count color) 4))
|
||||
{:color (shorthex->longhex color)
|
||||
:opacity 1}
|
||||
|
||||
(and (str/starts-with? color "#") (= (count color) 9))
|
||||
{:color (subs color 1 6)
|
||||
:opacity (-> (subs color 7 2) (hex->number))}
|
||||
|
||||
;; TODO CHECK IF IT'S A GRADIENT
|
||||
|
||||
:else nil))
|
||||
|
||||
|
||||
(defn get-fill-values [shape]
|
||||
(let [fill-values (or (select-keys shape fill-attrs))
|
||||
color (-> (get-in shape [:content :attrs :fill])
|
||||
(parse-color))
|
||||
|
||||
fill-values (if (and (empty? fill-values) color)
|
||||
{:fill-color (:color color)
|
||||
:fill-opacity (:opacity color)}
|
||||
fill-values)]
|
||||
fill-values))
|
||||
|
||||
(defn get-stroke-values [shape]
|
||||
(let [stroke-values (or (select-keys shape stroke-attrs))
|
||||
color (-> (get-in shape [:content :attrs :stroke])
|
||||
(parse-color))
|
||||
|
||||
stroke-color (:color color "#000000")
|
||||
stroke-opacity (:opacity color 1)
|
||||
stroke-style (-> (get-in shape [:content :attrs :stroke-style] (if color "solid" "none"))
|
||||
keyword)
|
||||
stroke-alignment :center
|
||||
stroke-width (-> (get-in shape [:content :attrs :stroke-width] "1")
|
||||
(d/parse-int))
|
||||
|
||||
stroke-values (if (empty? stroke-values)
|
||||
{:stroke-color stroke-color
|
||||
:stroke-opacity stroke-opacity
|
||||
:stroke-style stroke-style
|
||||
:stroke-alignment stroke-alignment
|
||||
:stroke-width stroke-width}
|
||||
|
||||
stroke-values)]
|
||||
stroke-values))
|
||||
|
||||
(mf/defc options
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [shape] :as props}]
|
||||
|
||||
(let [ids [(:id shape)]
|
||||
type (:type shape)
|
||||
{:keys [tag attrs] :as content} (:content shape)
|
||||
measure-values (select-keys shape measure-attrs)
|
||||
fill-values (get-fill-values shape)
|
||||
stroke-values (get-stroke-values shape)]
|
||||
|
||||
(when (contains? svg-elements tag)
|
||||
[:*
|
||||
(cond
|
||||
(= tag :svg)
|
||||
[:*
|
||||
[:& measures-menu {:ids ids
|
||||
:type type
|
||||
:values measure-values}]]
|
||||
|
||||
:else
|
||||
[:*
|
||||
[:& fill-menu {:ids ids
|
||||
:type type
|
||||
:values fill-values}]
|
||||
[:& stroke-menu {:ids ids
|
||||
:type type
|
||||
:values stroke-values}]])
|
||||
|
||||
[:& shadow-menu {:ids ids
|
||||
:values (select-keys shape [:shadow])}]
|
||||
|
||||
[:& blur-menu {:ids ids
|
||||
:values (select-keys shape [:blur])}]])))
|
|
@ -14,6 +14,7 @@
|
|||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as mth]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cfg]
|
||||
[app.main.constants :as c]
|
||||
[app.main.data.colors :as dwc]
|
||||
[app.main.data.fetch :as mdf]
|
||||
|
@ -39,22 +40,22 @@
|
|||
[app.main.ui.workspace.shapes :refer [shape-wrapper frame-wrapper]]
|
||||
[app.main.ui.workspace.shapes.interactions :refer [interactions]]
|
||||
[app.main.ui.workspace.shapes.outline :refer [outline]]
|
||||
[app.main.ui.workspace.shapes.path.actions :refer [path-actions]]
|
||||
[app.main.ui.workspace.snap-distances :refer [snap-distances]]
|
||||
[app.main.ui.workspace.snap-points :refer [snap-points]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.dom.dnd :as dnd]
|
||||
[app.util.http :as http]
|
||||
[app.util.object :as obj]
|
||||
[app.util.perf :as perf]
|
||||
[app.util.timers :as timers]
|
||||
[app.util.http :as http]
|
||||
[beicon.core :as rx]
|
||||
[clojure.set :as set]
|
||||
[cuerdas.core :as str]
|
||||
[goog.events :as events]
|
||||
[potok.core :as ptk]
|
||||
[promesa.core :as p]
|
||||
[rumext.alpha :as mf]
|
||||
[app.main.ui.workspace.shapes.path.actions :refer [path-actions]])
|
||||
[rumext.alpha :as mf])
|
||||
(:import goog.events.EventType))
|
||||
|
||||
;; --- Coordinates Widget
|
||||
|
@ -452,28 +453,20 @@
|
|||
(dnd/has-type? e "text/asset-id"))
|
||||
(dom/prevent-default e))))
|
||||
|
||||
on-uploaded
|
||||
on-image-uploaded
|
||||
(mf/use-callback
|
||||
(fn [image {:keys [x y]}]
|
||||
(prn "on-uploaded" image x y)
|
||||
(let [shape {:name (:name image)
|
||||
:width (:width image)
|
||||
:height (:height image)
|
||||
:x (- x (/ (:width image) 2))
|
||||
:y (- y (/ (:height image) 2))
|
||||
:metadata {:width (:width image)
|
||||
:height (:height image)
|
||||
:name (:name image)
|
||||
:id (:id image)
|
||||
:mtype (:mtype image)}}]
|
||||
(st/emit! (dw/create-and-add-shape :image x y shape)))))
|
||||
(st/emit! (dw/image-uploaded image x y))))
|
||||
|
||||
on-drop
|
||||
(mf/use-callback
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(let [point (gpt/point (.-clientX event) (.-clientY event))
|
||||
viewport-coord (translate-point-to-viewport point)]
|
||||
viewport-coord (translate-point-to-viewport point)
|
||||
asset-id (-> (dnd/get-data event "text/asset-id") uuid/uuid)
|
||||
asset-name (dnd/get-data event "text/asset-name")
|
||||
asset-type (dnd/get-data event "text/asset-type")]
|
||||
(cond
|
||||
(dnd/has-type? event "app/shape")
|
||||
(let [shape (dnd/get-data event "app/shape")
|
||||
|
@ -493,40 +486,43 @@
|
|||
(:id component)
|
||||
(gpt/point final-x final-y))))
|
||||
|
||||
;; Will trigger when the user drags an image from a browser to the viewport
|
||||
(dnd/has-type? event "text/uri-list")
|
||||
(let [data (dnd/get-data event "text/uri-list")
|
||||
name (dnd/get-data event "text/asset-name")
|
||||
lines (str/lines data)
|
||||
urls (filter #(and (not (str/blank? %))
|
||||
(not (str/starts-with? % "#")))
|
||||
lines)]
|
||||
(st/emit!
|
||||
(dw/upload-media-objects
|
||||
(with-meta {:file-id (:id file)
|
||||
:local? true
|
||||
:uris urls
|
||||
:name name}
|
||||
{:on-success #(on-uploaded % viewport-coord)}))))
|
||||
|
||||
(dnd/has-type? event "text/asset-id")
|
||||
(let [id (-> (dnd/get-data event "text/asset-id") uuid/uuid)
|
||||
name (dnd/get-data event "text/asset-name")
|
||||
lines)
|
||||
params {:file-id (:id file)
|
||||
:local? true
|
||||
:object-id id
|
||||
:name name}]
|
||||
:uris urls}]
|
||||
(st/emit! (dw/upload-media-workspace params viewport-coord)))
|
||||
|
||||
;; 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"))
|
||||
(let [path (cfg/resolve-file-media {:id asset-id})
|
||||
params {:file-id (:id file)
|
||||
:uris [path]
|
||||
:name asset-name
|
||||
:mtype asset-type}]
|
||||
(st/emit! (dw/upload-media-workspace params viewport-coord)))
|
||||
|
||||
;; Will trigger when the user drags an image from the assets SVG
|
||||
(dnd/has-type? event "text/asset-id")
|
||||
(let [params {:file-id (:id file)
|
||||
:object-id asset-id
|
||||
:name asset-name}]
|
||||
(st/emit! (dw/clone-media-object
|
||||
(with-meta params
|
||||
{:on-success #(on-uploaded % viewport-coord)}))))
|
||||
{:on-success #(on-image-uploaded % viewport-coord)}))))
|
||||
|
||||
;; Will trigger when the user drags a file from their file explorer into the viewport
|
||||
;; Or the user pastes an image
|
||||
;; Or the user uploads an image using the image tool
|
||||
:else
|
||||
(let [files (dnd/get-files event)
|
||||
params {:file-id (:id file)
|
||||
:local? true
|
||||
:data (seq files)}]
|
||||
(st/emit! (dw/upload-media-objects
|
||||
(with-meta params
|
||||
{:on-success #(on-uploaded % viewport-coord)}))))))))
|
||||
(st/emit! (dw/upload-media-workspace params viewport-coord)))))))
|
||||
|
||||
on-paste
|
||||
(mf/use-callback
|
||||
|
@ -591,7 +587,9 @@
|
|||
:file-id (:id file)}])
|
||||
|
||||
[:svg.viewport
|
||||
{:preserveAspectRatio "xMidYMid meet"
|
||||
{:xmlns "http://www.w3.org/2000/svg"
|
||||
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||
:preserveAspectRatio "xMidYMid meet"
|
||||
:key page-id
|
||||
:width (:width vport 0)
|
||||
:height (:height vport 0)
|
||||
|
|
Loading…
Add table
Reference in a new issue