0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-04-12 23:11:23 -05:00

Merge pull request #1053 from penpot/feat/export-import

Feat/export import
This commit is contained in:
Andrés Moya 2021-06-22 12:02:04 +02:00 committed by GitHub
commit be387ad892
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 503 additions and 238 deletions

View file

@ -17,14 +17,7 @@
{:exclude-files
["data_readers.clj"
"app/util/perf.cljs"
"app/common/exceptions.cljc"
"app/util/import/.*"
"app/worker/export.cljs"
"app/worker/import.cljs"
"app/libs/.*"
"app/main/data/workspace/path/selection.cljs"
"app/main/data/workspace/transforms.cljs"
]}
"app/common/exceptions.cljc"]}
:linters
{:unsorted-required-namespaces

View file

@ -556,3 +556,7 @@
:else
m)))
(defn not-empty?
[coll]
(boolean (seq coll)))

View file

@ -291,6 +291,31 @@
(-> file
(update :parent-stack pop)))
(defn add-interaction
[file action-type event-type from-id destination-id]
(assert (some? (lookup-shape file from-id)) (str "Cannot locate shape with id " from-id))
(assert (some? (lookup-shape file destination-id)) (str "Cannot locate shape with id " destination-id))
(let [interactions (->> (lookup-shape file from-id)
:interactions
(filterv #(or (not= (:action-type %) action-type)
(not= (:event-type %) event-type))))
conj (fnil conj [])
interactions (-> interactions
(conj
{:action-type action-type
:event-type event-type
:destination destination-id}))]
(commit-change
file
{:type :mod-obj
:page-id (:current-page-id file)
:id from-id
:operations
[{:type :set :attr :interactions :val interactions}]})))
(defn generate-changes
[file]
(:changes file))

View file

