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:
parent
3230c5c2ed
commit
9309c76162
5 changed files with 290 additions and 72 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
176
frontend/src/uxbox/main/ui/workspace/shapes/interactions.cljs
Normal file
176
frontend/src/uxbox/main/ui/workspace/shapes/interactions.cljs
Normal 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}])))]))
|
||||
|
|
@ -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)]))]]]
|
||||
|
|
|
@ -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}])]]))
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue