From f303d7b33e4277c108f84993213161c795ad15d0 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 21 Jan 2022 13:52:21 +0100 Subject: [PATCH] :sparkles: Add support to export/import guides --- common/src/app/common/types/page_options.cljc | 2 +- frontend/src/app/main/data/workspace.cljs | 6 +- frontend/src/app/main/render.cljs | 4 +- frontend/src/app/main/snap.cljs | 54 ++++++----- frontend/src/app/main/ui/shapes/export.cljs | 30 ++++-- .../src/app/main/ui/workspace/viewport.cljs | 19 ++-- .../main/ui/workspace/viewport/guides.cljs | 91 +++++++++++-------- .../ui/workspace/viewport/snap_points.cljs | 37 ++++---- frontend/src/app/util/import/parser.cljs | 22 ++++- frontend/src/app/worker/import.cljs | 9 +- 10 files changed, 165 insertions(+), 109 deletions(-) diff --git a/common/src/app/common/types/page_options.cljc b/common/src/app/common/types/page_options.cljc index f7927363f..687e98985 100644 --- a/common/src/app/common/types/page_options.cljc +++ b/common/src/app/common/types/page_options.cljc @@ -75,7 +75,7 @@ :opt-un [:guides/frame-id])) (s/def ::guides - (s/map-of uuid? ::shape)) + (s/map-of uuid? ::guide)) ;; --- Options diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 2ed754a81..6af0751cb 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -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 diff --git a/frontend/src/app/main/render.cljs b/frontend/src/app/main/render.cljs index 26a197e5a..91d6dd72d 100644 --- a/frontend/src/app/main/render.cljs +++ b/frontend/src/app/main/render.cljs @@ -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)] diff --git a/frontend/src/app/main/snap.cljs b/frontend/src/app/main/snap.cljs index 8fa0dc6ec..1de5b1aab 100644 --- a/frontend/src/app/main/snap.cljs +++ b/frontend/src/app/main/snap.cljs @@ -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) diff --git a/frontend/src/app/main/ui/shapes/export.cljs b/frontend/src/app/main/ui/shapes/export.cljs index 1ea58e5ed..efce856f6 100644 --- a/frontend/src/app/main/ui/shapes/export.cljs +++ b/frontend/src/app/main/ui/shapes/export.cljs @@ -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 diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 015d39e3e..41a1a04e2 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -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?}]])]]])) diff --git a/frontend/src/app/main/ui/workspace/viewport/guides.cljs b/frontend/src/app/main/ui/workspace/viewport/guides.cljs index 1251a71e1..8c0c7f84b 100644 --- a/frontend/src/app/main/ui/workspace/viewport/guides.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/guides.cljs @@ -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?}])])) diff --git a/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs b/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs index ec95528ee..ee807765f 100644 --- a/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs @@ -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}]))) diff --git a/frontend/src/app/util/import/parser.cljs b/frontend/src/app/util/import/parser.cljs index dec08ba7c..35f9e74f0 100644 --- a/frontend/src/app/util/import/parser.cljs +++ b/frontend/src/app/util/import/parser.cljs @@ -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] diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index 983e5d83d..e6bdb5d49 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -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?)