mirror of
https://github.com/penpot/penpot.git
synced 2025-02-10 00:58:26 -05:00
✨ Add support to export/import guides
This commit is contained in:
parent
d356a3fa56
commit
f303d7b33e
10 changed files with 165 additions and 109 deletions
|
@ -75,7 +75,7 @@
|
|||
:opt-un [:guides/frame-id]))
|
||||
|
||||
(s/def ::guides
|
||||
(s/map-of uuid? ::shape))
|
||||
(s/map-of uuid? ::guide))
|
||||
|
||||
;; --- Options
|
||||
|
||||
|
|
|
@ -84,7 +84,8 @@
|
|||
:snap-grid
|
||||
:scale-text
|
||||
:dynamic-alignment
|
||||
:display-artboard-names})
|
||||
:display-artboard-names
|
||||
:snap-guides})
|
||||
|
||||
(s/def ::layout-flags (s/coll-of ::layout-flag))
|
||||
|
||||
|
@ -96,7 +97,8 @@
|
|||
:display-grid
|
||||
:snap-grid
|
||||
:dynamic-alignment
|
||||
:display-artboard-names})
|
||||
:display-artboard-names
|
||||
:snap-guides})
|
||||
|
||||
(def layout-presets
|
||||
{:assets
|
||||
|
|
|
@ -204,7 +204,9 @@
|
|||
:height "100%"
|
||||
:background background-color}}
|
||||
|
||||
[:& export/export-page {:options (:options data)}]
|
||||
(when include-metadata?
|
||||
[:& export/export-page {:options (:options data)}])
|
||||
|
||||
[:& ff/fontfaces-style {:shapes root-children}]
|
||||
(for [item shapes]
|
||||
(let [frame? (= (:type item) :frame)]
|
||||
|
|
|
@ -24,12 +24,30 @@
|
|||
(def ^:const snap-distance-accuracy 10)
|
||||
|
||||
(defn- remove-from-snap-points
|
||||
[remove-id?]
|
||||
[remove-snap?]
|
||||
(fn [query-result]
|
||||
(->> query-result
|
||||
(map (fn [[value data]] [value (remove (comp remove-id? :id) data)]))
|
||||
(map (fn [[value data]] [value (remove remove-snap? data)]))
|
||||
(filter (fn [[_ data]] (seq data))))))
|
||||
|
||||
(defn make-remove-snap
|
||||
"Creates a filter for the snap data. Used to disable certain layouts"
|
||||
[layout filter-shapes]
|
||||
|
||||
(fn [{:keys [type id]}]
|
||||
(cond
|
||||
(= type :layout)
|
||||
(or (not (contains? layout :display-grid))
|
||||
(not (contains? layout :snap-grid)))
|
||||
|
||||
(= type :guide)
|
||||
(or (not (contains? layout :rules))
|
||||
(not (contains? layout :snap-guides)))
|
||||
|
||||
:else
|
||||
(or (contains? filter-shapes id)
|
||||
(not (contains? layout :dynamic-alignment))))))
|
||||
|
||||
(defn- flatten-to-points
|
||||
[query-result]
|
||||
(mapcat (fn [[_ data]] (map :pt data)) query-result))
|
||||
|
@ -57,7 +75,7 @@
|
|||
;; Otherwise the root frame is the common
|
||||
:else zero)))
|
||||
|
||||
(defn get-snap-points [page-id frame-id filter-shapes point coord]
|
||||
(defn get-snap-points [page-id frame-id remove-snap? point coord]
|
||||
(let [value (get point coord)]
|
||||
(->> (uw/ask! {:cmd :snaps/range-query
|
||||
:page-id page-id
|
||||
|
@ -65,11 +83,11 @@
|
|||
:axis coord
|
||||
:ranges [[(- value 0.5) (+ value 0.5)]]})
|
||||
(rx/first)
|
||||
(rx/map (remove-from-snap-points filter-shapes))
|
||||
(rx/map (remove-from-snap-points remove-snap?))
|
||||
(rx/map flatten-to-points))))
|
||||
|
||||
(defn- search-snap
|
||||
[page-id frame-id points coord filter-shapes zoom]
|
||||
[page-id frame-id points coord remove-snap? zoom]
|
||||
(let [snap-accuracy (/ snap-accuracy zoom)
|
||||
ranges (->> points
|
||||
(map coord)
|
||||
|
@ -81,7 +99,7 @@
|
|||
:axis coord
|
||||
:ranges ranges})
|
||||
(rx/first)
|
||||
(rx/map (remove-from-snap-points filter-shapes))
|
||||
(rx/map (remove-from-snap-points remove-snap?))
|
||||
(rx/map (get-min-distance-snap points coord)))))
|
||||
|
||||
(defn snap->vector [[[from-x to-x] [from-y to-y]]]
|
||||
|
@ -91,13 +109,12 @@
|
|||
(gpt/to-vec from to))))
|
||||
|
||||
(defn- closest-snap
|
||||
[page-id frame-id points filter-shapes zoom]
|
||||
(let [snap-x (search-snap page-id frame-id points :x filter-shapes zoom)
|
||||
snap-y (search-snap page-id frame-id points :y filter-shapes zoom)]
|
||||
[page-id frame-id points remove-snap? zoom]
|
||||
(let [snap-x (search-snap page-id frame-id points :x remove-snap? zoom)
|
||||
snap-y (search-snap page-id frame-id points :y remove-snap? zoom)]
|
||||
(->> (rx/combine-latest snap-x snap-y)
|
||||
(rx/map snap->vector))))
|
||||
|
||||
|
||||
(defn sr-distance [coord sr1 sr2]
|
||||
(let [c1 (if (= coord :x) :x1 :y1)
|
||||
c2 (if (= coord :x) :x2 :y2)
|
||||
|
@ -209,12 +226,8 @@
|
|||
[page-id shapes layout zoom point]
|
||||
(let [frame-id (snap-frame-id shapes)
|
||||
filter-shapes (into #{} (map :id shapes))
|
||||
filter-shapes (fn [id] (if (= id :layout)
|
||||
(or (not (contains? layout :display-grid))
|
||||
(not (contains? layout :snap-grid)))
|
||||
(or (filter-shapes id)
|
||||
(not (contains? layout :dynamic-alignment)))))]
|
||||
(->> (closest-snap page-id frame-id [point] filter-shapes zoom)
|
||||
remove-snap? (make-remove-snap layout filter-shapes)]
|
||||
(->> (closest-snap page-id frame-id [point] remove-snap? zoom)
|
||||
(rx/map #(or % (gpt/point 0 0)))
|
||||
(rx/map #(gpt/add point %)))))
|
||||
|
||||
|
@ -222,11 +235,8 @@
|
|||
[page-id shapes objects layout zoom movev]
|
||||
(let [frame-id (snap-frame-id shapes)
|
||||
filter-shapes (into #{} (map :id shapes))
|
||||
filter-shapes (fn [id] (if (= id :layout)
|
||||
(or (not (contains? layout :display-grid))
|
||||
(not (contains? layout :snap-grid)))
|
||||
(or (filter-shapes id)
|
||||
(not (contains? layout :dynamic-alignment)))))
|
||||
remove-snap? (make-remove-snap layout filter-shapes)
|
||||
|
||||
shape (if (> (count shapes) 1)
|
||||
(->> shapes (map gsh/transform-shape) gsh/selection-rect (gsh/setup {:type :rect}))
|
||||
(->> shapes (first)))
|
||||
|
@ -236,7 +246,7 @@
|
|||
;; Move the points in the translation vector
|
||||
(map #(gpt/add % movev)))]
|
||||
|
||||
(->> (rx/merge (closest-snap page-id frame-id shapes-points filter-shapes zoom)
|
||||
(->> (rx/merge (closest-snap page-id frame-id shapes-points remove-snap? zoom)
|
||||
(when (contains? layout :dynamic-alignment)
|
||||
(closest-distance-snap page-id shapes objects zoom movev)))
|
||||
(rx/reduce gpt/min)
|
||||
|
|
|
@ -155,20 +155,30 @@
|
|||
:name name
|
||||
:starting-frame starting-frame}])])
|
||||
|
||||
(mf/defc export-guides
|
||||
[{:keys [guides]}]
|
||||
[:> "penpot:guides" #js {}
|
||||
(for [{:keys [id position frame-id axis]} (vals guides)]
|
||||
[:> "penpot:guide" #js {:position position
|
||||
:frame-id frame-id
|
||||
:axis (d/name axis)}])])
|
||||
|
||||
(mf/defc export-page
|
||||
[{:keys [options]}]
|
||||
(let [saved-grids (get options :saved-grids)
|
||||
flows (get options :flows)]
|
||||
(when (or (seq saved-grids) (seq flows))
|
||||
(let [parse-grid
|
||||
(fn [[type params]]
|
||||
{:type type :params params})
|
||||
flows (get options :flows)
|
||||
guides (get options :guides)]
|
||||
[:> "penpot:page" #js {}
|
||||
(when (d/not-empty? saved-grids)
|
||||
(let [parse-grid (fn [[type params]] {:type type :params params})
|
||||
grids (->> saved-grids (mapv parse-grid))]
|
||||
[:> "penpot:page" #js {}
|
||||
(when (seq saved-grids)
|
||||
[:& export-grid-data {:grids grids}])
|
||||
(when (seq flows)
|
||||
[:& export-flows {:flows flows}])]))))
|
||||
[:& export-grid-data {:grids grids}]))
|
||||
|
||||
(when (d/not-empty? flows)
|
||||
[:& export-flows {:flows flows}])
|
||||
|
||||
(when (d/not-empty? guides)
|
||||
[:& export-guides {:guides guides}])]))
|
||||
|
||||
(defn- export-shadow-data [{:keys [shadow]}]
|
||||
(mf/html
|
||||
|
|
|
@ -158,7 +158,7 @@
|
|||
show-rules? (contains? layout :rules)
|
||||
|
||||
;; TODO
|
||||
show-guides? true]
|
||||
disabled-guides? (or drawing-tool transform)]
|
||||
|
||||
(hooks/setup-dom-events viewport-ref zoom disable-paste in-viewport?)
|
||||
(hooks/setup-viewport-size viewport-ref)
|
||||
|
@ -361,13 +361,14 @@
|
|||
[:& widgets/viewport-actions]
|
||||
|
||||
(when show-rules?
|
||||
[:& rules/rules
|
||||
{:zoom zoom
|
||||
:vbox vbox}])
|
||||
[:*
|
||||
[:& rules/rules
|
||||
{:zoom zoom
|
||||
:vbox vbox}]
|
||||
|
||||
(when show-guides?
|
||||
[:& guides/viewport-guides
|
||||
{:zoom zoom
|
||||
:vbox vbox
|
||||
:hover-frame frame-parent}])]]]))
|
||||
[:& guides/viewport-guides
|
||||
{:zoom zoom
|
||||
:vbox vbox
|
||||
:hover-frame frame-parent
|
||||
:disabled-guides? disabled-guides?}]])]]]))
|
||||
|
||||
|
|
|
@ -234,7 +234,7 @@
|
|||
|
||||
(mf/defc guide
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [guide hover? on-guide-change get-hover-frame vbox zoom hover-frame]}]
|
||||
[{:keys [guide hover? on-guide-change get-hover-frame vbox zoom hover-frame disabled-guides?]}]
|
||||
|
||||
(let [axis (:axis guide)
|
||||
|
||||
|
@ -260,20 +260,21 @@
|
|||
guide-pill-corner-radius (/ guide-pill-corner-radius zoom)]
|
||||
|
||||
[:g.guide-area
|
||||
(let [{:keys [x y width height]} (guide-area-axis pos vbox zoom frame axis)]
|
||||
[:rect {:x x
|
||||
:y y
|
||||
:width width
|
||||
:height height
|
||||
:style {:fill "none"
|
||||
:pointer-events "fill"
|
||||
:cursor (if (= axis :x) "ew-resize" "ns-resize")}
|
||||
:on-pointer-enter on-pointer-enter
|
||||
:on-pointer-leave on-pointer-leave
|
||||
:on-pointer-down on-pointer-down
|
||||
:on-pointer-up on-pointer-up
|
||||
:on-lost-pointer-capture on-lost-pointer-capture
|
||||
:on-mouse-move on-mouse-move}])
|
||||
(when-not disabled-guides?
|
||||
(let [{:keys [x y width height]} (guide-area-axis pos vbox zoom frame axis)]
|
||||
[:rect {:x x
|
||||
:y y
|
||||
:width width
|
||||
:height height
|
||||
:style {:fill "none"
|
||||
:pointer-events "fill"
|
||||
:cursor (if (= axis :x) "ew-resize" "ns-resize")}
|
||||
:on-pointer-enter on-pointer-enter
|
||||
:on-pointer-leave on-pointer-leave
|
||||
:on-pointer-down on-pointer-down
|
||||
:on-pointer-up on-pointer-up
|
||||
:on-lost-pointer-capture on-lost-pointer-capture
|
||||
:on-mouse-move on-mouse-move}]))
|
||||
|
||||
(if (some? frame)
|
||||
(let [{:keys [l1-x1 l1-y1 l1-x2 l1-y2
|
||||
|
@ -345,7 +346,7 @@
|
|||
(str (mth/round pos))]]))]))
|
||||
|
||||
(mf/defc new-guide-area
|
||||
[{:keys [vbox zoom axis get-hover-frame]}]
|
||||
[{:keys [vbox zoom axis get-hover-frame disabled-guides?]}]
|
||||
|
||||
(let [on-guide-change
|
||||
(mf/use-callback
|
||||
|
@ -367,20 +368,21 @@
|
|||
frame]} (use-guide on-guide-change get-hover-frame zoom {:axis axis})]
|
||||
|
||||
[:g.new-guides
|
||||
(let [{:keys [x y width height]} (guide-creation-area vbox zoom axis)]
|
||||
[:rect {:x x
|
||||
:y y
|
||||
:width width
|
||||
:height height
|
||||
:on-pointer-enter on-pointer-enter
|
||||
:on-pointer-leave on-pointer-leave
|
||||
:on-pointer-down on-pointer-down
|
||||
:on-pointer-up on-pointer-up
|
||||
:on-lost-pointer-capture on-lost-pointer-capture
|
||||
:on-mouse-move on-mouse-move
|
||||
:style {:fill "none"
|
||||
:pointer-events "fill"
|
||||
:cursor (if (= axis :x) "ew-resize" "ns-resize")}}])
|
||||
(when-not disabled-guides?
|
||||
(let [{:keys [x y width height]} (guide-creation-area vbox zoom axis)]
|
||||
[:rect {:x x
|
||||
:y y
|
||||
:width width
|
||||
:height height
|
||||
:on-pointer-enter on-pointer-enter
|
||||
:on-pointer-leave on-pointer-leave
|
||||
:on-pointer-down on-pointer-down
|
||||
:on-pointer-up on-pointer-up
|
||||
:on-lost-pointer-capture on-lost-pointer-capture
|
||||
:on-mouse-move on-mouse-move
|
||||
:style {:fill "none"
|
||||
:pointer-events "fill"
|
||||
:cursor (if (= axis :x) "ew-resize" "ns-resize")}}]))
|
||||
|
||||
(when (:new-position @state)
|
||||
[:& guide {:guide {:axis axis
|
||||
|
@ -393,12 +395,15 @@
|
|||
|
||||
(mf/defc viewport-guides
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [zoom vbox hover-frame]}]
|
||||
[{:keys [zoom vbox hover-frame disabled-guides?]}]
|
||||
|
||||
(let [page (mf/deref refs/workspace-page)
|
||||
guides (->> (get-in page [:options :guides] {})
|
||||
(vals)
|
||||
(filter (guide-inside-vbox? vbox)))
|
||||
|
||||
guides (mf/use-memo
|
||||
(mf/deps page vbox)
|
||||
#(->> (get-in page [:options :guides] {})
|
||||
(vals)
|
||||
(filter (guide-inside-vbox? vbox))))
|
||||
|
||||
hover-frame-ref (mf/use-ref nil)
|
||||
|
||||
|
@ -417,16 +422,23 @@
|
|||
(st/emit! (dw/update-guides guide))
|
||||
(st/emit! (dw/remove-guide guide)))))]
|
||||
|
||||
#_(mf/use-effect (mf/deps guides) #(.log js/console (clj->js guides)))
|
||||
(mf/use-effect
|
||||
(mf/deps hover-frame)
|
||||
(fn []
|
||||
#_(.log js/console "set" (clj->js hover-frame))
|
||||
(mf/set-ref-val! hover-frame-ref hover-frame)))
|
||||
|
||||
[:g.guides {:pointer-events "none"}
|
||||
[:& new-guide-area {:vbox vbox :zoom zoom :axis :x :get-hover-frame get-hover-frame}]
|
||||
[:& new-guide-area {:vbox vbox :zoom zoom :axis :y :get-hover-frame get-hover-frame}]
|
||||
[:& new-guide-area {:vbox vbox
|
||||
:zoom zoom
|
||||
:axis :x
|
||||
:get-hover-frame get-hover-frame
|
||||
:disabled-guides? disabled-guides?}]
|
||||
|
||||
[:& new-guide-area {:vbox vbox
|
||||
:zoom zoom
|
||||
:axis :y
|
||||
:get-hover-frame get-hover-frame
|
||||
:disabled-guides? disabled-guides?}]
|
||||
|
||||
(for [current guides]
|
||||
[:& guide {:key (str "guide-" (:id current))
|
||||
|
@ -434,5 +446,6 @@
|
|||
:vbox vbox
|
||||
:zoom zoom
|
||||
:get-hover-frame get-hover-frame
|
||||
:on-guide-change on-guide-change}])]))
|
||||
:on-guide-change on-guide-change
|
||||
:disabled-guides? disabled-guides?}])]))
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
:opacity line-opacity}])
|
||||
|
||||
(defn get-snap
|
||||
[coord {:keys [shapes page-id filter-shapes modifiers]}]
|
||||
[coord {:keys [shapes page-id remove-snap? modifiers]}]
|
||||
(let [shape (if (> (count shapes) 1)
|
||||
(->> shapes (map gsh/transform-shape) gsh/selection-rect (gsh/setup {:type :rect}))
|
||||
(->> shapes (first)))
|
||||
|
@ -68,7 +68,7 @@
|
|||
(->> (sp/shape-snap-points shape)
|
||||
(map #(vector frame-id %)))))
|
||||
(rx/flat-map (fn [[frame-id point]]
|
||||
(->> (snap/get-snap-points page-id frame-id filter-shapes point coord)
|
||||
(->> (snap/get-snap-points page-id frame-id remove-snap? point coord)
|
||||
(rx/map #(vector point % coord)))))
|
||||
(rx/reduce conj []))))
|
||||
|
||||
|
@ -104,7 +104,7 @@
|
|||
(hash-map coord fixedv (flip coord) maxv)]))))
|
||||
|
||||
(mf/defc snap-feedback
|
||||
[{:keys [shapes filter-shapes zoom modifiers] :as props}]
|
||||
[{:keys [shapes remove-snap? zoom modifiers] :as props}]
|
||||
(let [state (mf/use-state [])
|
||||
subject (mf/use-memo #(rx/subject))
|
||||
|
||||
|
@ -129,7 +129,7 @@
|
|||
#(rx/dispose! sub))))
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps shapes filter-shapes modifiers)
|
||||
(mf/deps shapes remove-snap? modifiers)
|
||||
(fn []
|
||||
(rx/push! subject props)))
|
||||
|
||||
|
@ -152,29 +152,26 @@
|
|||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [layout zoom objects selected page-id drawing transform modifiers] :as props}]
|
||||
|
||||
(let [;; shapes (mf/deref (refs/objects-by-id selected))
|
||||
;; filter-shapes (mf/deref refs/selected-shapes-with-children)
|
||||
(let [shapes
|
||||
(->> selected
|
||||
(map #(get objects %))
|
||||
(filterv (comp not nil?)))
|
||||
|
||||
shapes (->> selected
|
||||
(map #(get objects %))
|
||||
(filterv (comp not nil?)))
|
||||
filter-shapes (into #{}
|
||||
(comp (mapcat #(cp/get-object-with-children % objects))
|
||||
(map :id))
|
||||
selected)
|
||||
filter-shapes
|
||||
(into #{}
|
||||
(comp (mapcat #(cp/get-object-with-children % objects))
|
||||
(map :id))
|
||||
selected)
|
||||
|
||||
filter-shapes (fn [id]
|
||||
(if (= id :layout)
|
||||
(or (not (contains? layout :display-grid))
|
||||
(not (contains? layout :snap-grid)))
|
||||
(or (filter-shapes id)
|
||||
(not (contains? layout :dynamic-alignment)))))
|
||||
remove-snap? (mf/use-memo
|
||||
(mf/deps layout filter-shapes)
|
||||
#(snap/make-remove-snap layout filter-shapes))
|
||||
|
||||
shapes (if drawing [drawing] shapes)]
|
||||
(when (or drawing transform)
|
||||
[:& snap-feedback {:shapes shapes
|
||||
:page-id page-id
|
||||
:filter-shapes filter-shapes
|
||||
:remove-snap? remove-snap?
|
||||
:zoom zoom
|
||||
:modifiers modifiers}])))
|
||||
|
||||
|
|
|
@ -515,6 +515,20 @@
|
|||
(let [flows-node (get-data node :penpot:flows)]
|
||||
(->> flows-node :content (mapv parse-flow-node))))
|
||||
|
||||
(defn parse-guide-node [node]
|
||||
(let [attrs (-> node :attrs remove-penpot-prefix)]
|
||||
(println attrs)
|
||||
(let [id (uuid/next)]
|
||||
[id
|
||||
{:id id
|
||||
:frame-id (when (:frame-id attrs) (-> attrs :frame-id uuid))
|
||||
:axis (-> attrs :axis keyword)
|
||||
:position (-> attrs :position d/parse-double)}])))
|
||||
|
||||
(defn parse-guides [node]
|
||||
(let [guides-node (get-data node :penpot:guides)]
|
||||
(->> guides-node :content (map parse-guide-node) (into {}))))
|
||||
|
||||
(defn extract-from-data
|
||||
([node tag]
|
||||
(extract-from-data node tag identity))
|
||||
|
@ -764,7 +778,8 @@
|
|||
grids (->> (parse-grids node)
|
||||
(group-by :type)
|
||||
(d/mapm (fn [_ v] (-> v first :params))))
|
||||
flows (parse-flows node)]
|
||||
flows (parse-flows node)
|
||||
guides (parse-guides node)]
|
||||
(cond-> {}
|
||||
(some? background)
|
||||
(assoc-in [:options :background] background)
|
||||
|
@ -773,7 +788,10 @@
|
|||
(assoc-in [:options :saved-grids] grids)
|
||||
|
||||
(d/not-empty? flows)
|
||||
(assoc-in [:options :flows] flows))))
|
||||
(assoc-in [:options :flows] flows)
|
||||
|
||||
(d/not-empty? guides)
|
||||
(assoc-in [:options :guides] guides))))
|
||||
|
||||
(defn parse-interactions
|
||||
[node]
|
||||
|
|
|
@ -283,7 +283,6 @@
|
|||
|
||||
(defn setup-interactions
|
||||
[file]
|
||||
|
||||
(letfn [(add-interactions
|
||||
[file [id interactions]]
|
||||
(->> interactions
|
||||
|
@ -294,7 +293,6 @@
|
|||
(let [interactions (:interactions file)
|
||||
file (dissoc file :interactions)]
|
||||
(->> interactions (reduce add-interactions file))))]
|
||||
|
||||
(-> file process-interactions)))
|
||||
|
||||
(defn resolve-media
|
||||
|
@ -328,7 +326,12 @@
|
|||
(assoc :id (resolve page-id)))
|
||||
flows (->> (get-in page-data [:options :flows])
|
||||
(mapv #(update % :starting-frame resolve)))
|
||||
page-data (d/assoc-in-when page-data [:options :flows] flows)
|
||||
guides (->> (get-in page-data [:options :guides])
|
||||
(d/mapm #(update %2 :frame-id resolve)))
|
||||
|
||||
page-data (-> page-data
|
||||
(d/assoc-in-when [:options :flows] flows)
|
||||
(d/assoc-in-when [:options :guides] guides))
|
||||
file (-> file (fb/add-page page-data))]
|
||||
(->> (rx/from nodes)
|
||||
(rx/filter cip/shape?)
|
||||
|
|
Loading…
Add table
Reference in a new issue