From 9309c7616241f97e6772158947ab02e6e8395d47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Fri, 8 May 2020 15:19:53 +0200 Subject: [PATCH] :tada: Show interactions lines in workspace --- frontend/src/uxbox/main/ui/shapes/group.cljs | 5 +- frontend/src/uxbox/main/ui/viewer/shapes.cljs | 169 ++++++++++------- .../ui/workspace/shapes/interactions.cljs | 176 ++++++++++++++++++ .../sidebar/options/interactions.cljs | 3 +- .../src/uxbox/main/ui/workspace/viewport.cljs | 9 +- 5 files changed, 290 insertions(+), 72 deletions(-) create mode 100644 frontend/src/uxbox/main/ui/workspace/shapes/interactions.cljs diff --git a/frontend/src/uxbox/main/ui/shapes/group.cljs b/frontend/src/uxbox/main/ui/shapes/group.cljs index 8c967a3eb..999efca25 100644 --- a/frontend/src/uxbox/main/ui/shapes/group.cljs +++ b/frontend/src/uxbox/main/ui/shapes/group.cljs @@ -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 diff --git a/frontend/src/uxbox/main/ui/viewer/shapes.cljs b/frontend/src/uxbox/main/ui/viewer/shapes.cljs index 34368e663..1c29ffc2f 100644 --- a/frontend/src/uxbox/main/ui/viewer/shapes.cljs +++ b/frontend/src/uxbox/main/ui/viewer/shapes.cljs @@ -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 diff --git a/frontend/src/uxbox/main/ui/workspace/shapes/interactions.cljs b/frontend/src/uxbox/main/ui/workspace/shapes/interactions.cljs new file mode 100644 index 000000000..4f5dbd587 --- /dev/null +++ b/frontend/src/uxbox/main/ui/workspace/shapes/interactions.cljs @@ -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}])))])) + diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs index bef9cdc61..f389fa7ca 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs @@ -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)]))]]] diff --git a/frontend/src/uxbox/main/ui/workspace/viewport.cljs b/frontend/src/uxbox/main/ui/workspace/viewport.cljs index 79bbb068b..7c62bd7fa 100644 --- a/frontend/src/uxbox/main/ui/workspace/viewport.cljs +++ b/frontend/src/uxbox/main/ui/workspace/viewport.cljs @@ -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}])]]))