0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-24 23:49:45 -05:00

🎉 Show interactions lines in workspace

This commit is contained in:
Andrés Moya 2020-05-08 15:19:53 +02:00
parent 3230c5c2ed
commit 9309c76162
5 changed files with 290 additions and 72 deletions

View file

@ -27,8 +27,9 @@
transform (geom/transform-matrix shape)]
[:g
(for [item childs]
[:& shape-wrapper
{:frame frame :shape item :key (:id item)}])
[:& shape-wrapper {:frame frame
:shape item
:key (:id item)}])
(when (not is-child-selected?)
[:rect {:transform transform

View file

@ -30,45 +30,16 @@
(defn on-mouse-down
[event {:keys [interactions] :as shape}]
(let [interaction (first (filter #(= (:action-type % :click)) interactions))]
(let [interaction (first (filter #(= (:event-type %) :click) interactions))]
(case (:action-type interaction)
:navigate
(let [frame-id (:destination interaction)]
(st/emit! (dv/go-to-frame frame-id)))
nil)))
(declare shape-wrapper-factory)
(defn frame-wrapper-factory
[objects]
(let [shape-wrapper (shape-wrapper-factory objects)
frame-shape (frame/frame-shape shape-wrapper)]
(mf/fnc frame-wrapper
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
childs (mapv #(get objects %) (:shapes shape))
shape (geom/transform-shape shape)
props (obj/merge! #js {} props
#js {:shape shape
:childs childs})]
[:& frame-shape {:shape shape :childs childs}]))))
(defn group-wrapper-factory
[objects]
(let [shape-wrapper (shape-wrapper-factory objects)
group-shape (group/group-shape shape-wrapper)]
(mf/fnc group-wrapper
{::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})]
[:> group-shape props]))))
(defn generic-wrapper-factory
[component]
"Wrap some svg shape and add interaction controls"
[component show-interactions?]
(mf/fnc generic-wrapper
{::mf/wrap-props false}
[props]
@ -76,14 +47,18 @@
:as shape} (->> (unchecked-get props "shape")
(geom/selection-rect-shape))
show-interactions? (unchecked-get props "show-interactions?")
childs (unchecked-get props "childs")
frame (unchecked-get props "frame")
on-mouse-down (mf/use-callback
(mf/deps shape)
#(on-mouse-down % shape))]
[:g.shape {:on-mouse-down on-mouse-down
:cursor (when (:interactions shape) "pointer")}
[:& component {:shape shape}]
[:& component {:shape shape
:frame frame
:childs childs}]
(when (and (:interactions shape) show-interactions?)
[:rect {:x (- x 1)
:y (- y 1)
@ -94,38 +69,100 @@
:stroke-width 1
:fill-opacity 0.2}])])))
(def rect-wrapper (generic-wrapper-factory rect/rect-shape))
(def icon-wrapper (generic-wrapper-factory icon/icon-shape))
(def image-wrapper (generic-wrapper-factory image/image-shape))
(def path-wrapper (generic-wrapper-factory path/path-shape))
(def text-wrapper (generic-wrapper-factory text/text-shape))
(def circle-wrapper (generic-wrapper-factory circle/circle-shape))
(defn frame-wrapper
[shape-container show-interactions?]
(generic-wrapper-factory (frame/frame-shape shape-container) show-interactions?))
(defn shape-wrapper-factory
[objects]
(mf/fnc shape-wrapper
{::mf/wrap-props false}
[props]
(let [group-wrapper (mf/use-memo (mf/deps objects) #(group-wrapper-factory objects))
shape (unchecked-get props "shape")
frame (unchecked-get props "frame")
show-interactions? (unchecked-get props "show-interactions?")]
(when (and shape (not (:hidden shape)))
(let [shape (geom/transform-shape frame shape)
opts #js {:shape shape
:show-interactions? show-interactions?}]
(case (:type shape)
:curve [:> path-wrapper opts]
:text [:> text-wrapper opts]
:icon [:> icon-wrapper opts]
:rect [:> rect-wrapper opts]
:path [:> path-wrapper opts]
:image [:> image-wrapper opts]
:circle [:> circle-wrapper opts]
:group [:> group-wrapper
{:shape shape
:frame frame
:show-interactions? show-interactions?}]))))))
(defn group-wrapper
[shape-container show-interactions?]
(generic-wrapper-factory (group/group-shape shape-container) show-interactions?))
(defn rect-wrapper
[show-interactions?]
(generic-wrapper-factory rect/rect-shape show-interactions?))
(defn icon-wrapper
[show-interactions?]
(generic-wrapper-factory icon/icon-shape show-interactions?))
(defn image-wrapper
[show-interactions?]
(generic-wrapper-factory image/image-shape show-interactions?))
(defn path-wrapper
[show-interactions?]
(generic-wrapper-factory path/path-shape show-interactions?))
(defn text-wrapper
[show-interactions?]
(generic-wrapper-factory text/text-shape show-interactions?))
(defn circle-wrapper
[show-interactions?]
(generic-wrapper-factory circle/circle-shape show-interactions?))
(declare shape-container-factory)
(defn frame-container-factory
[objects show-interactions?]
(let [shape-container (shape-container-factory objects show-interactions?)
frame-wrapper (frame-wrapper shape-container show-interactions?)]
(mf/fnc frame-container
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
childs (mapv #(get objects %) (:shapes shape))
shape (geom/transform-shape shape)
props (obj/merge! #js {} props
#js {:childs childs
:show-interactions? show-interactions?})]
[:> frame-wrapper props]))))
(defn group-container-factory
[objects show-interactions?]
(let [shape-container (shape-container-factory objects show-interactions?)
group-wrapper (group-wrapper shape-container show-interactions?)]
(mf/fnc group-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?})]
[:> group-wrapper props]))))
(defn shape-container-factory
[objects show-interactions?]
(let [path-wrapper (path-wrapper show-interactions?)
text-wrapper (text-wrapper show-interactions?)
icon-wrapper (icon-wrapper show-interactions?)
rect-wrapper (rect-wrapper show-interactions?)
image-wrapper (image-wrapper show-interactions?)
circle-wrapper (circle-wrapper show-interactions?)]
(mf/fnc shape-container
{::mf/wrap-props false}
[props]
(let [group-container (mf/use-memo
(mf/deps objects)
#(group-container-factory objects show-interactions?))
shape (unchecked-get props "shape")
frame (unchecked-get props "frame")]
(when (and shape (not (:hidden shape)))
(let [shape (geom/transform-shape frame shape)
opts #js {:shape shape}]
(case (:type shape)
:curve [:> path-wrapper opts]
:text [:> text-wrapper opts]
:icon [:> icon-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}])))))))
(mf/defc frame-svg
{::mf/wrap [mf/memo]}
@ -146,7 +183,7 @@
" " (:height frame 0))
wrapper (mf/use-memo
(mf/deps objects)
#(frame-wrapper-factory objects))]
#(frame-container-factory objects show-interactions?))]
[:svg {:view-box vbox
:width width

View file

@ -0,0 +1,176 @@
;; 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 uxbox.main.ui.workspace.shapes.interactions
"Visually show shape interactions in workspace"
(:require
[rumext.alpha :as mf]
[cuerdas.core :as str]
[uxbox.util.data :as dt]
[uxbox.util.dom :as dom]
[uxbox.util.geom.point :as gpt]
[uxbox.util.geom.shapes :as geom]
[uxbox.main.store :as st]
[uxbox.main.refs :as refs]
[uxbox.main.data.workspace :as dw]
[uxbox.main.ui.keyboard :as kbd]
))
(defn- get-click-interaction
[shape]
(first (filter #(= (:event-type %) :click) (:interactions shape))))
(defn- on-mouse-down-unselected
[event {:keys [id type] :as shape} selected]
(do
(dom/stop-propagation event)
(if (empty? selected)
(st/emit! (dw/select-shape id)
(dw/start-move-selected))
(if (kbd/shift? event)
(st/emit! (dw/select-shape id))
(st/emit! dw/deselect-all
(dw/select-shape id)
(dw/start-move-selected))))))
;; TODO: add more mouse behavior, to create interactions by drag&drop
;; (defn- on-mouse-down
;; [event {:keys [id type] :as shape}]
;; (let [selected @refs/selected-shapes
;; selected? (contains? selected id)
;; drawing? @refs/selected-drawing-tool
;; button (.-which (.-nativeEvent event))]
;; (when-not (:blocked shape)
;; (cond
;; (not= 1 button)
;; nil
;;
;; drawing?
;; nil
;;
;; (= type :frame)
;; (when selected?
;; (dom/stop-propagation event)
;; (st/emit! (dw/start-move-selected)))
;;
;; (and (not selected?) (empty? selected))
;; (do
;; (dom/stop-propagation event)
;; (st/emit! dw/deselect-all
;; (dw/select-shape id)
;; (dw/start-move-selected)))
;;
;; (and (not selected?) (not (empty? selected)))
;; (do
;; (dom/stop-propagation event)
;; (if (kbd/shift? event)
;; (st/emit! (dw/select-shape id))
;; (st/emit! dw/deselect-all
;; (dw/select-shape id)
;; (dw/start-move-selected))))
;; :else
;; (do
;; (dom/stop-propagation event)
;; (st/emit! (dw/start-move-selected)))))))
(mf/defc interaction-path
[{:keys [orig-shape dest-shape selected selected?] :as props}]
(let [orig-rect (geom/selection-rect-shape orig-shape)
dest-rect (geom/selection-rect-shape dest-shape)
orig-x-left (:x orig-rect)
orig-x-right (+ orig-x-left (:width orig-rect))
orig-x-center (+ orig-x-left (/ (:width orig-rect) 2))
dest-x-left (:x dest-rect)
dest-x-right (+ dest-x-left (:width dest-rect))
dest-x-center (+ dest-x-left (/ (:width dest-rect) 2))
orig-pos (if (<= orig-x-right dest-x-left) :right
(if (>= orig-x-left dest-x-right) :left
(if (<= orig-x-center dest-x-center) :left :right)))
dest-pos (if (<= orig-x-right dest-x-left) :left
(if (>= orig-x-left dest-x-right) :right
(if (<= orig-x-center dest-x-center) :left :right)))
orig-x (if (= orig-pos :right) orig-x-right orig-x-left)
dest-x (if (= dest-pos :right) dest-x-right dest-x-left)
orig-dx (if (= orig-pos :right) 100 -100)
dest-dx (if (= dest-pos :right) 100 -100)
orig-y (+ (:y orig-rect) (/ (:height orig-rect) 2))
dest-y (+ (:y dest-rect) (/ (:height dest-rect) 2))
path ["M" orig-x orig-y "C" (+ orig-x orig-dx) orig-y (+ dest-x dest-dx) dest-y dest-x dest-y]
pdata (str/join " " path)
arrow-path (if (= dest-pos :left)
["M" (- dest-x 5) dest-y "l 8 0 l -4 -4 m 4 4 l -4 4"]
["M" (+ dest-x 5) dest-y "l -8 0 l 4 -4 m -4 4 l 4 4"])
arrow-pdata (str/join " " arrow-path)]
(if-not selected?
[:path {:stroke "#B1B2B5"
:fill "transparent"
:stroke-width 2
:d pdata
:on-mouse-down #(on-mouse-down-unselected % orig-shape selected)}]
[:g
[:path {:stroke "#31EFB8"
:fill "transparent"
:stroke-width 2
:d pdata}]
[:circle {:cx orig-x
:cy orig-y
:r 4
:stroke "#31EFB8"
:stroke-width 2
:fill "#FFFFFF"}]
[:circle {:cx dest-x
:cy dest-y
:r 8
:stroke "#31EFB8"
:stroke-width 2
:fill "#FFFFFF"}]
[:path {:stroke "#31EFB8"
:fill "transparent"
:stroke-width 2
:d arrow-pdata}]])))
(mf/defc interactions
[{:keys [selected] :as props}]
(let [data (mf/deref refs/workspace-data)
objects (:objects data)
active-shapes (filter #(first (get-click-interaction %)) (vals objects))
selected-shapes (map #(get objects %) selected)]
[:*
(for [shape active-shapes]
(let [interaction (get-click-interaction shape)
dest-shape (get objects (:destination interaction))
selected? (contains? selected (:id shape))]
(when-not selected?
[:& interaction-path {:key (:id shape)
:orig-shape shape
:dest-shape dest-shape
:selected selected
:selected? false}])))
(for [shape selected-shapes]
(let [interaction (get-click-interaction shape)
dest-shape (get objects (:destination interaction))]
(when dest-shape
[:& interaction-path {:key (:id shape)
:orig-shape shape
:dest-shape dest-shape
:selected selected
:selected? true}])))]))

View file

@ -71,7 +71,8 @@
(t locale "workspace.options.none")]
(for [frame frames]
(when (not= (:id frame) (:id shape)) ; A frame cannot navigate to itself
(when (and (not= (:id frame) (:id shape)) ; A frame cannot navigate to itself
(not= (:id frame) (:frame-id shape))) ; nor a shape to its container frame
[:li {:key (:id frame)
:on-click #(on-select-destination (:id frame))}
(:name frame)]))]]]

View file

@ -24,6 +24,7 @@
[uxbox.main.ui.keyboard :as kbd]
[uxbox.main.ui.hooks :as hooks]
[uxbox.main.ui.workspace.shapes :refer [shape-wrapper frame-wrapper]]
[uxbox.main.ui.workspace.shapes.interactions :refer [interactions]]
[uxbox.main.ui.workspace.drawarea :refer [draw-area start-drawing]]
[uxbox.main.ui.workspace.grid :refer [grid]]
[uxbox.main.ui.workspace.ruler :refer [ruler]]
@ -126,8 +127,8 @@
(mf/defc viewport
[{:keys [page local] :as props}]
(let [
{:keys [drawing-tool
(let [{:keys [drawing-tool
options-mode
zoom
flags
vport
@ -375,5 +376,7 @@
[:& ruler {:zoom zoom :ruler (:ruler local)}])
[:& presence/active-cursors {:page page}]
[:& selection-rect {:data (:selrect local)}]]]))
[:& selection-rect {:data (:selrect local)}]
(when (= options-mode :prototype)
[:& interactions {:selected selected}])]]))