🎉 Add basic interaction to shapes

This commit is contained in:
Andrés Moya 2020-04-27 14:31:57 +02:00
parent 49023117c3
commit 1e898f94f3
32 changed files with 1024 additions and 226 deletions

View file

@ -53,6 +53,7 @@
(def icon-empty (icon-xref :icon-empty))
(def image (icon-xref :image))
(def infocard (icon-xref :infocard))
(def interaction (icon-xref :interaction))
(def layers (icon-xref :layers))
(def letter-spacing (icon-xref :letter-spacing))
(def line (icon-xref :line))
@ -66,6 +67,7 @@
(def mail (icon-xref :mail))
(def minus (icon-xref :minus))
(def move (icon-xref :move))
(def navigate (icon-xref :navigate))
(def options (icon-xref :options))
(def organize (icon-xref :organize))
(def palette (icon-xref :palette))

View file

@ -31,6 +31,8 @@
(s/def ::file (s/keys :req-un [::id ::name]))
(s/def ::page (s/keys :req-un [::id ::name ::cp/data]))
(s/def ::interactions-mode #{:hide :show :show-on-click})
(s/def ::bundle
(s/keys :req-un [::project ::file ::page]))
@ -45,7 +47,10 @@
(ptk/reify ::initialize
ptk/UpdateEvent
(update [_ state]
(assoc state :viewer-local {:zoom 1 :page-id page-id}))
(assoc state :viewer-local {:zoom 1
:page-id page-id
:interactions-mode :hide
:show-interactions? false}))
ptk/WatchEvent
(watch [_ state stream]
@ -178,6 +183,52 @@
(when (< index (dec total))
(rx/of (rt/nav :viewer pparams (assoc qparams :index (inc index)))))))))
(defn set-interactions-mode
[mode]
(us/verify ::interactions-mode mode)
(ptk/reify ::set-interactions-mode
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:viewer-local :interactions-mode] mode)
(assoc-in [:viewer-local :show-interactions?] (case mode
:hide false
:show true
:show-on-click false))))))
(declare flash-done)
(def flash-interactions
(ptk/reify ::flash-interactions
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:viewer-local :show-interactions?] true))
ptk/WatchEvent
(watch [_ state stream]
(let [stopper (rx/filter (ptk/type? ::flash-interactions) stream)]
(->> (rx/of flash-done)
(rx/delay 1000)
(rx/take-until stopper))))))
(def flash-done
(ptk/reify ::flash-done
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:viewer-local :show-interactions?] false))))
;; --- Navigation
(defn go-to-frame
[frame-id]
(us/verify ::us/uuid frame-id)
(ptk/reify ::go-to-frame
ptk/WatchEvent
(watch [_ state stream]
(let [page-id (get-in state [:viewer-local :page-id])
frames (get-in state [:viewer-data :frames])
index (d/index-of-pred frames #(= (:id %) frame-id))]
(rx/of (rt/nav :viewer {:page-id page-id} {:index index}))))))
;; --- Shortcuts

View file

@ -68,13 +68,16 @@
:element-options
:rules})
(s/def ::options-mode #{:design :prototype})
(def workspace-default
{:zoom 1
:flags #{}
:selected #{}
:drawing nil
:drawing-tool nil
:tooltip nil})
:tooltip nil
:options-mode :design})
(def initialize-layout
(ptk/reify ::initialize-layout
@ -243,6 +246,16 @@
(conj flags flag)))))]
(reduce reduce-fn state flags)))))
;; --- Set element options mode
(defn set-options-mode
[mode]
(us/assert ::options-mode mode)
(ptk/reify ::set-options-mode
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-local :options-mode] mode))))
;; --- Tooltip
(defn assign-cursor-tooltip

View file

@ -9,6 +9,7 @@
(:require
[rumext.alpha :as mf]
[uxbox.common.uuid :as uuid]
[uxbox.common.pages :as cp]
[uxbox.util.math :as mth]
[uxbox.util.geom.shapes :as geom]
[uxbox.util.geom.point :as gpt]
@ -110,3 +111,34 @@
[:& shape-wrapper {:shape item
:key (:id item)}]))]))
(mf/defc frame-svg
{::mf/wrap [mf/memo]}
[{:keys [objects frame zoom] :or {zoom 1} :as props}]
(let [modifier (-> (gpt/point (:x frame) (:y frame))
(gpt/negate)
(gmt/translate-matrix))
frame-id (:id frame)
modifier-ids (concat [frame-id] (cp/get-children frame-id objects))
update-fn (fn [state shape-id]
(-> state
(assoc-in [shape-id :modifiers :displacement] modifier)))
objects (reduce update-fn objects modifier-ids)
frame (assoc-in frame [:modifiers :displacement] modifier )
width (* (:width frame) zoom)
height (* (:height frame) zoom)
vbox (str "0 0 " (:width frame 0) " " (:height frame 0))
frame-wrapper (mf/use-memo (mf/deps objects)
#(frame-wrapper objects))]
[:svg {:view-box vbox
:width width
:height height
:version "1.1"
:xmlnsXlink "http://www.w3.org/1999/xlink"
:xmlns "http://www.w3.org/2000/svg"}
[:& frame-wrapper {:shape frame
:view-box vbox}]]))

View file

@ -14,7 +14,10 @@
[beicon.core :as rx]
[uxbox.common.pages :as cp]
[uxbox.main.constants :as c]
[uxbox.main.store :as st]))
[uxbox.main.store :as st]
[uxbox.common.uuid :as uuid]))
;; ---- Global refs
(def route
(l/derived :route st/state))
@ -28,6 +31,8 @@
(def profile
(l/derived :profile st/state))
;; ---- Workspace refs
(def workspace-local
(l/derived :workspace-local st/state))
@ -79,6 +84,15 @@
(vec))))]
(l/derived selector st/state =)))
(def frames
(letfn [(selector [data]
(->> (get-in data [:objects uuid/zero])
:shapes
(map #(get-in data [:objects %]))
(filter #(= (:type %) :frame))
(sort-by :name)))]
(l/derived selector workspace-data)))
(defn is-child-selected?
[id]
(letfn [(selector [state]
@ -120,3 +134,21 @@
(def current-transform
(l/derived :transform workspace-local))
(def options-mode
(l/derived :options-mode workspace-local))
;; ---- Viewer refs
(def viewer-data-ref
(l/derived :viewer-data st/state))
(def viewer-local-ref
(l/derived :viewer-local st/state))
(def interactions-mode
(l/derived :interactions-mode viewer-local-ref))
(def show-interactions?
(l/derived :show-interactions? viewer-local-ref))

View file

@ -17,13 +17,15 @@
[uxbox.main.ui.shapes.bounding-box :refer [bounding-box]]
[uxbox.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]]))
;; --- Circle Wrapper
;; --- Circle Wrapper for workspace
(declare circle-shape)
(mf/defc circle-wrapper
[{:keys [shape] :as props}]
(let [selected (mf/deref refs/selected-shapes)
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
selected (mf/deref refs/selected-shapes)
selected? (contains? selected (:id shape))
on-mouse-down #(common/on-mouse-down % shape)
on-context-menu #(common/on-context-menu % shape)]
@ -32,6 +34,36 @@
:on-context-menu on-context-menu}
[:& circle-shape {:shape shape}]]))
;; --- Circle Wrapper for viewer
(mf/defc circle-viewer-wrapper
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
{:keys [id x y width height]} shape
transform (geom/transform-matrix shape)
show-interactions? (mf/deref refs/show-interactions?)
on-mouse-down (mf/use-callback
(mf/deps shape)
#(common/on-mouse-down-viewer % shape))]
[:g.shape {:on-mouse-down on-mouse-down
:cursor (when (:interactions shape) "pointer")}
[:*
[:& circle-shape {:shape shape}]
(when (and (:interactions shape) show-interactions?)
[:> "rect" #js {:x (- x 1)
:y (- y 1)
:width (+ width 2)
:height (+ height 2)
:transform transform
:fill "#31EFB8"
:stroke "#31EFB8"
:strokeWidth 1
:fillOpacity 0.2}])]]))
;; --- Circle Shape
(mf/defc circle-shape

View file

@ -16,6 +16,7 @@
[uxbox.common.data :as d]
[uxbox.common.spec :as us]
[uxbox.main.data.workspace :as dw]
[uxbox.main.data.viewer :as dv]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.ui.keyboard :as kbd]
@ -66,10 +67,24 @@
(st/emit! (dw/start-move-selected)))))))
;; --- Workspace context menu
(defn on-context-menu
[event shape]
(dom/prevent-default event)
(dom/stop-propagation event)
(let [position (dom/get-client-position event)]
(st/emit!(dw/show-shape-context-menu {:position position
:shape shape}))))
(st/emit! (dw/show-shape-context-menu {:position position
:shape shape}))))
;; --- Interaction actions (in viewer mode)
(defn on-mouse-down-viewer
[event {:keys [interactions] :as shape}]
(let [interaction (first (filter #(= (:action-type % :click)) interactions))]
(case (:action-type interaction)
:navigate
(let [frame-id (:destination interaction)]
(st/emit! (dv/go-to-frame frame-id)))
nil)))

View file

@ -32,6 +32,8 @@
(declare frame-shape)
(declare translate-to-frame)
;; ---- Frame Wrapper for workspace
(defn frame-wrapper-memo-equals?
[np op]
(let [n-shape (aget np "shape")
@ -114,6 +116,20 @@
{:shape (geom/transform-shape shape)
:childs childs}]])))))
;; ;; --- Frame Wrapper for viewer
;;
;; (mf/defc frame-viewer-wrapper
;; {::mf/wrap-props false}
;; [props]
;; (let [shape (unchecked-get props "shape")
;; on-mouse-down (mf/use-callback
;; (mf/deps shape)
;; #(common/on-mouse-down-viewer % shape))]
;; [:g.shape {:on-mouse-down on-mouse-down}
;; [:& rect-shape {:shape shape}]]))
;; ---- Frame shape
(defn frame-shape
[shape-wrapper]
(mf/fnc frame-shape

View file

@ -14,25 +14,60 @@
[uxbox.main.ui.shapes.common :as common]
[uxbox.util.interop :as itr]))
;; --- Icon Wrapper
;; --- Icon Wrapper for workspace
(declare icon-shape)
(mf/defc icon-wrapper
[{:keys [shape frame] :as props}]
(let [selected (mf/deref refs/selected-shapes)
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
frame (unchecked-get props "frame")
selected (mf/deref refs/selected-shapes)
selected? (contains? selected (:id shape))
on-mouse-down #(common/on-mouse-down % shape)]
[:g.shape {:class (when selected? "selected")
:on-mouse-down on-mouse-down}
[:& icon-shape {:shape (geom/transform-shape frame shape)}]]))
;; --- Icon Wrapper for viewer
(mf/defc icon-viewer-wrapper
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
frame (unchecked-get props "frame")
{:keys [x y width height]} shape
transform (geom/transform-matrix shape)
show-interactions? (mf/deref refs/show-interactions?)
on-mouse-down (mf/use-callback
(mf/deps shape)
#(common/on-mouse-down-viewer % shape))]
[:g.shape {:on-mouse-down on-mouse-down
:cursor (when (:interactions shape) "pointer")}
[:*
[:& icon-shape {:shape (geom/transform-shape frame shape)}]
(when (and (:interactions shape) show-interactions?)
[:> "rect" #js {:x (- x 1)
:y (- y 1)
:width (+ width 2)
:height (+ height 2)
:transform transform
:fill "#31EFB8"
:stroke "#31EFB8"
:strokeWidth 1
:fillOpacity 0.2}])]]))
;; --- Icon Shape
(mf/defc icon-shape
[{:keys [shape] :as props}]
(let [{:keys [id x y width height metadata rotation content] :as shape} shape
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
{:keys [id x y width height metadata rotation content]} shape
transform (when (and rotation (pos? rotation))
(str/format "rotate(%s %s %s)"
rotation

View file

@ -17,13 +17,15 @@
[uxbox.util.interop :as itr]
[uxbox.util.geom.matrix :as gmt]))
;; --- Image Wrapper
;; --- Image Wrapper for workspace
(declare image-shape)
(mf/defc image-wrapper
[{:keys [shape] :as props}]
(let [selected (mf/deref refs/selected-shapes)
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
selected (mf/deref refs/selected-shapes)
selected? (contains? selected (:id shape))
on-mouse-down (mf/use-callback
(mf/deps shape)
@ -38,18 +40,48 @@
:on-context-menu on-context-menu}
[:& image-shape {:shape shape}]]))
;; --- Image Wrapper for viewer
(mf/defc image-viewer-wrapper
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
{:keys [x y width height]} shape
transform (geom/transform-matrix shape)
show-interactions? (mf/deref refs/show-interactions?)
on-mouse-down (mf/use-callback
(mf/deps shape)
#(common/on-mouse-down-viewer % shape))]
[:g.shape {:on-mouse-down on-mouse-down
:cursor (when (:interactions shape) "pointer")}
[:*
[:& image-shape {:shape shape}]
(when (and (:interactions shape) show-interactions?)
[:> "rect" #js {:x (- x 1)
:y (- y 1)
:width (+ width 2)
:height (+ height 2)
:transform transform
:fill "#31EFB8"
:stroke "#31EFB8"
:strokeWidth 1
:fillOpacity 0.2}])]]))
;; --- Image Shape
(mf/defc image-shape
[{:keys [shape] :as props}]
(let [{:keys [id x y width height rotation metadata]} shape
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
{:keys [id x y width height rotation metadata]} shape
transform (geom/transform-matrix shape)
uri (if (or (> (:thumb-width metadata) width)
(> (:thumb-height metadata) height))
(:thumb-uri metadata)
(:uri metadata))
props (-> (attrs/extract-style-attrs shape)
(itr/obj-assign!
#js {:x x

View file

@ -19,13 +19,15 @@
[uxbox.main.ui.shapes.bounding-box :refer [bounding-box]]
[uxbox.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]]))
;; --- Path Wrapper
;; --- Path Wrapper for workspace
(declare path-shape)
(mf/defc path-wrapper
[{:keys [shape] :as props}]
(let [selected (mf/deref refs/selected-shapes)
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
selected (mf/deref refs/selected-shapes)
selected? (contains? selected (:id shape))
on-mouse-down (mf/use-callback
(mf/deps shape)
@ -44,6 +46,35 @@
:on-context-menu on-context-menu}
[:& path-shape {:shape shape :background? true}]]))
;; --- Path Wrapper for viewer
(mf/defc path-viewer-wrapper
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
{:keys [x y width height]} (geom/shape->rect-shape shape)
transform (geom/transform-matrix shape)
show-interactions? (mf/deref refs/show-interactions?)
on-mouse-down (mf/use-callback
(mf/deps shape)
#(common/on-mouse-down-viewer % shape))]
[:g.shape {:on-mouse-down on-mouse-down
:cursor (when (:interactions shape) "pointer")}
[:*
[:& path-shape {:shape shape}]
(when (and (:interactions shape) show-interactions?)
[:> "rect" #js {:x (- x 1)
:y (- y 1)
:width (+ width 2)
:height (+ height 2)
:transform transform
:fill "#31EFB8"
:stroke "#31EFB8"
:strokeWidth 1
:fillOpacity 0.2}])]]))
;; --- Path Shape
(defn- render-path
@ -68,8 +99,11 @@
(recur buffer (inc index)))))))
(mf/defc path-shape
[{:keys [shape background?] :as props}]
(let [{:keys [id x y width height]} (geom/shape->rect-shape shape)
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
background? (unchecked-get props "background?")
{:keys [id x y width height]} (geom/shape->rect-shape shape)
transform (geom/transform-matrix shape)
pdata (render-path shape)
props (-> (attrs/extract-style-attrs shape)

View file

@ -17,17 +17,17 @@
[uxbox.util.interop :as itr]
[uxbox.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]]))
;; --- Rect Wrapper
(declare rect-shape)
;; --- Rect Wrapper for workspace
(mf/defc rect-wrapper
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
on-mouse-down (mf/use-callback
(mf/deps shape)
#(common/on-mouse-down % shape))
on-mouse-down (mf/use-callback
(mf/deps shape)
#(common/on-mouse-down % shape))
on-context-menu (mf/use-callback
(mf/deps shape)
#(common/on-context-menu % shape))]
@ -35,6 +35,36 @@
:on-context-menu on-context-menu}
[:& rect-shape {:shape shape}]]))
;; --- Rect Wrapper for viewer
(mf/defc rect-viewer-wrapper
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
{:keys [x y width height]} shape
transform (geom/transform-matrix shape)
show-interactions? (mf/deref refs/show-interactions?)
on-mouse-down (mf/use-callback
(mf/deps shape)
#(common/on-mouse-down-viewer % shape))]
[:g.shape {:on-mouse-down on-mouse-down
:cursor (when (:interactions shape) "pointer")}
[:*
[:& rect-shape {:shape shape}]
(when (and (:interactions shape) show-interactions?)
[:> "rect" #js {:x (- x 1)
:y (- y 1)
:width (+ width 2)
:height (+ height 2)
:transform transform
:fill "#31EFB8"
:stroke "#31EFB8"
:strokeWidth 1
:fillOpacity 0.2}])]]))
;; --- Rect Shape
(mf/defc rect-shape

View file

@ -39,15 +39,17 @@
(dom/stop-propagation event)
(common/on-mouse-down event shape)))
;; --- Text Wrapper
;; --- Text Wrapper for workspace
(declare text-shape-html)
(declare text-shape-edit)
(declare text-shape)
(mf/defc text-wrapper
[{:keys [shape] :as props}]
(let [{:keys [id x1 y1 content group]} shape
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
{:keys [id x1 y1 content group]} shape
selected (mf/deref refs/selected-shapes)
edition (mf/deref refs/selected-edition)
edition? (= edition id)
@ -72,6 +74,35 @@
[:& text-shape {:shape shape
:selected? selected?}])]))
;; --- Text Wrapper for viewer
(mf/defc text-viewer-wrapper
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
{:keys [x y width height]} shape
transform (geom/transform-matrix shape)
show-interactions? (mf/deref refs/show-interactions?)
on-mouse-down (mf/use-callback
(mf/deps shape)
#(common/on-mouse-down-viewer % shape))]
[:g.shape {:on-mouse-down on-mouse-down
:cursor (when (:interactions shape) "pointer")}
[:*
[:& text-shape {:shape shape}]
(when (and (:interactions shape) show-interactions?)
[:> "rect" #js {:x (- x 1)
:y (- y 1)
:width (+ width 2)
:height (+ height 2)
:transform transform
:fill "#31EFB8"
:stroke "#31EFB8"
:strokeWidth 1
:fillOpacity 0.2}])]]))
;; --- Text Editor Rendering
(defn- generate-root-styles
@ -342,8 +373,11 @@
(render-text-node root)))
(mf/defc text-shape
[{:keys [shape selected?] :as props}]
(let [{:keys [id x y width height rotation content]} shape]
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
shape (unchecked-get props "selected?")
{:keys [id x y width height rotation content]} shape]
[:foreignObject {:x x
:y y
:transform (geom/transform-matrix shape)

View file

@ -17,13 +17,15 @@
[uxbox.builtins.icons :as i]
[uxbox.common.exceptions :as ex]
[uxbox.main.data.viewer :as dv]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.ui.components.dropdown :refer [dropdown]]
[uxbox.main.ui.hooks :as hooks]
[uxbox.main.ui.keyboard :as kbd]
[uxbox.main.ui.messages :refer [messages]]
[uxbox.main.ui.viewer.header :refer [header]]
[uxbox.main.ui.viewer.thumbnails :refer [thumbnails-panel frame-svg]]
[uxbox.main.ui.viewer.thumbnails :refer [thumbnails-panel]]
[uxbox.main.ui.viewer.frame-viewer :refer [frame-viewer-svg]]
[uxbox.util.data :refer [classnames]]
[uxbox.util.dom :as dom]
[uxbox.util.i18n :as i18n :refer [t tr]])
@ -46,9 +48,9 @@
[:span (t locale "viewer.frame-not-found")]]
:else
[:& frame-svg {:frame frame
:zoom zoom
:objects objects}])]))
[:& frame-viewer-svg {:frame frame
:zoom zoom
:objects objects}])]))
(mf/defc viewer-content
[{:keys [data local index] :as props}]
@ -56,6 +58,13 @@
[toggle-fullscreen fullscreen?] (hooks/use-fullscreen container)
on-click
(fn [event]
(dom/stop-propagation event)
(let [mode (get local :interactions-mode)]
(when (= mode :show-on-click)
(st/emit! dv/flash-interactions))))
on-mouse-wheel
(fn [event]
(when (kbd/ctrl? event)
@ -65,7 +74,6 @@
(st/emit! dv/decrease-zoom)
(st/emit! dv/increase-zoom)))))
on-mount
(fn []
;; bind with passive=false to allow the event to be cancelled
@ -88,7 +96,7 @@
:fullscreen? fullscreen?
:local local
:index index}]
[:div.viewer-content
[:div.viewer-content {:on-click on-click}
(when (:show-thumbnails local)
[:& thumbnails-panel {:index index
:data data}])
@ -99,20 +107,14 @@
;; --- Component: Viewer Page
(def viewer-data-ref
(l/derived :viewer-data st/state))
(def viewer-local-ref
(l/derived :viewer-local st/state))
(mf/defc viewer-page
[{:keys [page-id index token] :as props}]
(mf/use-effect
(mf/deps page-id token)
#(st/emit! (dv/initialize page-id token)))
(let [data (mf/deref viewer-data-ref)
local (mf/deref viewer-local-ref)]
(let [data (mf/deref refs/viewer-data-ref)
local (mf/deref refs/viewer-local-ref)]
(when data
[:& viewer-content {:index index
:local local

View file

@ -0,0 +1,104 @@
;; 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/.
;;
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.viewer.frame-viewer
"The main container for a frame in viewer mode"
(:require
[rumext.alpha :as mf]
[uxbox.common.uuid :as uuid]
[uxbox.util.math :as mth]
[uxbox.util.geom.shapes :as geom]
[uxbox.util.geom.point :as gpt]
[uxbox.util.geom.matrix :as gmt]
[uxbox.common.pages :as cp]
[uxbox.main.ui.shapes.frame :as frame]
[uxbox.main.ui.shapes.circle :as circle]
[uxbox.main.ui.shapes.icon :as icon]
[uxbox.main.ui.shapes.image :as image]
[uxbox.main.ui.shapes.path :as path]
[uxbox.main.ui.shapes.rect :as rect]
[uxbox.main.ui.shapes.text :as text]
[uxbox.main.ui.shapes.group :as group]))
(declare shape-wrapper)
(defn frame-wrapper
[objects]
(mf/fnc frame-wrapper
[{:keys [shape] :as props}]
(let [childs (mapv #(get objects %)
(:shapes shape))
shape-wrapper (mf/use-memo (mf/deps objects)
#(shape-wrapper objects))
frame-shape (mf/use-memo (mf/deps objects)
#(frame/frame-shape shape-wrapper))
shape (geom/transform-shape shape)]
[:& frame-shape {:shape shape :childs childs}])))
(defn group-wrapper
[objects]
(mf/fnc group-wrapper
[{:keys [shape frame] :as props}]
(let [children (mapv #(get objects %)
(:shapes shape))
shape-wrapper (mf/use-memo (mf/deps objects)
#(shape-wrapper objects))
group-shape (mf/use-memo (mf/deps objects)
#(group/group-shape shape-wrapper))]
[:& group-shape {:frame frame
:shape shape
:children children}])))
(defn shape-wrapper
[objects]
(mf/fnc shape-wrapper
[{:keys [frame shape] :as props}]
(let [group-wrapper (mf/use-memo (mf/deps objects) #(group-wrapper objects))]
(when (and shape (not (:hidden shape)))
(let [shape (geom/transform-shape frame shape)
opts #js {:shape shape}]
(case (:type shape)
:curve [:> path/path-viewer-wrapper opts]
:text [:> text/text-viewer-wrapper opts]
:icon [:> icon/icon-viewer-wrapper opts]
:rect [:> rect/rect-viewer-wrapper opts]
:path [:> path/path-viewer-wrapper opts]
:image [:> image/image-viewer-wrapper opts]
:circle [:> circle/circle-viewer-wrapper opts]
:group [:> group-wrapper {:shape shape :frame frame}]
nil))))))
(mf/defc frame-viewer-svg
{::mf/wrap [mf/memo]}
[{:keys [objects frame zoom] :or {zoom 1} :as props}]
(let [modifier (-> (gpt/point (:x frame) (:y frame))
(gpt/negate)
(gmt/translate-matrix))
frame-id (:id frame)
modifier-ids (concat [frame-id] (cp/get-children frame-id objects))
update-fn (fn [state shape-id]
(-> state
(assoc-in [shape-id :modifiers :displacement] modifier)))
objects (reduce update-fn objects modifier-ids)
frame (assoc-in frame [:modifiers :displacement] modifier )
width (* (:width frame) zoom)
height (* (:height frame) zoom)
vbox (str "0 0 " (:width frame 0) " " (:height frame 0))
frame-wrapper (mf/use-memo (mf/deps objects)
#(frame-wrapper objects))]
[:svg {:view-box vbox
:width width
:height height
:version "1.1"
:xmlnsXlink "http://www.w3.org/1999/xlink"
:xmlns "http://www.w3.org/2000/svg"}
[:& frame-wrapper {:shape frame
:view-box vbox}]]))

View file

@ -24,6 +24,28 @@
[uxbox.common.uuid :as uuid]
[uxbox.util.webapi :as wapi]))
(mf/defc interactions-menu
[{:keys [interactions-mode] :as props}]
(let [show-dropdown? (mf/use-state false)
locale (i18n/use-locale)
on-select-mode #(st/emit! (dv/set-interactions-mode %))]
[:div.header-icon
[:a {:on-click #(swap! show-dropdown? not)} i/eye
[:& dropdown {:show @show-dropdown?
:on-close #(swap! show-dropdown? not)}
[:ul.custom-select-dropdown
[:li {:key :hide
:class (classnames :selected (= interactions-mode :hide))
:on-click #(on-select-mode :hide)}
(t locale "viewer.header.dont-show-interactions")]
[:li {:key :show
:class (classnames :selected (= interactions-mode :show))
:on-click #(on-select-mode :show)}
(t locale "viewer.header.show-interactions")]
[:li {:key :show-on-click
:class (classnames :selected (= interactions-mode :show-on-click))
:on-click #(on-select-mode :show-on-click)}
(t locale "viewer.header.show-interactions-on-click")]]]]]))
(mf/defc share-link
[{:keys [page] :as props}]
@ -77,6 +99,8 @@
total (count frames)
on-click #(st/emit! dv/toggle-thumbnails-panel)
interactions-mode (:interactions-mode local)
locale (i18n/use-locale)
profile (mf/deref refs/profile)
@ -105,6 +129,7 @@
[:span.counters (str (inc index) " / " total)]]
[:div.options-zone
[:& interactions-menu {:interactions-mode interactions-mode}]
(when-not anonymous?
[:& share-link {:page (:page data)}])
(when-not anonymous?
@ -127,4 +152,3 @@
i/full-screen)]
]]))

View file

@ -15,7 +15,6 @@
[rumext.alpha :as mf]
[uxbox.builtins.icons :as i]
[uxbox.common.data :as d]
[uxbox.common.pages :as cp]
[uxbox.main.store :as st]
[uxbox.main.data.viewer :as dv]
[uxbox.main.ui.components.dropdown :refer [dropdown']]
@ -78,37 +77,6 @@
[:div.thumbnails-list-inside {:style {:right (str (* @offset 152) "px")}}
children]]])))
(mf/defc frame-svg
{::mf/wrap [mf/memo]}
[{:keys [objects frame zoom] :or {zoom 1} :as props}]
(let [modifier (-> (gpt/point (:x frame) (:y frame))
(gpt/negate)
(gmt/translate-matrix))
frame-id (:id frame)
modifier-ids (concat [frame-id] (cp/get-children frame-id objects))
update-fn (fn [state shape-id]
(-> state
(assoc-in [shape-id :modifiers :displacement] modifier)))
objects (reduce update-fn objects modifier-ids)
frame (assoc-in frame [:modifiers :displacement] modifier )
width (* (:width frame) zoom)
height (* (:height frame) zoom)
vbox (str "0 0 " (:width frame 0) " " (:height frame 0))
frame-wrapper (mf/use-memo (mf/deps objects)
#(exports/frame-wrapper objects))]
[:svg {:view-box vbox
:width width
:height height
:version "1.1"
:xmlnsXlink "http://www.w3.org/1999/xlink"
:xmlns "http://www.w3.org/2000/svg"}
[:& frame-wrapper {:shape frame
:view-box vbox}]]))
(mf/defc thumbnails-summary
[{:keys [on-toggle-expand on-close total] :as props}]
[:div.thumbnails-summary
@ -122,7 +90,7 @@
[:div.thumbnail-item {:on-click #(on-click % index)}
[:div.thumbnail-preview
{:class (classnames :selected selected?)}
[:& frame-svg {:frame frame :objects objects}]]
[:& exports/frame-svg {:frame frame :objects objects}]]
[:div.thumbnail-info
[:span.name (:name frame)]]])

View file

@ -25,6 +25,8 @@
[uxbox.main.ui.workspace.sidebar.options.image :as image]
[uxbox.main.ui.workspace.sidebar.options.text :as text]
[uxbox.main.ui.workspace.sidebar.options.page :as page]
[uxbox.main.ui.workspace.sidebar.options.interactions :refer [interactions-menu]]
[uxbox.main.ui.components.tab-container :refer [tab-container tab-element]]
[uxbox.util.i18n :refer [tr]]))
;; --- Options
@ -45,29 +47,43 @@
:image [:& image/options {:shape shape}]
nil)])
(mf/defc shape-options-wrapper
[{:keys [shape-id page-id] :as props}]
(let [shape-iref (-> (mf/deps shape-id page-id)
(mf/use-memo
#(-> (l/in [:objects shape-id])
(l/derived refs/workspace-data))))
shape (mf/deref shape-iref)]
[:& shape-options {:shape shape}]))
(mf/defc options-toolbox
{:wrap [mf/memo]}
[{:keys [page selected] :as props}]
(let [close #(st/emit! (udw/toggle-layout-flag :element-options))
selected (mf/deref refs/selected-shapes)]
[:div.element-options.tool-window
;; [:div.tool-window-bar
;; [:div.tool-window-icon i/options]
;; [:span (tr "ds.settings.element-options")]
;; [:div.tool-window-close {:on-click close} i/close]]
[:& align-options]
[:div.tool-window-content
[:div.element-options
(if (= (count selected) 1)
[:& shape-options-wrapper {:shape-id (first selected)
:page-id (:id page)}]
[:& page/options {:page page}])]]]))
on-change-tab #(st/emit! (udw/set-options-mode %))
options-mode (mf/deref refs/options-mode)
selected (mf/deref refs/selected-shapes)
shape-id (first selected)
page-id (:id page)
shape-iref (-> (mf/deps shape-id page-id)
(mf/use-memo
#(-> (l/in [:objects shape-id])
(l/derived refs/workspace-data))))
shape (mf/deref shape-iref)]
[:div.tool-window
;; [:div.tool-window-bar
;; [:div.tool-window-icon i/options]
;; [:span (tr "ds.settings.element-options")]
;; [:div.tool-window-close {:on-click close} i/close]]
[:div.tool-window-content
[:& tab-container {:on-change-tab on-change-tab :selected options-mode}
[:& tab-element
{:id :design :title (tr "workspace.options.design")}
[:div.element-options
[:& align-options]
[:div
(if (= (count selected) 1)
[:& shape-options {:shape shape}]
[:& page/options {:page page}])]]]
[:& tab-element
{:id :prototype :title (tr "workspace.options.prototype")}
[:div.element-options
[:& interactions-menu {:shape shape}]]]]
]]))

View file

@ -64,7 +64,7 @@
delta (if (= attr :x)
(gpt/point (math/neg (- pval cval)) 0)
(gpt/point 0 (math/neg (- pval cval))))]
;; TODO: Change so not apply the modifiers until blur
(st/emit! (udw/set-modifiers #{(:id shape)} {:displacement delta})
(udw/apply-modifiers #{(:id shape)}))))
@ -94,9 +94,7 @@
(:name size-preset)
[:span (:width size-preset) " x " (:height size-preset)]]))]]]
[:span.orientation-icon {on-click #(on-orientation-clicked :vert)} i/size-vert]
[:span.orientation-icon {on-click #(on-orientation-clicked :horiz)} i/size-horiz]
]
[:span.orientation-icon {on-click #(on-orientation-clicked :horiz)} i/size-horiz]]
;; WIDTH & HEIGHT
[:div.row-flex

View file

@ -14,10 +14,61 @@
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.ui.colorpicker :as cp]
[uxbox.main.ui.components.dropdown :refer [dropdown]]
[uxbox.util.data :refer [read-string]]
[uxbox.util.dom :as dom]
[uxbox.util.i18n :refer [tr]]))
(mf/defc interactions-menu
[{:keys [shape] :as props}]
(let [interaction (first (:interactions shape)) ;; TODO: in the future we may have several interactions in one shape
destination (first (deref (refs/objects-by-id [(:destination interaction)])))
frames (mf/deref refs/frames)
show-frames-dropdown? (mf/use-state false)
on-set-blur
#(reset! show-frames-dropdown? false)
on-select-destination
#(if (nil? %)
(st/emit! (dw/update-shape (:id shape) {:interactions []}))
(st/emit! (dw/update-shape (:id shape) {:interactions [{:event-type :click
:action-type :navigate
:destination %}]})))
on-navigate
#(st/emit! (dw/select-shapes #{(:id destination)}))]
(if (not shape)
[:*
[:div.interactions-help-icon i/interaction]
[:div.interactions-help (tr "workspace.options.select-a-shape")]
[:div.interactions-help-icon i/play]
[:div.interactions-help (tr "workspace.options.use-play-button")]]
[:div.element-set {:on-blur on-set-blur}
[:div.element-set-title
[:span (tr "workspace.options.navigate-to")]]
[:div.element-set-content
[:div.row-flex
[:div.custom-select.flex-grow {:on-click #(reset! show-frames-dropdown? true)}
(if destination
[:span (:name destination)]
[:span (tr "workspace.options.select-artboard")])
[:span.dropdown-button i/arrow-down]
[:& dropdown {:show @show-frames-dropdown?
:on-close #(reset! show-frames-dropdown? false)}
[:ul.custom-select-dropdown
[:li.dropdown-separator {:key nil
:on-click #(on-select-destination nil)}
(tr "workspace.options.none")]
(for [frame frames]
(when (not= (:id frame) (:id shape)) ; A frame cannot navigate to itself
[:li {:key (:id frame)
:on-click #(on-select-destination (:id frame))}
(:name frame)]))]]]
[:span.navigate-icon {on-click on-navigate} i/navigate]]]])))
;; --- Helpers
;; (defn- on-change
@ -469,25 +520,25 @@
;; --- Interactions Menu
(def +initial-form+
{:trigger :click
:action :show})
(mf/defc interactions-menu
[{:keys [menu shape] :as props}]
#_(let [form (mf/use-state nil)
interactions (:interactions shape)]
[:div.element-set {:key (str (:id menu))}
[:div.element-set-title (:name menu)]
[:div.element-set-content
(if form
[:& interactions-form {:form form :shape shape}]
[:div
[:& interactions-list {:form form :shape shape}]
[:input.btn-primary.btn-small
{:value "New interaction"
:on-click #(reset! form +initial-form+)
:type "button"}]])]]))
;; (def +initial-form+
;; {:trigger :click
;; :action :show})
;;
;; (mf/defc interactions-menu
;; [{:keys [menu shape] :as props}]
;; #_(let [form (mf/use-state nil)
;; interactions (:interactions shape)]
;; [:div.element-set {:key (str (:id menu))}
;; [:div.element-set-title (:name menu)]
;; [:div.element-set-content
;; (if form
;; [:& interactions-form {:form form :shape shape}]
;; [:div
;; [:& interactions-list {:form form :shape shape}]
;; [:input.btn-primary.btn-small
;; {:value "New interaction"
;; :on-click #(reset! form +initial-form+)
;; :type "button"}]])]]))
;; --- Not implemented stuff

View file

@ -9,8 +9,8 @@
(defn debug-none! [] (reset! *debug* #{}))
(defn debug! [option] (swap! *debug* conj option))
(defn -debug! [option] (swap! *debug* disj option))
(defn debug? [option] (@*debug* option))
(defn ^:export debug? [option] (@*debug* option))
(defn ^:export toggle-debug [name] (let [option (keyword name)]
(if (debug? option)
@ -18,7 +18,7 @@
(debug! option))))
(defn ^:export debug-all [name] (debug-all!))
(defn tap
(defn ^:export tap
"Transducer function that can execute a side-effect `effect-fn` per input"
[effect-fn]
@ -30,7 +30,7 @@
(effect-fn input)
(rf result input)))))
(defn logjs
(defn ^:export logjs
([str] (tap (partial logjs str)))
([str val]
(js/console.log str (clj->js val))