@ -25,59 +25,59 @@
(deftype File [^:mutable file]
Object
(addPage [self name]
(addPage [_ name]
(set! file (fb/add-page file {:name name}))
(str (:current-page-id file)))
(addPage [self name options]
(addPage [_ name options]
(set! file (fb/add-page file {:name name :options options}))
(str (:current-page-id file)))
(closePage [self]
(closePage [_]
(set! file (fb/close-page file)))
(addArtboard [self data]
(addArtboard [_ data]
(set! file (fb/add-artboard file (parse-data data)))
(str (:last-id file)))
(closeArtboard [self data]
(closeArtboard [_]
(set! file (fb/close-artboard file)))
(addGroup [self data]
(addGroup [_ data]
(set! file (fb/add-group file (parse-data data)))
(str (:last-id file)))
(closeGroup [self]
(closeGroup [_]
(set! file (fb/close-group file)))
(createRect [self data]
(createRect [_ data]
(set! file (fb/create-rect file (parse-data data)))
(str (:last-id file)))
(createCircle [self data]
(createCircle [_ data]
(set! file (fb/create-circle file (parse-data data)))
(str (:last-id file)))
(createPath [self data]
(createPath [_ data]
(set! file (fb/create-path file (parse-data data)))
(str (:last-id file)))
(createText [self data]
(createText [_ data]
(set! file (fb/create-text file (parse-data data)))
(str (:last-id file)))
(createImage [self data]
(createImage [_ data]
(set! file (fb/create-image file (parse-data data)))
(str (:last-id file)))
(createSVG [self data]
(createSVG [_ data]
(set! file (fb/create-svg-raw file (parse-data data)))
(str (:last-id file)))
(closeSVG [self]
(closeSVG [_]
(set! file (fb/close-svg-raw file)))
(asMap [self]
(asMap [_]
(clj->js file)))
(defn create-file-export [^string name]

View file

@ -141,9 +141,9 @@
selected? (contains? selected-points position)]
(streams/drag-stream
(rx/of
(when-not selected? (selection/select-node position shift? "drag"))
(when-not selected? (selection/select-node position shift?))
(drag-selected-points @ms/mouse-position))
(rx/of (selection/select-node position shift? "click")))))))
(rx/of (selection/select-node position shift?)))))))
(defn drag-selected-points
[start-position]

View file

@ -60,7 +60,7 @@
(some? id)
(assoc-in [:workspace-local :edit-path id :selected-points] positions))))))
(defn select-node [position shift? kk]
(defn select-node [position shift?]
(ptk/reify ::select-node
ptk/UpdateEvent
(update [_ state]
@ -79,38 +79,6 @@
(some? id)
(assoc-in [:workspace-local :edit-path id :selected-points] selected-points))))))
(defn deselect-node [position shift?]
(ptk/reify ::deselect-node
ptk/UpdateEvent
(update [_ state]
(let [id (get-in state [:workspace-local :edition])]
(-> state
(update-in [:workspace-local :edit-path id :selected-points] (fnil disj #{}) position))))))
(defn add-to-selection-handler [index type]
(ptk/reify ::add-to-selection-handler
ptk/UpdateEvent
(update [_ state]
state)))
(defn add-to-selection-node [index]
(ptk/reify ::add-to-selection-node
ptk/UpdateEvent
(update [_ state]
state)))
(defn remove-from-selection-handler [index]
(ptk/reify ::remove-from-selection-handler
ptk/UpdateEvent
(update [_ state]
state)))
(defn remove-from-selection-node [index]
(ptk/reify ::remove-from-selection-handler
ptk/UpdateEvent
(update [_ state]
state)))
(defn deselect-all []
(ptk/reify ::deselect-all
ptk/UpdateEvent
@ -140,7 +108,7 @@
(ptk/reify ::handle-selection
ptk/WatchEvent
(watch [_ state stream]
(watch [_ _ stream]
(let [stop? (fn [event] (or (dwc/interrupt? event) (ms/mouse-up? event)))
stoper (->> stream (rx/filter stop?))
from-p @ms/mouse-position]

View file

@ -98,8 +98,8 @@
;; -- RESIZE
(defn start-resize
[handler initial ids shape]
(letfn [(resize [shape initial resizing-shapes layout [point lock? point-snap]]
[handler ids shape]
(letfn [(resize [shape initial layout [point lock? point-snap]]
(let [{:keys [width height]} (:selrect shape)
{:keys [rotation]} shape
rotation (or rotation 0)
@ -181,7 +181,7 @@
(rx/switch-map (fn [[point :as current]]
(->> (snap/closest-snap-point page-id resizing-shapes layout zoom point)
(rx/map #(conj current %)))))
(rx/mapcat (partial resize shape initial-position resizing-shapes layout))
(rx/mapcat (partial resize shape initial-position layout))
(rx/take-until stoper))
(rx/of (apply-modifiers ids)
(finish-transform))))))))
@ -196,7 +196,7 @@
(assoc-in [:workspace-local :transform] :rotate)))
ptk/WatchEvent
(watch [it state stream]
(watch [_ _ stream]
(let [stoper (rx/filter ms/mouse-up? stream)
group (gsh/selection-rect shapes)
group-center (gsh/center-selrect group)
@ -235,7 +235,7 @@
[]
(ptk/reify ::start-move-selected
ptk/WatchEvent
(watch [it state stream]
(watch [_ state stream]
(let [initial (deref ms/mouse-position)
selected (wsh/lookup-selected state {:omit-blocked? true})
stopper (rx/filter ms/mouse-up? stream)]
@ -259,7 +259,7 @@
(defn start-move-duplicate [from-position]
(ptk/reify ::start-move-selected
ptk/WatchEvent
(watch [it state stream]
(watch [_ _ stream]
(->> stream
(rx/filter (ptk/type? ::dws/duplicate-selected))
(rx/first)
@ -268,7 +268,7 @@
(defn calculate-frame-for-move [ids]
(ptk/reify ::calculate-frame-for-move
ptk/WatchEvent
(watch [it state stream]
(watch [it state _]
(let [position @ms/mouse-position
page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
@ -313,7 +313,7 @@
(assoc-in [:workspace-local :transform] :move)))
ptk/WatchEvent
(watch [it state stream]
(watch [_ state stream]
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
selected (wsh/lookup-selected state {:omit-blocked? true})
@ -349,20 +349,6 @@
(calculate-frame-for-move ids)
(finish-transform)))))))))
(defn- get-displacement-with-grid
"Retrieve the correct displacement delta point for the
provided direction speed and distances thresholds."
[shape direction options]
(let [grid-x (:grid-x options 10)
grid-y (:grid-y options 10)
x-mod (mod (:x shape) grid-x)
y-mod (mod (:y shape) grid-y)]
(case direction
:up (gpt/point 0 (- (if (zero? y-mod) grid-y y-mod)))
:down (gpt/point 0 (- grid-y y-mod))
:left (gpt/point (- (if (zero? x-mod) grid-x x-mod)) 0)
:right (gpt/point (- grid-x x-mod) 0))))
(defn- get-displacement
"Retrieve the correct displacement delta point for the
provided direction speed and distances thresholds."
@ -394,7 +380,7 @@
state))
ptk/WatchEvent
(watch [it state stream]
(watch [_ state stream]
(if (= same-event (get-in state [:workspace-local :current-move-selected]))
(let [selected (wsh/lookup-selected state {:omit-blocked? true})
move-events (->> stream
@ -539,7 +525,7 @@
(defn increase-rotation [ids rotation]
(ptk/reify ::increase-rotation
ptk/WatchEvent
(watch [it state stream]
(watch [_ state _]
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
@ -559,7 +545,7 @@
(us/verify (s/coll-of uuid?) ids)
(ptk/reify ::apply-modifiers
ptk/WatchEvent
(watch [it state stream]
(watch [_ state _]
(let [objects (wsh/lookup-page-objects state)
children-ids (->> ids (mapcat #(cp/get-children % objects)))
ids-with-children (d/concat [] children-ids ids)
@ -610,11 +596,6 @@
(update [_ state]
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
update-children
(fn [objects ids modifiers]
(reduce #(assoc-in %1 [%2 :modifiers] modifiers) objects ids))
;; For each shape updates the modifiers given as arguments
update-shape
@ -629,7 +610,7 @@
#(reduce update-shape % ids))))
ptk/WatchEvent
(watch [it state stream]
(watch [_ state _]
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
ids (d/concat [] ids (mapcat #(cp/get-children % objects) ids))]
@ -638,7 +619,7 @@
(defn flip-horizontal-selected []
(ptk/reify ::flip-horizontal-selected
ptk/WatchEvent
(watch [it state stream]
(watch [_ state _]
(let [objects (wsh/lookup-page-objects state)
selected (wsh/lookup-selected state {:omit-blocked? true})
shapes (map #(get objects %) selected)
@ -654,7 +635,7 @@
(defn flip-vertical-selected []
(ptk/reify ::flip-vertical-selected
ptk/WatchEvent
(watch [it state stream]
(watch [_ state _]
(let [objects (wsh/lookup-page-objects state)
selected (wsh/lookup-selected state {:omit-blocked? true})
shapes (map #(get objects %) selected)
@ -687,6 +668,6 @@
[]
(ptk/reify ::selected-to-path
ptk/WatchEvent
(watch [_ state stream]
(watch [_ state _]
(let [ids (wsh/lookup-selected state {:omit-blocked? true})]
(rx/of (dch/update-shapes ids ups/convert-to-path))))))

View file

@ -25,6 +25,7 @@
[app.main.ui.shapes.shape :refer [shape-container]]
[app.main.ui.shapes.svg-raw :as svg-raw]
[app.main.ui.shapes.text :as text]
[app.util.object :as obj]
[app.util.timers :as ts]
[cuerdas.core :as str]
[rumext.alpha :as mf]))
@ -73,10 +74,11 @@
(mf/fnc group-wrapper
[{:keys [shape frame] :as props}]
(let [childs (mapv #(get objects %) (:shapes shape))]
[:& group-shape {:frame frame
:shape shape
:is-child-selected? true
:childs childs}]))))
[:& shape-container {:shape shape}
[:& group-shape {:frame frame
:shape shape
:is-child-selected? true
:childs childs}]]))))
(defn svg-raw-wrapper-factory
[objects]
@ -207,7 +209,8 @@
:height height
:version "1.1"
:xmlnsXlink "http://www.w3.org/1999/xlink"
:xmlns "http://www.w3.org/2000/svg"}
:xmlns "http://www.w3.org/2000/svg"
:xmlns:penpot "https://penpot.app/xmlns"}
[:& wrapper {:shape frame :view-box vbox}]]))
(mf/defc component-svg
@ -238,5 +241,58 @@
:height height
:version "1.1"
:xmlnsXlink "http://www.w3.org/1999/xlink"
:xmlns "http://www.w3.org/2000/svg"}
:xmlns "http://www.w3.org/2000/svg"
:xmlns:penpot "https://penpot.app/xmlns"}
[:& wrapper {:shape group :view-box vbox}]]))
(mf/defc component-symbol
[{:keys [id data] :as props}]
(let [{:keys [name objects]} data
root (get objects id)
{:keys [width height]} (:selrect root)
vbox (str "0 0 " width " " height)
modifier (-> (gpt/point (:x root) (:y root))
(gpt/negate)
(gmt/translate-matrix))
modifier-ids (concat [id] (cp/get-children id objects))
update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier)
objects (reduce update-fn objects modifier-ids)
root (assoc-in root [:modifiers :displacement] modifier)
group-wrapper
(mf/use-memo
(mf/deps objects)
#(group-wrapper-factory objects))]
[:symbol {:id (str id)
:viewBox vbox}
[:title name]
[:& group-wrapper {:shape root :view-box vbox}]]))
(mf/defc components-sprite-svg
{::mf/wrap-props false}
[props]
(let [data (obj/get props "data")
children (obj/get props "children")
embed? (obj/get props "embed?")]
[:& (mf/provider embed/context) {:value embed?}
[:svg {:version "1.1"
:xmlns "http://www.w3.org/2000/svg"
:xmlnsXlink "http://www.w3.org/1999/xlink"
:xmlns:penpot "https://penpot.app/xmlns"
:style {:width "100vw"
:height "100vh"
:display (when-not (some? children) "none")}}
[:defs
(for [[component-id component-data] (:components data)]
[:& component-symbol {:id component-id
:key (str component-id)
:data component-data}])]
children]]))

View file

@ -66,5 +66,16 @@
(let [elem (mf/element exports/page-svg #js {:data data :embed? true})]
(rds/renderToStaticMarkup elem)))))))
(defn render-components
[data]
(rx/concat
(->> (rx/merge
(populate-images-cache data)
(populate-fonts-cache data))
(rx/ignore))
(->> (rx/of data)
(rx/map
(fn [data]
(let [elem (mf/element exports/components-sprite-svg #js {:data data :embed? true})]
(rds/renderToStaticMarkup elem)))))))

View file

@ -82,6 +82,7 @@
;; Used for export
["/render-object/:file-id/:page-id/:object-id" :render-object]
["/render-sprite/:file-id" :render-sprite]
["/dashboard/team/:team-id"
["/members" :dashboard-team-members]
@ -172,6 +173,14 @@
:page-id page-id
:object-id object-id}]))
:render-sprite
(do
(let [file-id (uuid (get-in route [:path-params :file-id]))
component-id (get-in route [:query-params :component-id])
component-id (when (some? component-id) (uuid component-id))]
[:& render/render-sprite {:file-id file-id
:component-id component-id}]))
:workspace
(let [project-id (some-> params :path :project-id uuid)
file-id (some-> params :path :file-id uuid)

View file

@ -123,3 +123,30 @@
[:& object-svg {:objects @objects
:object-id object-id
:zoom 1}])))
(mf/defc render-sprite
[{:keys [file-id component-id] :as props}]
(let [file (mf/use-state nil)]
(mf/use-effect
(mf/deps file-id)
(fn []
(->> (repo/query! :file {:id file-id})
(rx/subs
(fn [result]
(reset! file result))))
(constantly nil)))
(when @file
[:*
[:& exports/components-sprite-svg {:data (:data @file) :embed true}
(when (some? component-id)
[:use {:x 0 :y 0
:xlinkHref (str "#" component-id)}])]
(when-not (some? component-id)
[:ul
(for [[id data] (get-in @file [:data :components])]
(let [url (str "#/render-sprite/" (:id @file) "?component-id=" id)]
[:li [:a {:href url} (:name data)]]))])])))

View file

@ -91,20 +91,22 @@
[(str "penpot:" (d/name k)) v])]
(into {} (map prefix-entry) m)))
(mf/defc export-grid-data
[{:keys [grids]}]
[:> "penpot:grids" #js {}
(for [{:keys [type display params]} grids]
(let [props (->> (d/without-keys params [:color])
(prefix-keys)
(clj->js))]
[:> "penpot:grid"
(-> props
(obj/set! "penpot:color" (get-in params [:color :color]))
(obj/set! "penpot:opacity" (get-in params [:color :opacity]))
(obj/set! "penpot:type" (d/name type))
(cond-> (some? display)
(obj/set! "penpot:display" (str display))))]))])
(when-not (empty? grids)
[:> "penpot:grids" #js {}
(for [{:keys [type display params]} grids]
(let [props (->> (d/without-keys params [:color])
(prefix-keys)
(clj->js))]
[:> "penpot:grid"
(-> props
(obj/set! "penpot:color" (get-in params [:color :color]))
(obj/set! "penpot:opacity" (get-in params [:color :opacity]))
(obj/set! "penpot:type" (d/name type))
(cond-> (some? display)
(obj/set! "penpot:display" (str display))))]))]))
(mf/defc export-page
[{:keys [options]}]
@ -117,62 +119,84 @@
[:> "penpot:page" #js {}
[:& export-grid-data {:grids grids}]]))))
(mf/defc export-shadow-data
[{:keys [shadow]}]
(for [{:keys [style hidden color offset-x offset-y blur spread]} shadow]
[:> "penpot:shadow"
#js {:penpot:shadow-type (d/name style)
:penpot:hidden (str hidden)
:penpot:color (str (:color color))
:penpot:opacity (str (:opacity color))
:penpot:offset-x (str offset-x)
:penpot:offset-y (str offset-y)
:penpot:blur (str blur)
:penpot:spread (str spread)}]))
(mf/defc export-blur-data [{:keys [blur]}]
(when (some? blur)
(let [{:keys [type hidden value]} blur]
[:> "penpot:blur"
#js {:penpot:blur-type (d/name type)
:penpot:hidden (str hidden)
:penpot:value (str value)}])))
(mf/defc export-exports-data [{:keys [exports]}]
(for [{:keys [scale suffix type]} exports]
[:> "penpot:export"
#js {:penpot:type (d/name type)
:penpot:suffix suffix
:penpot:scale (str scale)}]))
(mf/defc export-svg-data [shape]
[:*
(when (contains? shape :svg-attrs)
(let [svg-transform (get shape :svg-transform)
svg-attrs (->> shape :svg-attrs keys (mapv d/name) (str/join ",") )
svg-defs (->> shape :svg-defs keys (mapv d/name) (str/join ","))]
[:> "penpot:svg-import"
#js {:penpot:svg-attrs (when-not (empty? svg-attrs) svg-attrs)
:penpot:svg-defs (when-not (empty? svg-defs) svg-defs)
:penpot:svg-transform (when svg-transform (str svg-transform))
:penpot:svg-viewbox-x (get-in shape [:svg-viewbox :x])
:penpot:svg-viewbox-y (get-in shape [:svg-viewbox :y])
:penpot:svg-viewbox-width (get-in shape [:svg-viewbox :width])
:penpot:svg-viewbox-height (get-in shape [:svg-viewbox :height])}
(for [[def-id def-xml] (:svg-defs shape)]
[:> "penpot:svg-def" #js {:def-id def-id}
[:& render-xml {:xml def-xml}]])]))
(when (= (:type shape) :svg-raw)
(let [props
(-> (obj/new)
(obj/set! "penpot:x" (:x shape))
(obj/set! "penpot:y" (:y shape))
(obj/set! "penpot:width" (:width shape))
(obj/set! "penpot:height" (:height shape))
(obj/set! "penpot:tag" (-> (get-in shape [:content :tag]) d/name))
(obj/merge! (-> (get-in shape [:content :attrs])
(clj->js))))]
[:> "penpot:svg-content" props
(for [leaf (->> shape :content :content (filter string?))]
[:> "penpot:svg-child" {} leaf])]))])
(mf/defc export-interactions-data
[{:keys [interactions]}]
(when-not (empty? interactions)
[:> "penpot:interactions" #js {}
(for [{:keys [action-type destination event-type]} interactions]
[:> "penpot:interaction"
#js {:penpot:action-type (d/name action-type)
:penpot:destination (str destination)
:penpot:event-type (d/name event-type)}])]))
(mf/defc export-data
[{:keys [shape]}]
(let [props (-> (obj/new)
(add-data shape))]
(let [props (-> (obj/new) (add-data shape))]
[:> "penpot:shape" props
(for [{:keys [style hidden color offset-x offset-y blur spread]} (:shadow shape)]
[:> "penpot:shadow" #js {:penpot:shadow-type (d/name style)
:penpot:hidden (str hidden)
:penpot:color (str (:color color))
:penpot:opacity (str (:opacity color))
:penpot:offset-x (str offset-x)
:penpot:offset-y (str offset-y)
:penpot:blur (str blur)
:penpot:spread (str spread)}])
(when (some? (:blur shape))
(let [{:keys [type hidden value]} (:blur shape)]
[:> "penpot:blur" #js {:penpot:blur-type (d/name type)
:penpot:hidden (str hidden)
:penpot:value (str value)}]))
(for [{:keys [scale suffix type]} (:exports shape)]
[:> "penpot:export" #js {:penpot:type (d/name type)
:penpot:suffix suffix
:penpot:scale (str scale)}])
(when (contains? shape :svg-attrs)
(let [svg-transform (get shape :svg-transform)
svg-attrs (->> shape :svg-attrs keys (mapv d/name) (str/join ",") )
svg-defs (->> shape :svg-defs keys (mapv d/name) (str/join ","))]
[:> "penpot:svg-import" #js {:penpot:svg-attrs (when-not (empty? svg-attrs) svg-attrs)
:penpot:svg-defs (when-not (empty? svg-defs) svg-defs)
:penpot:svg-transform (when svg-transform (str svg-transform))
:penpot:svg-viewbox-x (get-in shape [:svg-viewbox :x])
:penpot:svg-viewbox-y (get-in shape [:svg-viewbox :y])
:penpot:svg-viewbox-width (get-in shape [:svg-viewbox :width])
:penpot:svg-viewbox-height (get-in shape [:svg-viewbox :height])}
(for [[def-id def-xml] (:svg-defs shape)]
[:> "penpot:svg-def" #js {:def-id def-id}
[:& render-xml {:xml def-xml}]])]))
(when (= (:type shape) :svg-raw)
(let [props (-> (obj/new)
(obj/set! "penpot:x" (:x shape))
(obj/set! "penpot:y" (:y shape))
(obj/set! "penpot:width" (:width shape))
(obj/set! "penpot:height" (:height shape))
(obj/set! "penpot:tag" (-> (get-in shape [:content :tag]) d/name))
(obj/merge! (-> (get-in shape [:content :attrs])
(clj->js))))]
[:> "penpot:svg-content" props
(for [leaf (->> shape :content :content (filter string?))]
[:> "penpot:svg-child" {} leaf])]))
(when (and (= (:type shape) :frame)
(seq (:grids shape)))
[:& export-grid-data {:grids (:grids shape)}])]))
[:& export-shadow-data shape]
[:& export-blur-data shape]
[:& export-exports-data shape]
[:& export-svg-data shape]
[:& export-interactions-data shape]
[:& export-grid-data shape]]))

View file

@ -286,9 +286,9 @@
shape-center (geom/center-shape shape)
on-resize (fn [current-position initial-position event]
on-resize (fn [current-position _initial-position event]
(dom/stop-propagation event)
(st/emit! (dw/start-resize current-position initial-position selected shape)))
(st/emit! (dw/start-resize current-position selected shape)))
on-rotate #(do (dom/stop-propagation %)
(st/emit! (dw/start-rotate shapes)))]
@ -311,9 +311,9 @@
shape (geom/transform-shape shape {:round-coords? false})
shape' (if (debug? :simple-selection) (geom/setup {:type :rect} (geom/selection-rect [shape])) shape)
on-resize (fn [current-position initial-position event]
on-resize (fn [current-position _initial-position event]
(dom/stop-propagation event)
(st/emit! (dw/start-resize current-position initial-position #{shape-id} shape')))
(st/emit! (dw/start-resize current-position #{shape-id} shape')))
on-rotate
#(do (dom/stop-propagation %)

View file

@ -15,6 +15,12 @@
[app.util.path.parser :as upp]
[cuerdas.core :as str]))
(def url-regex
#"url\(#([^\)]*)\)")
(def uuid-regex
#"\w{8}-\w{4}-\w{4}-\w{4}-\w{12}")
(defn valid?
[root]
(contains? (:attrs root) :xmlns:penpot))
@ -41,7 +47,7 @@
(defn find-all-nodes
[node tag]
(when (some? node)
(->> node :content (filterv #(= (:tag %) :defs)))))
(->> node :content (filterv #(= (:tag %) tag)))))
(defn get-data
([node]
@ -65,6 +71,11 @@
(or (close? node)
(some? (get-data node))))
(defn get-id
[node]
(when-let [id (re-find uuid-regex (get-in node [:attrs :id]))]
(uuid/uuid id)))
(defn str->bool
[val]
(when (some? val) (= val "true")))
@ -115,7 +126,7 @@
(defn without-penpot-prefix
[m]
(let [no-penpot-prefix?
(fn [[k v]]
(fn [[k _]]
(not (str/starts-with? (d/name k) "penpot:")))]
(into {} (filter no-penpot-prefix?) m)))
@ -180,11 +191,11 @@
[props svg-data]
(let [values (->> (select-keys svg-data [:cx :cy :rx :ry])
(d/mapm (fn [_ val] (d/parse-double val))))]
{:x (- (:cx values) (:rx values))
:y (- (:cy values) (:ry values))
:width (* (:rx values) 2)
:height (* (:ry values) 2)}))
(-> props
(assoc :x (- (:cx values) (:rx values))
:y (- (:cy values) (:ry values))
:width (* (:rx values) 2)
:height (* (:ry values) 2)))))
(defn parse-path
[props center svg-data]
@ -193,9 +204,6 @@
(assoc :content content)
(assoc :center center))))
(def url-regex #"url\(#([^\)]*)\)")
(defn parse-stops
[gradient-node]
(->> gradient-node
@ -451,7 +459,7 @@
[props node]
(let [shadows (extract-from-data node :penpot:shadow parse-shadow)]
(cond-> props
(not (empty? shadows))
(d/not-empty? shadows)
(assoc :shadow shadows))))
(defn add-blur
@ -465,7 +473,7 @@
[props node]
(let [exports (extract-from-data node :penpot:export parse-export)]
(cond-> props
(not (empty? exports))
(d/not-empty? exports)
(assoc :exports exports))))
(defn add-layer-options
@ -483,7 +491,7 @@
(defn remove-prefix [s]
(cond-> s
(string? s)
(str/replace #"\w{8}-\w{4}-\w{4}-\w{4}-\w{12}-" "")))
(str/replace (re-pattern (str uuid-regex "-")) "")))
(defn get-svg-attrs
[svg-data svg-attrs]
@ -499,7 +507,7 @@
(reduce assoc-key {}))))
(defn get-svg-defs
[node svg-defs]
[node]
(let [svg-import (get-data node :penpot:svg-import)]
(->> svg-import
@ -537,7 +545,7 @@
(some? svg-defs)
(assoc :svg-defs (get-svg-defs node svg-defs))))
(assoc :svg-defs (get-svg-defs node))))
props)))
@ -563,7 +571,7 @@
(defn add-frame-data [props node]
(let [grids (parse-grids node)]
(cond-> props
(not (empty? grids))
(d/not-empty? grids)
(assoc :grids grids))))
(defn has-image?
@ -633,10 +641,19 @@
background (:background style)
grids (->> (parse-grids node)
(group-by :type)
(d/mapm (fn [k v] (-> v first :params))))]
(d/mapm (fn [_ v] (-> v first :params))))]
(cond-> {}
(some? background)
(assoc-in [:options :background] background)
(not (empty? grids))
(d/not-empty? grids)
(assoc-in [:options :saved-grids] grids))))
(defn parse-interactions
[node]
(let [interactions-node (get-data node :penpot:interactions)]
(->> (find-all-nodes interactions-node :penpot:interaction)
(mapv (fn [node]
{:destination (get-meta node :destination uuid/uuid)
:action-type (get-meta node :action-type keyword)
:event-type (get-meta node :event-type keyword)})))))

View file

@ -6,36 +6,57 @@
(ns app.worker.export
(:require
[app.common.data :as d]
[app.config :as cfg]
[app.main.render :as r]
[app.main.repo :as rp]
[app.util.dom :as dom]
[app.util.zip :as uz]
[app.util.http :as http]
[app.util.json :as json]
[app.util.zip :as uz]
[app.worker.impl :as impl]
[beicon.core :as rx]))
[beicon.core :as rx]
[cuerdas.core :as str]))
(defn create-manifest
"Creates a manifest entry for the given files"
[team-id files]
[team-id file-id files]
(letfn [(format-page [manifest page]
(-> manifest
(assoc (str (:id page))
{:name (:name page)})))
(format-file [manifest file]
(let [name (:name file)
pages (->> (get-in file [:data :pages]) (mapv str))
index (->> (get-in file [:data :pages-index]) (vals)
(reduce format-page {}))]
(let [name (:name file)
is-shared (:is-shared file)
pages (->> (get-in file [:data :pages])
(mapv str))
index (->> (get-in file [:data :pages-index])
(vals)
(reduce format-page {}))]
(-> manifest
(assoc (str (:id file))
{:name name
:pages pages
:pagesIndex index}))))]
{:name name
:shared is-shared
:pages pages
:pagesIndex index
:hasComponents (d/not-empty? (get-in file [:data :components]))
:hasImages (d/not-empty? (get-in file [:data :media]))
:hasColors (d/not-empty? (get-in file [:data :colors]))
:hasTypographies (d/not-empty? (get-in file [:data :typographies]))}))))]
(let [manifest {:teamId (str team-id)
:fileId (str file-id)
:files (->> (vals files) (reduce format-file {}))}]
(json/encode manifest))))
(defn process-pages [file]
(let [pages (get-in file [:data :pages])
pages-index (get-in file [:data :pages-index])]
(->> pages
(map #(hash-map
:file-id (:id file)
:data (get pages-index %))))))
(defn get-page-data
[{file-id :file-id {:keys [id name] :as data} :data}]
(->> (r/render-page data)
@ -45,30 +66,96 @@
:file-id file-id
:markup markup}))))
(defn process-pages [file]
(let [pages (get-in file [:data :pages])
pages-index (get-in file [:data :pages-index])]
(->> pages
(map #(hash-map
:file-id (:id file)
:data (get pages-index %))))))
(defn collect-page
[{:keys [id file-id markup] :as page}]
[(str file-id "/" id ".svg") markup])
(defn collect-entries [result data keys]
(-> result
(assoc (str (:id data))
(->> (select-keys data keys)
(d/deep-mapm
(fn [[k v]]
[(-> k str/camel) v]))))))
(def ^:const color-keys
[:name :color :opacity :gradient])
(def ^:const typography-keys
[:name :font-family :font-id :font-size :font-style :font-variant-id :font-weight
:letter-spacing :line-height :text-transform])
(def ^:const media-keys
[:name :mtype :width :height])
(defn collect-color
[result color]
(collect-entries result color color-keys))
(defn collect-typography
[result typography]
(collect-entries result typography typography-keys))
(defn collect-media
[result media]
(collect-entries result media media-keys))
(defn parse-library-color
[[file-id colors]]
(let [markup
(->> (vals colors)
(reduce collect-color {})
(json/encode))]
[(str file-id "/colors.json") markup]))
(defn parse-library-typographies
[[file-id typographies]]
(let [markup
(->> (vals typographies)
(reduce collect-typography {})
(json/encode))]
[(str file-id "/typographies.json") markup]))
(defn parse-library-media
[[file-id media]]
(rx/merge
(let [markup
(->> (vals media)
(reduce collect-media {})
(json/encode))]
(rx/of (vector (str file-id "/images.json") markup)))
(->> (rx/from (vals media))
(rx/map #(assoc % :file-id file-id))
(rx/flat-map
(fn [media]
(let [file-path (str file-id "/images/" (:id media) "." (dom/mtype->extension (:mtype media)))]
(->> (http/send!
{:uri (cfg/resolve-file-media media)
:response-type :blob
:method :get})
(rx/map :body)
(rx/map #(vector file-path %)))))))))
(defn parse-library-components
[file]
(->> (r/render-components (:data file))
(rx/map #(vector (str (:id file) "/components.svg") %))))
(defn export-file
[team-id file-id]
(let [files-stream
(->> (rp/query :file {:id file-id})
(->> (rx/merge (rp/query :file {:id file-id})
(->> (rp/query :file-libraries {:file-id file-id})
(rx/flat-map identity)
(rx/map #(assoc % :is-shared true))))
(rx/reduce #(assoc %1 (:id %2) %2) {})
(rx/share))
manifest-stream
(->> files-stream
(rx/map #(create-manifest team-id %))
(rx/map #(create-manifest team-id file-id %))
(rx/map #(vector "manifest.json" %)))
render-stream
@ -79,6 +166,33 @@
(rx/flat-map get-page-data)
(rx/share))
colors-stream
(->> files-stream
(rx/flat-map vals)
(rx/map #(vector (:id %) (get-in % [:data :colors])))
(rx/filter #(d/not-empty? (second %)))
(rx/map parse-library-color))
typographies-stream
(->> files-stream
(rx/flat-map vals)
(rx/map #(vector (:id %) (get-in % [:data :typographies])))
(rx/filter #(d/not-empty? (second %)))
(rx/map parse-library-typographies))
media-stream
(->> files-stream
(rx/flat-map vals)
(rx/map #(vector (:id %) (get-in % [:data :media])))
(rx/filter #(d/not-empty? (second %)))
(rx/flat-map parse-library-media))
components-stream
(->> files-stream
(rx/flat-map vals)
(rx/filter #(d/not-empty? (get-in % [:data :components])))
(rx/flat-map parse-library-components))
pages-stream
(->> render-stream
(rx/map collect-page))]
@ -90,7 +204,13 @@
:file file-id
:data (str "Render " (:file-name %) " - " (:name %)))))
(->> (rx/merge manifest-stream pages-stream)
(->> (rx/merge
manifest-stream
pages-stream
components-stream
media-stream
colors-stream
typographies-stream)
(rx/reduce conj [])
(rx/with-latest-from files-stream)
(rx/flat-map (fn [[data files]]
@ -98,7 +218,7 @@
(rx/map #(vector (get files file-id) %)))))))))
(defmethod impl/handler :export-file
[{:keys [team-id project-id files] :as message}]
[{:keys [team-id files] :as message}]
(->> (rx/from files)
(rx/mapcat #(export-file team-id %))

View file

@ -17,7 +17,6 @@
[app.util.zip :as uz]
[app.worker.impl :as impl]
[beicon.core :as rx]
[cuerdas.core :as str]
[tubax.core :as tubax]))
;; Upload changes batches size
@ -25,12 +24,13 @@
(defn create-file
"Create a new file on the back-end"
[project-id name]
[project-id file-desc]
(let [file-id (uuid/next)]
(rp/mutation
:create-temp-file
{:id file-id
:name name
:name (:name file-desc)
:is-shared (:shared file-desc)
:project-id project-id
:data (-> cp/empty-file-data (assoc :id file-id))})))
@ -78,36 +78,65 @@
(defn add-shape-file
[file node]
(let [type (cip/get-type node)
close? (cip/close? node)
data (cip/parse-data type node)]
(let [type (cip/get-type node)
close? (cip/close? node)]
(if close?
(case type
:frame
(fb/close-artboard file)
:frame (fb/close-artboard file)
:group (fb/close-group file)
:svg-raw (fb/close-svg-raw file)
#_default file)
:group
(fb/close-group file)
(let [data (cip/parse-data type node)
old-id (cip/get-id node)
interactions (cip/parse-interactions node)
:svg-raw
(fb/close-svg-raw file)
file (case type
:frame (fb/add-artboard file data)
:group (fb/add-group file data)
:rect (fb/create-rect file data)
:circle (fb/create-circle file data)
:path (fb/create-path file data)
:text (fb/create-text file data)
:image (fb/create-image file data)
:svg-raw (fb/create-svg-raw file data)
#_default file)]
;; default
file)
(assert (some? old-id) "ID not found")
(case type
:frame (fb/add-artboard file data)
:group (fb/add-group file data)
:rect (fb/create-rect file data)
:circle (fb/create-circle file data)
:path (fb/create-path file data)
:text (fb/create-text file data)
:image (fb/create-image file data)
:svg-raw (fb/create-svg-raw file data)
;; We store this data for post-processing after every shape has been
;; added
(cond-> file
(some? (:last-id file))
(assoc-in [:id-mapping old-id] (:last-id file))
;; default
file))))
(d/not-empty? interactions)
(assoc-in [:interactions old-id] interactions))))))
(defn post-process-file
[file]
(letfn [(add-interaction
[id file {:keys [action-type event-type destination] :as interaction}]
(fb/add-interaction file action-type event-type id destination))
(add-interactions
[file [old-id interactions]]
(let [id (get-in file [:id-mapping old-id])]
(->> interactions
(mapv (fn [interaction]
(let [id (get-in file [:id-mapping (:destination interaction)])]
(assoc interaction :destination id))))
(reduce
(partial add-interaction id) file))))
(process-interactions
[file]
(reduce add-interactions file (:interactions file)))]
(-> file
(process-interactions)
(dissoc :id-mapping :interactions))))
(defn merge-reduce [f seed ob]
(->> (rx/concat
@ -145,6 +174,7 @@
(rx/filter cip/shape?)
(rx/mapcat (partial resolve-images file-id))
(rx/reduce add-shape-file (fb/add-page file page-data))
(rx/map post-process-file)
(rx/map fb/close-page)))
(rx/empty)))
@ -173,7 +203,7 @@
(rx/flat-map (comp :files json/decode :content))
(rx/flat-map
(fn [[file-id file-desc]]
(->> (create-file project-id (:name file-desc))
(->> (create-file project-id file-desc)
(rx/flat-map #(process-file % file-id file-desc zip-file)))))))
(defmethod impl/handler :import-file